Summary
Guarantee that a published or exported project never carries a credential, by centralizing the redaction and adding a test per egress path that asserts it.
Why
A .geolibre.json can plausibly reach places a secret should not: share.geolibre.app (public or unlisted, so a URL anyone can fetch), a standalone HTML export, a project handed to a colleague as a file, a Jupyter notebook committed to a repository.
Meanwhile the app holds a growing set of credentials that are conceptually attached to layers: custom request headers for authenticated 3D Tiles, PostGIS connection strings, geocoding apiKeys in the project preferences, Cesium ion and Google Maps tokens, and whatever an external plugin decides to persist in its own layer or plugin state.
The publish path in useProjectFileActions.ts already does careful work here — it rewrites the project on the way out, and there is precedent for deliberately not stamping shares with information that turned out to disclose too much. But the protection is currently a property of the code paths that happen to exist rather than an invariant the codebase enforces. A new layer type, a new plugin storing an auth header in its state, or a new export path can quietly break it, and nothing fails.
The failure is also silent and delayed: an unlisted project with a key in it will work perfectly and disclose the key for as long as it exists.
Proposal
1. Mark credential-bearing fields once
Identify, in packages/core, every field in the project schema that may hold a secret — request headers, connection strings, apiKeys, plugin-state fields declared as sensitive — and mark them in one place rather than by name at each call site.
2. One redaction helper, used by every egress
A single redactCredentials(project) applied by: the share upload, Save/Save As (with a prompt — a local save may legitimately want to keep a connection string, and the user should choose knowingly), the standalone HTML export, @geolibre/embed payloads, the collaboration snapshot pushed to the relay, and the Python widget's project serialization.
The collaboration path deserves emphasis: a snapshot goes to a relay and is fanned out to every participant, including view-only guests. Anything sensitive in the project state is disclosed to everyone in the session today.
3. Tests as the actual deliverable
The mechanism is easy; keeping it true is the point. Add a test per egress path that builds a project containing a marked credential in every marked field and asserts the serialized output contains none of them. Add a schema-level test that fails when a new field is added to a credential-bearing type without an explicit sensitive/not-sensitive decision, so the next such field cannot be added silently.
4. Tell the user what happened
When redaction removes something, say so at the point of sharing: "the token for this 3D Tiles layer was not included — recipients will need their own, or use a brokered credential." Silent removal turns into a bug report about a broken layer.
Scope notes
- Plugin state is the hardest part. External plugins can persist arbitrary state; the plugin API should let a plugin declare fields as sensitive (
docs/plugin-api.md), and unknown plugin state should be handled by a documented, conservative default.
- Coordinate with the credential-broker issue: a brokered reference is not a secret and should survive redaction, which is what makes brokering the working answer for shared maps.
- Verify the current behavior first and write it down. Part of the value here is a documented invariant in
docs/project-format.md where today there is an assumption.
Effort
Small to medium, and high value for the size — mostly tests and one helper.
Part of #1665 (access control umbrella).
Summary
Guarantee that a published or exported project never carries a credential, by centralizing the redaction and adding a test per egress path that asserts it.
Why
A
.geolibre.jsoncan plausibly reach places a secret should not:share.geolibre.app(public or unlisted, so a URL anyone can fetch), a standalone HTML export, a project handed to a colleague as a file, a Jupyter notebook committed to a repository.Meanwhile the app holds a growing set of credentials that are conceptually attached to layers: custom request headers for authenticated 3D Tiles, PostGIS connection strings, geocoding
apiKeysin the project preferences, Cesium ion and Google Maps tokens, and whatever an external plugin decides to persist in its own layer or plugin state.The publish path in
useProjectFileActions.tsalready does careful work here — it rewrites the project on the way out, and there is precedent for deliberately not stamping shares with information that turned out to disclose too much. But the protection is currently a property of the code paths that happen to exist rather than an invariant the codebase enforces. A new layer type, a new plugin storing an auth header in its state, or a new export path can quietly break it, and nothing fails.The failure is also silent and delayed: an unlisted project with a key in it will work perfectly and disclose the key for as long as it exists.
Proposal
1. Mark credential-bearing fields once
Identify, in
packages/core, every field in the project schema that may hold a secret — request headers, connection strings,apiKeys, plugin-state fields declared as sensitive — and mark them in one place rather than by name at each call site.2. One redaction helper, used by every egress
A single
redactCredentials(project)applied by: the share upload, Save/Save As (with a prompt — a local save may legitimately want to keep a connection string, and the user should choose knowingly), the standalone HTML export,@geolibre/embedpayloads, the collaboration snapshot pushed to the relay, and the Python widget's project serialization.The collaboration path deserves emphasis: a snapshot goes to a relay and is fanned out to every participant, including view-only guests. Anything sensitive in the project state is disclosed to everyone in the session today.
3. Tests as the actual deliverable
The mechanism is easy; keeping it true is the point. Add a test per egress path that builds a project containing a marked credential in every marked field and asserts the serialized output contains none of them. Add a schema-level test that fails when a new field is added to a credential-bearing type without an explicit sensitive/not-sensitive decision, so the next such field cannot be added silently.
4. Tell the user what happened
When redaction removes something, say so at the point of sharing: "the token for this 3D Tiles layer was not included — recipients will need their own, or use a brokered credential." Silent removal turns into a bug report about a broken layer.
Scope notes
docs/plugin-api.md), and unknown plugin state should be handled by a documented, conservative default.docs/project-format.mdwhere today there is an assumption.Effort
Small to medium, and high value for the size — mostly tests and one helper.
Part of #1665 (access control umbrella).