#21901: fix: guard writeConfigFile against persisting redaction sentinels
docs
size: XS
Cluster:
Config Redaction Improvements
## Summary
Closes #18102.
`writeConfigFile` had no safety check for the `__OPENCLAW_REDACTED__` redaction sentinel. While the gateway's config write handlers (`config.set`, `config.apply`, `config.patch`) call `restoreRedactedValues` before writing, other callers such as `doctor --fix` pass config objects directly to `writeConfigFile` without sentinel detection. If a redacted placeholder ever reached the write path, it would be serialized to disk, permanently destroying the original credential.
This PR adds a **universal pre-write guard** inside `writeConfigFile` that:
- Scans the final serialized JSON for `REDACTED_SENTINEL` after all transformations (merge-patch, validation, env-var restoration)
- Throws with a descriptive error **before any file I/O**, keeping the on-disk config intact
- Logs the blocked write via `deps.logger.error` for forensic visibility
This protects **all** callers of `writeConfigFile` (doctor, onboarding, security fix, CLI config set, etc.), not just the gateway path.
## TL;DR for maintainers
One sentinel string check (`json.includes(REDACTED_SENTINEL)`) right after `JSON.stringify` and before any disk write. ~15 lines of guard code + ~30 lines of test. Zero behaviour change for configs that don't contain the sentinel.
## Test plan
- [x] New test: `writeConfigFile` rejects a config where `botToken` is set to `__OPENCLAW_REDACTED__`
- [x] New test: on-disk config file is verified unchanged after the rejected write
- [x] All existing `io.write-config.test.ts` tests pass (9/9)
- [x] `redact-snapshot.test.ts` tests pass (57/57)
- [x] `env-preserve-io.test.ts` tests pass (4/4)
<!-- AI-assisted (protocol-zero + Claude) -->
Made with [Cursor](https://cursor.com)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds a universal pre-write guard to prevent the redaction sentinel `__OPENCLAW_REDACTED__` from being persisted to disk. While the gateway's config handlers already call `restoreRedactedValues` before writing, other code paths like `doctor --fix` pass config objects directly to `writeConfigFile` without sentinel detection.
The fix adds a simple string check after `JSON.stringify` and before any file I/O. If the sentinel is found, the function logs an error and throws, keeping the on-disk config unchanged.
- Prevents credential destruction across all `writeConfigFile` callers
- Test verifies both rejection and disk preservation
- Zero behavior change for configs without the sentinel
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The change is a defensive guard that only throws when a bug would otherwise destroy credentials. The implementation is simple (a string check after JSON.stringify), well-tested (verifies rejection and disk preservation), and cannot introduce regressions since it only activates when the sentinel is present (which should never happen in valid flows). The fix protects all writeConfigFile callers universally.
- No files require special attention
<sub>Last reviewed commit: 8e87d93</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#11208: fix(config): prevent __OPENCLAW_REDACTED__ corruption on config writes
by janckerchen · 2026-02-07
86.0%
#23654: security(cli): redact sensitive values in config get output
by SleuthCo · 2026-02-22
82.0%
#19129: fix(config): block destructive config writes instead of only loggin...
by pierreeurope · 2026-02-17
79.7%
#19670: fix(config): guard config.apply against catastrophic key loss
by nabbilkhan · 2026-02-18
76.2%
#12792: fix: exclude 'tokens' (plural) fields from config redaction
by jpaine · 2026-02-09
76.0%
#12296: security: persistence-only secret redaction for session transcripts
by akoscz · 2026-02-09
75.6%
#16708: fix(security): OC-17 add token redaction to error formatting, depre...
by aether-ai-agent · 2026-02-15
74.4%
#21931: feat(config): auto-rollback to last known-good backup on invalid co...
by Protocol-zero-0 · 2026-02-20
74.3%
#21240: fix: GH#20607 prevent doctor from dropping custom config sections
by theognis1002 · 2026-02-19
74.3%
#22408: fix: doctor --fix now persists config when removing unrecognized keys
by astroclaw · 2026-02-21
73.8%