← Back to PRs

#12296: security: persistence-only secret redaction for session transcripts

by akoscz open 2026-02-09 03:08 View on GitHub →
docs gateway agents size: L
## Summary 🤖 **AI-assisted** — Built by an OpenClaw agent (Claude Opus 4.6), fully tested, fully reviewed. The author understands the code and architecture decisions. Redact sensitive secrets (API keys, tokens, passwords) from session transcript JSONL files **at the persistence layer only**, keeping in-memory entries unredacted so the LLM can reason about full tool output (e.g., `curl` with Bearer tokens, `cat` of config files). See [design document on #12182](https://github.com/openclaw/openclaw/issues/12182#issuecomment-3869025731) for full architecture rationale, tradeoffs, and coverage analysis. ## Root Cause In `session-tool-result-guard.ts`, tool results are persisted via `_appendEntry()` → `_persist()` which calls `JSON.stringify(entry)` + `appendFileSync` — no redaction applied. The existing `redactSensitiveText()` was only used on the read path. Additionally, `appendAssistantMessageToSessionTranscript()` in `transcript.ts` creates a raw `SessionManager.open()` without the guard, bypassing any redaction. ## Fix ### Architecture: Persistence-only via `_persist` monkey-patch - **`SessionManager._persist()`** — Replaced entirely to apply `redactEntryForPersistence()` during `JSON.stringify` serialization (both bulk-flush and single-entry paths) - **`SessionManager._rewriteFile()`** — Also replaced for the migration/recovery write path - **`transcript.ts`** — Wrapped with `guardSessionManager()` to close the bypass - **In-memory `fileEntries[]`** — Untouched. LLM sees full unredacted content via `buildSessionContext()` ### Entry types covered (all text-carrying types) | Entry Type | Field(s) Redacted | |---|---| | `message` (toolResult, assistant, user) | `message.content[].text` | | `compaction` / `branch_summary` | `summary` string | | `custom_message` | `content` (string or TextContent[]) | ### Why monkey-patching `SessionManager` lives in the upstream `@mariozechner/pi-coding-agent` package — we cannot modify `_persist` directly. The existing guard already monkey-patches `appendMessage`; our `_persist` patch follows the same established pattern. We pin to upstream v0.52.8 with SHA-256 hash tests that fail on any structural change. ### Other improvements - Cache compiled regex patterns (content-based `JSON.stringify` comparison) - Resolve redact options once at guard install time (no per-call `loadConfig()`) - Extract shared `redactTextBlocks()` helper (zero code duplication) ## Test plan **38 tests total**, all passing (Node + Bun): - **Dual-path verification**: every redaction test checks both in-memory (unredacted) and on-disk (redacted) using disk-persisted `SessionManager` - **All message roles**: toolResult, assistant, user - **All entry types**: compaction, branch_summary, custom_message (string + array) - **Non-message passthrough**: session header unchanged - **Transcript mirror**: delivery-mirror redaction verified - **Upstream compatibility**: SHA-256 hash of `_persist.toString()` and `_rewriteFile.toString()` - **Clean passthrough**: entries without secrets return same reference ## Related - Refs: #12182 - Related: #12260 (similar goal, different architecture — redacts before `appendMessage` which also affects LLM context) ## Files changed (5) - `src/agents/session-tool-result-guard.ts` — `redactEntryForPersistence()`, `_persist`/`_rewriteFile` patches, `redactTextBlocks()` helper - `src/agents/session-tool-result-guard.test.ts` — 26 guard tests with dual-path verification - `src/config/sessions/transcript.ts` — `guardSessionManager()` wrapper - `src/config/sessions/transcript.test.ts` — delivery-mirror redaction test - `src/logging/redact.ts` — pattern caching <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR changes session transcript persistence so secrets are redacted only when writing JSONL to disk, keeping in-memory session entries unredacted for LLM context. It does this by extending the existing `guardSessionManager()` approach to wrap `SessionManager`’s persistence methods (`_persist` and `_rewriteFile`) to serialize redacted copies of entries, and by ensuring `transcript.ts` uses a guarded session manager so it can’t bypass redaction. It also updates the redaction utility to cache compiled regex/pattern work and adds tests that verify the “dual path” behavior (unredacted in-memory, redacted on disk) across message roles and other transcript entry types. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk. - Reviewed the changed persistence wrapper and transcript guard integration along with the updated redaction caching and tests; changes are focused, preserve upstream persistence semantics, and include strong dual-path test coverage to prevent regressions. - No files require special attention <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs