security: redact credentials from config.get gateway responses#9858
security: redact credentials from config.get gateway responses#9858grp06 merged 10 commits intoopenclaw:mainfrom
Conversation
Additional Comments (2)
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/config/redact-snapshot.ts
Line: 283:286
Comment:
**Redaction depends on logging config**
`redactConfigSnapshot()` calls `getDefaultRedactPatterns()` but then passes them into `redactSensitiveText(snapshot.raw, { mode: "tools", patterns })`, which means redaction can still be effectively disabled if `redactSensitiveText` is invoked elsewhere with `mode: "off"` (and more importantly: this function is importing redaction config concepts from logging into a security boundary). For `config.get`, redaction should be unconditional and not depend on runtime logging redaction settings; otherwise secrets can leak to any WS client when redaction is turned off.
How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/config/redact-snapshot.ts
Line: 250:270
Comment:
**Misses sensitive non-string values**
`redactObject()` only masks sensitive keys when the value is a string. If credentials are stored as numbers (common for Telegram IDs/secrets mistakenly), buffers, or other scalar types, they will be returned to clients unredacted. Given this is guarding a gateway response (`config.get`), the masking should cover any primitive value (e.g., number/boolean) for sensitive keys, or stringify+mask, otherwise secrets can still leak depending on config shape.
How can I resolve this? If you propose a fix, please make it concise. |
|
Doesn't that mean that when editing your config in the Web UI, all keys are trashed, basically disabling the whole system? |
ok so the Web UI round-trips config through config.get → edit → config.set, so masked values would |
|
The only one is that that WebSocket API is only for the owner of the system. The documentation explicitly states that it isn't hardened against attack, even though you need a token to get in. Giving someone access to it is akin to giving them a login to the system and user openclaw is running on. And it's not just those tokens. You can add all kinds of nasty backdoors to the system if you can edit the config. |
The config.get gateway method returned the full config snapshot including channel credentials (Discord tokens, Slack botToken/appToken, Telegram botToken, Feishu appSecret, etc.), model provider API keys, and gateway auth tokens in plaintext. Any WebSocket client—including the unauthenticated Control UI when dangerouslyDisableDeviceAuth is set—could read every secret. This adds redactConfigSnapshot() which: - Deep-walks the config object and masks any field whose key matches token, password, secret, or apiKey patterns - Uses the existing redactSensitiveText() to scrub the raw JSON5 source - Preserves the hash for change detection - Includes 15 test cases covering all channel types
99c3e9f to
f9d41bc
Compare
|
Fixed in the latest push with a sentinel-based approach:
So the Web UI can safely display redacted credentials, and saving - even without changing any secrets - preserves all original values. Also removed the dependency on the logging module's 22 tests including a full redact → restore round-trip test. Ready for review! :) |
please review again |
|
It's pretty much useless to mask anything for the web UI because you have access to the unrestricted agent there... What you're trying to do is like putting a high-security lock on the off switch of the TV in your living room so nobody can turn it off. They can only pull the plug out of the TV, or out of the wall outlet, or cut the power cable, or turn off the circuit breaker. |
|
You're right — the threat model doesn't hold up. Anyone with Web UI access has an unrestricted agent that can read the config file directly, so redacting credentials in one API response doesn't provide meaningful security. Withdrawing this PR. |
Additional Comments (1)
In Also appears in Prompt To Fix With AIThis is a comment left during a code review.
Path: src/gateway/server-methods/config.ts
Line: 252:273
Comment:
**Redaction restoration not applied**
In `config.patch`, the merge result is passed through `restoreRedactedValues` (`restoredMerge`), but the code still writes and returns `validated.config` (built from `resolved`). This means if a client sends back `__OPENCLAW_REDACTED__` for a sensitive key, the sentinel can be persisted to disk (and echoed back) instead of restoring from `snapshot.config`. This breaks the intended “UI round-trip doesn’t corrupt credentials” behavior.
Also appears in `config.apply` (response returns `validated.config` instead of the restored object).
How can I resolve this? If you propose a fix, please make it concise. |
|
Landed by updating the PR branch to include latest main (no force push) and then squash-merging onto main.
Thanks @abdelsfane! |
|
What? We just established that this is window-dressing, and, at best, dangerous, as it leads to users thinking that giving third parties access to the gateway UI is safe. |
|
I noticed a couple of issues with the current implementation:
When a config field contains an environment variable reference like "${MY_API_KEY}", config.get still replaces it with "OPENCLAW_REDACTED". This prevents agents from validating configuration correctness (checking if env vars are properly set, detecting typos in variable names, etc.). Would it make sense to preserve environment variable placeholder syntax (e.g., values matching ^${[A-Z0-9_]+}$) and only redact resolved plaintext values?
The more critical issue: config.patch resolves environment variable placeholders into their actual plaintext values when writing the config back to disk. This means: • User starts with: "apiKey": "${MY_API_KEY}" in openclaw.json |
|
This will also redact
And those a numbers, no idea what the UI makes out of getting a String for a number. |
|
hmm, I'm a bit confused, is this still being merged? What about Henry's concerns unless you're saying this is better than nothing so why not? |
|
Yes, it got merged, and it has some issues aside from being window dressing. That |
how do you suggest we move forward? |
I'd say ping @grp06 and ask them to revert the commit. Then we can talk about whether this change makes any sense at all, and if they still want it, we work on it to get it ready for tomorrow's merge window. I'd be happy to help out and go through the code and logic with a fine-toothed comb. |
…law#9858) * security: add skill/plugin code safety scanner module * security: integrate skill scanner into security audit * security: add pre-install code safety scan for plugins * style: fix curly brace lint errors in skill-scanner.ts * docs: add changelog entry for skill code safety scanner * security: redact credentials from config.get gateway responses The config.get gateway method returned the full config snapshot including channel credentials (Discord tokens, Slack botToken/appToken, Telegram botToken, Feishu appSecret, etc.), model provider API keys, and gateway auth tokens in plaintext. Any WebSocket client—including the unauthenticated Control UI when dangerouslyDisableDeviceAuth is set—could read every secret. This adds redactConfigSnapshot() which: - Deep-walks the config object and masks any field whose key matches token, password, secret, or apiKey patterns - Uses the existing redactSensitiveText() to scrub the raw JSON5 source - Preserves the hash for change detection - Includes 15 test cases covering all channel types * security: make gateway config writes return redacted values * test: disable control UI by default in gateway server tests * fix: redact credentials in gateway config APIs (openclaw#9858) (thanks @abdelsfane) --------- Co-authored-by: George Pickett <gpickett00@gmail.com>
…law#9858) * security: add skill/plugin code safety scanner module * security: integrate skill scanner into security audit * security: add pre-install code safety scan for plugins * style: fix curly brace lint errors in skill-scanner.ts * docs: add changelog entry for skill code safety scanner * security: redact credentials from config.get gateway responses The config.get gateway method returned the full config snapshot including channel credentials (Discord tokens, Slack botToken/appToken, Telegram botToken, Feishu appSecret, etc.), model provider API keys, and gateway auth tokens in plaintext. Any WebSocket client—including the unauthenticated Control UI when dangerouslyDisableDeviceAuth is set—could read every secret. This adds redactConfigSnapshot() which: - Deep-walks the config object and masks any field whose key matches token, password, secret, or apiKey patterns - Uses the existing redactSensitiveText() to scrub the raw JSON5 source - Preserves the hash for change detection - Includes 15 test cases covering all channel types * security: make gateway config writes return redacted values * test: disable control UI by default in gateway server tests * fix: redact credentials in gateway config APIs (openclaw#9858) (thanks @abdelsfane) --------- Co-authored-by: George Pickett <gpickett00@gmail.com>
|
Awsome. Thanks for looking into it! If we can make this work I think the
community would appreciate it.
…On Thu, Feb 5, 2026, 20:19 Henry Loenwind ***@***.***> wrote:
*HenryLoenwind* left a comment (openclaw/openclaw#9858)
<#9858 (comment)>
ok, I tested it. The GUI dosn't croak, but naturally the field stays empty.
image.png (view on web)
<https://github.com/user-attachments/assets/245bcb14-0d67-4272-b46f-49b919e99bed> image.png
(view on web)
<https://github.com/user-attachments/assets/614f6645-3660-42c0-88e1-f7c3e7ba19f9>
—
Reply to this email directly, view it on GitHub
<#9858 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AHXKWGS5GZZP3IRM4WM7YLL4KQB4ZAVCNFSM6AAAAACUEJY65SVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTQNJXG4YTQMBZGQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
ok, found it. There are two sources:
The difference to yours is that this is only called when it wants to render a text field. Now, I'm not happy that there is no uiHints source for non-plugin, non-channel config keys, but at least we have some source and he isSensitivePath() usage is plausible. So, my suggestion:
Does that sound like a plan? |
|
oh, come on... in schema.ts does the exact same thing and applies that hint to the schema. Ok, that changes some things: You don't need isSensitiveKey() anymore at all. You can go be the uiHint in the schema alone and are in sync with the UI hat way. And the UI doesn't need its isSensitivePath() either---that can be dropped, as the schema already has the same thing. |
|
Thanks for the progress! I'm looking into this now |
|
Ok addressed all three points:
Let me know what you think before I push |
…ction Address PR openclaw#9858 review feedback: 1. Fix /token/i regex to /token(?!s)/i — no longer matches 'tokens', 'softThresholdTokens', preventing numeric fields from showing as redacted in the dashboard UI. 2. Make redact-snapshot.ts schema-driven: lookupSensitive() checks uiHints (direct → wildcard → regex fallback) instead of maintaining a separate set of key patterns. Eliminates drift between backend redaction, schema hints, and UI sensitivity detection. 3. Extract loadSchemaWithPlugins() in config.ts so config.get, config.set, config.patch, and config.apply all pass uiHints to redactConfigSnapshot/restoreRedactedValues. 4. Remove duplicate isSensitivePath from UI config-form code — the schema's hint.sensitive is now the sole authority. 5. Add 13 new tests covering token regex fix, uiHints-driven redaction, sensitive:false overrides, wildcard hints, and round-trip with custom hints.
|
Hey, that looks good. Atm I'm working on adding the isSensitive hint to any key, and I'm 99% there. I have the mechanism (a zod registry), and a way to apply it. I just have to stomp some tiny thing that makes my code say it applied the flag but it never showing up... ;) When I have that, I'll PR it into your branch in your repo and do a through review of your code. |
|
And I found the bug---Gemini wrote me a nice recursion, but it didn't assign the result of the recursion, so everything inside of the top-level object just got lost. Took me over half an hour to get that... It'll probably be another half hour at least until I have it cleaned up and a commit ready. I'll send that your way for review then before going over the config and tagging everything that needs to be sensitive. |
|
awesome, take your time! |
|
Great to see this security hardening on the API layer. However, agents can still access secrets directly via filesystem reads: This means the redaction can be bypassed entirely via prompt injection from untrusted content (emails, webhooks, fetched web pages) tricking the agent into reading and leaking the config: Some possible mitigations to consider:
The API hardening is valuable for preventing accidental exposure in logs/history. Filesystem access is the deliberate exfiltration path worth closing. |
|
@realugbun See above, we've discussed this thoroughly. That's why this PR was discarded half-done. It's windows dressing that results in a false impression of security. As long as the AI has full system access, it can access everything. There's no way around it---if in doubt, it can simply write a program that extracts the tokens from the memory space of the gateway and call that. |
|
@abdelsfane Done, see the PR to your fork. |
Replaces hardcoded regex-based credential redaction with a schema-driven approach using z.registry(). Each sensitive field is now annotated with .register(sensitive, true) in the Zod schema, making mapSensitivePaths() the single source of truth for which fields to redact. Fixes regressions from openclaw#9858 where numerical token fields were corrupted and dashboard config display was broken. Also adds env-var placeholder exemption so ${VAR_NAME} values are not redacted. Co-authored-by: HenryLoenwind <HenryLoenwind@users.noreply.github.com>
…law#9858) * security: add skill/plugin code safety scanner module * security: integrate skill scanner into security audit * security: add pre-install code safety scan for plugins * style: fix curly brace lint errors in skill-scanner.ts * docs: add changelog entry for skill code safety scanner * security: redact credentials from config.get gateway responses The config.get gateway method returned the full config snapshot including channel credentials (Discord tokens, Slack botToken/appToken, Telegram botToken, Feishu appSecret, etc.), model provider API keys, and gateway auth tokens in plaintext. Any WebSocket client—including the unauthenticated Control UI when dangerouslyDisableDeviceAuth is set—could read every secret. This adds redactConfigSnapshot() which: - Deep-walks the config object and masks any field whose key matches token, password, secret, or apiKey patterns - Uses the existing redactSensitiveText() to scrub the raw JSON5 source - Preserves the hash for change detection - Includes 15 test cases covering all channel types * security: make gateway config writes return redacted values * test: disable control UI by default in gateway server tests * fix: redact credentials in gateway config APIs (openclaw#9858) (thanks @abdelsfane) --------- Co-authored-by: George Pickett <gpickett00@gmail.com>







