#19129: fix(config): block destructive config writes instead of only logging them (#19065)
size: S
Cluster:
Wizard Enhancements and Config Fixes
## Problem
Fixes #19065
The gateway's config write audit correctly detected destructive writes (>50% size drop, `gateway.mode` removed), but only logged them as warnings and allowed the write through. This has caused production outages when a truncated or corrupted config was silently persisted.
## Solution
After computing suspicious reasons, filter to the hard-blocking ones (`size-drop:*` and `gateway-mode-removed`). If any are present:
1. **Block the write** — the original `openclaw.json` is left untouched
2. **Save rejected payload** to `openclaw.json.rejected` for debugging
3. **Log a clear error** message identifying the reason(s)
4. **Append a `rejected` audit record** to `config-audit.jsonl` for forensics
5. **Throw** so the caller is informed
The informational `missing-meta-before-write` reason is intentionally excluded from the blocking set — it is diagnostic, not destructive, and fires on all pre-meta configs.
## Changes
- `src/config/io.ts`: Add `"rejected"` to `ConfigWriteAuditResult`; add blocking guard after suspicious-reason computation
- `src/config/io.write-config.test.ts`: Two new test cases — size-drop block and gateway-mode-removed block
## Testing
All 363 unit tests in `src/config/` pass:
```
Test Files 47 passed (47)
Tests 363 passed (363)
```
New tests cover:
- Write blocked + original file untouched + `.rejected` file created + error logged (size-drop)
- Write blocked + original file untouched + audit record has `result: "rejected"` (gateway-mode-removed)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR upgrades the config write audit system from warning-only to hard-blocking for destructive writes. Previously, the system detected dangerous config writes (>50% size drops and `gateway.mode` removal) but only logged warnings, leading to production outages when corrupted configs were persisted. Now, when blocking reasons are detected, the write is rejected, the original file remains untouched, and the rejected payload is saved to `.rejected` for debugging with a full audit trail.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with no identified issues
- The implementation is well-designed with proper error handling, the blocking logic is conservative and only targets truly destructive scenarios, comprehensive test coverage validates all the blocking behaviors, and the change transforms a critical production issue into a preventive safeguard
- No files require special attention
<sub>Last reviewed commit: be4f245</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21931: feat(config): auto-rollback to last known-good backup on invalid co...
by Protocol-zero-0 · 2026-02-20
79.9%
#21901: fix: guard writeConfigFile against persisting redaction sentinels
by Protocol-zero-0 · 2026-02-20
79.7%
#19670: fix(config): guard config.apply against catastrophic key loss
by nabbilkhan · 2026-02-18
78.1%
#11208: fix(config): prevent __OPENCLAW_REDACTED__ corruption on config writes
by janckerchen · 2026-02-07
77.8%
#17463: fix: write config files with explicit 0o600 mode instead of post-wr...
by miclaldogan · 2026-02-15
75.8%
#15757: feat(security): add hardening gap audit checks
by saurabhsh5 · 2026-02-13
75.7%
#11602: fix(config): skip stale legacy config files when openclaw.json exists
by akoscz · 2026-02-08
75.4%
#6770: fix(gateway): protect host-local transport fields from config.patch
by ryx2 · 2026-02-02
75.3%
#22227: fix(security): harden gateway auth — audit logging, pairing, mode v...
by novalis133 · 2026-02-20
75.0%
#14179: fix(config): resolve $include directives before validation in setup...
by ken2190 · 2026-02-11
74.7%