fix(config): restore SecretRef id under sensitive token paths on config round-trip#55034
fix(config): restore SecretRef id under sensitive token paths on config round-trip#55034D3N1ALL wants to merge 3 commits into
Conversation
Greptile SummaryThis PR fixes a bug where saving any config through the Web UI would fail with an Key changes:
Confidence Score: 5/5Safe to merge — targeted fix with a regression test, no risky side-effects identified. The fix is minimal, well-scoped, and symmetric with the existing redact-side logic. The new condition only triggers when hints[candidate]?.sensitive === true && isSecretRefShape(value) && value.id === REDACTED_SENTINEL, which is precisely the state produced by redactSecretRefId on the redact side. Scenarios outside this condition (user supplying a real new id, non-SecretRef nested objects, the guessing-path code) are unaffected. The regression test closes the loop end-to-end. No files require special attention.
|
| Filename | Overview |
|---|---|
| src/config/redact-snapshot.ts | Core fix: detects partially-redacted SecretRef at a sensitive hints path and restores the whole original object, symmetric with the redact side's redactSecretRefId call. |
| src/config/redact-snapshot.test-hints.ts | Adds channels.discord.token and channels.discord.accounts.*.token as sensitive paths in the test hints fixture. |
| src/config/redact-snapshot.test.ts | Adds regression test verifying env-source SecretRef at channels.discord.token is correctly redacted and fully restored on round-trip. |
Reviews (1): Last reviewed commit: "fix(config): restore SecretRef id under ..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7dbfb675bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| hints[candidate]?.sensitive === true && | ||
| isSecretRefShape(objValue) && | ||
| objValue.id === REDACTED_SENTINEL | ||
| ) { | ||
| result[key] = restoreOriginalValueOrThrow({ key, path: candidate, original: orig }); |
There was a problem hiding this comment.
Preserve edited SecretRef metadata when id is redacted
This branch restores the entire original object whenever a sensitive SecretRef arrives with id === REDACTED_SENTINEL, which silently drops user edits to non-secret fields like source or provider. A common case is editing only channels.discord.token.provider in the config UI/raw editor while leaving the redacted id placeholder; after this code runs, the provider change is discarded because result[key] is replaced with orig[key]. Restore should merge and only substitute the secret id field so non-sensitive updates are preserved.
Useful? React with 👍 / 👎.
|
The |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99198718bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| hints[candidate]?.sensitive === true && | ||
| isSecretRefShape(objValue) && | ||
| objValue.id === REDACTED_SENTINEL | ||
| ) { | ||
| result[key] = restoreOriginalValueOrThrow({ key, path: candidate, original: orig }); |
There was a problem hiding this comment.
Preserve non-secret SecretRef fields during restore
When a sensitive SecretRef comes back with id === REDACTED_SENTINEL, this branch restores orig[key] wholesale, which discards legitimate edits to non-secret fields (source, provider, etc.) made in the same save request. For example, changing channels.discord.token.provider while leaving the redacted id placeholder will silently revert provider to its old value. Restore should only substitute the secret id from the original object (or merge field-by-field), not replace the entire object.
Useful? React with 👍 / 👎.
Problem
When
channels.discord.token(or any field whose path ends with a token-like suffix) is stored as an env-sourceSecretRefobject — e.g.:{ "channels": { "discord": { "token": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" } } } }saving any setting in the Control UI would fail with:
This blocked all saves across the entire Web UI — workspace, tools, identity settings — not just Discord config. Rebuilding the server did not help because the issue was in the gateway's redaction/restore logic, not in stored state.
Root Cause
The redaction layer correctly redacts the
idfield of aSecretRefat a sensitive path (e.g.channels.discord.token) by replacing it with__OPENCLAW_REDACTED__. On the restore path, the gateway is supposed to substitute the sentinel back with the real value from the on-disk snapshot before validation.The restore succeeded for paths like
models.providers.*.apiKey.idbecauseisSensitivePathmatched the full path string against/api.?key/i(unanchored). It silently failed forchannels.discord.token.idbecause the sensitive pattern for token fields is/token$/i(end-anchored), and.idas a suffix breaks the match.The sentinel was therefore left in place, failing the
EnvSecretRefSchemaregex validator forid.Fix
In
restoreRedactedValuesWithLookup, when a path is matched in the hints lookup as sensitive and the incoming value is an object, detect the case where that object is a partially-redactedSecretRef(i.e.isSecretRefShapeis true andid === REDACTED_SENTINEL). In that case, restore the whole original value from the snapshot rather than recursing into the object and leaving the sentinel unresolved.This is symmetric with the redact side, which already calls
redactSecretRefIdto partially redactSecretRefobjects at sensitive paths.Tests
channels.discord.tokenandchannels.discord.accounts.*.tokento the redaction test hints fixture.round-trips channels.discord.token as env-source SecretRef— verifies that theidfield is redacted in the outgoing snapshot, thesource/providerstructural fields are preserved, andrestoreRedactedValuesfully reconstructs the original config.Closes #32445 (partial — the type fix landed in #32490; this fixes the missing restore-side handling that made the UI unusable when a SecretRef was stored for this field).
Related: #50537, #46109