Summary
The
config.getgateway method returns the full config snapshot including all channel credentials, model provider API keys, and gateway auth tokens in plaintext. Any WebSocket client can read every secret — including the unauthenticated Control UI whendangerouslyDisableDeviceAuthis set.This PR adds a
redactConfigSnapshot()function that sanitizes the config before it reaches clients:token,password,secret, orapiKeypatterns (aligned with the existingSENSITIVE_PATTERNSinschema.ts)redactSensitiveText()to the raw JSON5 source so no credential leaks through either pathCredentials redacted
botToken,webhookSecrettokenbotToken,appToken,userToken,signingSecretappSecretappPasswordauth.token,auth.password,remote.token,remote.passwordproviders.*.apiKeyweb.search.apiKeyapiKeyMasking behavior
abc123…xyz(keeps first 6 + last 4)***maskToken()logic fromsrc/logging/redact.tsFiles changed
src/config/redact-snapshot.ts— new redaction utilitysrc/config/redact-snapshot.test.ts— 15 test casessrc/gateway/server-methods/config.ts— one-line change to wrap the responseCHANGELOG.md— added entry under FixesTest plan
oxlint+oxfmt)Greptile Overview
Greptile Summary
This PR introduces redaction for Gateway
config.getresponses by addingsrc/config/redact-snapshot.tsand wrapping theconfig.gethandler to return a sanitizedConfigFileSnapshot(masking sensitive keys like token/password/secret/apiKey in both the parsed object and raw JSON5 text). It also adds write-side support (restoreRedactedValues) so UI round-trips can keep credentials intact when users submit configs containing the redaction sentinel.In addition, it adds a new “skill/plugin code safety scanner” (
src/security/skill-scanner.ts) and integrates it into plugin install flow (src/plugins/install.ts) and deep security audit (src/security/audit-extra.ts,src/security/audit.ts), with tests covering scanner rules and audit/install warnings.Confidence Score: 3/5
config.patch/config.applycurrently return (and inconfig.patch, write) the non-restored config object, which can persist the redaction sentinel to disk when clients round-trip redacted configs. Fixing that should make the change low risk.