fix(security): inline redact into appendSessionTranscriptMessage#73563
fix(security): inline redact into appendSessionTranscriptMessage#73563Ziy1-Tan wants to merge 6 commits into
Conversation
Greptile SummaryThis PR correctly wraps all 4 bare
Confidence Score: 4/5Safe to merge with awareness of the updateMode regression — currently only test-code callers use "file-only" or "none", so production impact is limited, but the contract is broken. One confirmed P1: updateMode: "file-only" now double-emits (guard fires full-payload event + switch fires bare-path event) and updateMode: "none" silencing is lost. Both modes are only used in tests currently, so there is no confirmed production breakage, but the API contract change warrants follow-up before the modes are relied upon in production paths. src/config/sessions/transcript.ts — the updateMode switch around lines 289-295 needs suppression logic for "none" and should not add a second emit for "file-only". Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/config/sessions/transcript.ts
Line: 286-295
Comment:
**`updateMode: "none"` no longer suppresses the event, and `"file-only"` now double-emits**
The comment on line 287 correctly states that `"file-only"` and `"none"` require explicit handling, but the switch only adds an *extra* emit for `"file-only"` and does nothing for `"none"` — neither case suppresses the guard's automatic emit.
Concrete effects:
- **`"none"`**: `guardedAppend` inside `installSessionToolResultGuard` unconditionally calls `emitSessionTranscriptUpdate({ sessionFile, sessionKey, message, messageId })`. The `default: break` branch never fires, so `updateMode: "none"` no longer silences the event.
- **`"file-only"`**: the guard fires a full-payload event (`message` + `messageId` included), then the switch fires a second bare `emitSessionTranscriptUpdate(sessionFile)`. Listeners receive the message twice: once with full details, once with only the path. The original semantics of "file-only" (no message details exposed to listeners) are violated.
The guard does not expose a mechanism to suppress its own emit, so the fix needs to be applied at the `guardSessionManager` call site or by threading an `onAfterAppend` callback through `guardSessionManager`'s options.
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "test: remove stale eslint-disable from t..." | Re-trigger Greptile |
|
Codex review: found issues before merge. Summary Reproducibility: yes. A direct call to Real behavior proof Next step before merge Security Review findings
Review detailsBest possible solution: Land the append-layer redaction after resolving the branch conflicts, adding the active changelog entry, and rerunning the normal focused and changed-surface gates. Do we have a high-confidence way to reproduce the issue? Yes. A direct call to Is this the best way to solve the issue? Yes, with process blockers. Centralizing redaction at the locked append helper is the narrow durable fix for direct transcript-message writes, but the PR should add the changelog entry and resolve dirty mergeability before merge. Full review comments:
Overall correctness: patch is correct What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 3e53b192842e. |
18da68e to
d86f58c
Compare
|
cc @hxy91819 . |
6c6108f to
fc4cd78
Compare
fc4cd78 to
a264208
Compare
a264208 to
8a61c8d
Compare
8a61c8d to
3e2c175
Compare
AgentMessage is a union that includes BashExecutionMessage (from @mariozechner/pi-coding-agent module augmentation), which has no content field. Direct .content access on AgentMessage fails tsgo's strict union check. Changes: - Add msgContent() helper that casts through unknown to access content - Replace all result.content casts with msgContent(result) calls - Change as AgentMessage casts to as unknown as AgentMessage for object literals that don't fully satisfy AssistantMessage shape
…ranscript-inject.ts transcript.ts and chat-transcript-inject.ts passed SessionWriteLockAcquireTimeoutConfig (which has no logging field) to appendSessionTranscriptMessage, silently dropping logging.redactSensitive='off' and custom redactPatterns on those write paths. - Widen config param from SessionWriteLockAcquireTimeoutConfig to OpenClawConfig in appendExactAssistantMessageToSessionTranscript (transcript.ts) and injectAssistantMessageToSessionTranscript (chat-transcript-inject.ts) - Add integration test: appendExactAssistantMessageToSessionTranscript with redactSensitive='off' asserts the secret is NOT redacted on disk - Regenerate plugin-sdk-api-baseline.sha256 (appendSessionTranscriptMessage is re-exported via agent-harness-runtime; config type change drifts the hash)
f297619 to
4aff949
Compare
There was a problem hiding this comment.
- 4aff949 addresses this directly —
appendInjectedAssistantMessageToTranscriptno longer goes throughguardSessionManager. It now callsappendSessionTranscriptMessagedirectly and passesconfig: params.config, soredactSensitive="off"is honored on this path too. - The
guardSessionManagercall you flagged is gone from this file entirely. pnpm plugin-sdk:api:checkpasses on this branch — baseline was regenerated indocs/.generated/plugin-sdk-api-baseline.sha256.
PTAL @hxy91819
|
@hxy91819 and I both think this is good to go. Going to get this closed out with clawsweeper. |
|
/clawsweeper automerge |
|
🦞🔧
Draft PRs stay fix-only until GitHub marks them ready for review. Pause with Automerge progress:
|
|
ClawSweeper 🐠 reef update Thanks for the contribution. The source branch was not safely writable by ClawSweeper, so it opened a replacement PR and kept the credit trail visible. Replacement PR: #79645 fish notes: model gpt-5.5, reasoning high; reviewed against c9600f1. |
What
All callsites of
appendSessionTranscriptMessagenow redact sensitive content before writing to disk, closing the path-B leak identified in #64046. User-configured redact settings (logging.redactSensitive,redactPatterns) are now correctly threaded through all write paths.Previously, only messages routed through
guardSessionManager(thepi-embedded-runnerpath) were redacted. Direct callers —transcript.ts,attempt-execution.ts,chat-transcript-inject.ts,transcript-mirror.ts— wrote plaintext to the JSONL transcript. Additionally,transcript.tsandchat-transcript-inject.tstyped theirconfigparam asSessionWriteLockAcquireTimeoutConfig, which has nologgingfield, silently discarding user redact preferences on those paths.Why
Sensitive data (API keys, tokens, PII matching configured patterns) could persist unredacted in session transcript files on disk. This fix makes redaction always-on at the write layer and correctly propagates user config through all write paths.
Changes
src/agents/transcript-redact.ts(new) — extract the 4 redact helpers fromsession-tool-result-guard-wrapper.tsinto a shared module; zero logic change, verbatim movesrc/agents/session-tool-result-guard-wrapper.ts— delete local copies, import fromtranscript-redact.tssrc/config/sessions/transcript-append.ts— widenconfig?:toOpenClawConfig; callredactTranscriptMessagebefore building the JSONL entrysrc/config/sessions/transcript.ts— widenconfigparam inappendExactAssistantMessageToSessionTranscriptfromSessionWriteLockAcquireTimeoutConfigtoOpenClawConfigso user redact settings thread through correctlysrc/gateway/server-methods/chat-transcript-inject.ts— widenconfigparam ininjectAssistantMessageToSessionTranscripttoOpenClawConfigfor the same reasonsrc/agents/transcript-redact.test.ts(new) — 9 unit tests for the shared redact modulesrc/config/sessions/transcript-append-redact.test.ts(new) — 4 integration tests asserting on-disk JSONL content (includes test forappendExactAssistantMessageToSessionTranscriptpath)docs/.generated/plugin-sdk-api-baseline.sha256— regenerated afterappendSessionTranscriptMessageconfig type change (re-exported viaagent-harness-runtime.ts)Key property: always-on. When
configisundefined,redactTranscriptMessagefalls back toDEFAULT_REDACT_MODE+DEFAULT_REDACT_PATTERNS— not a noop. When the user setslogging.redactSensitive='off', that preference is now honored on all write paths.Real behavior proof (required for external PRs)
appendSessionTranscriptMessagethat did not go throughguardSessionManager.OPENCLAW_STATE_DIR=/tmp/proof-openclaw.OPENCLAW_STATE_DIR=/tmp/proof-openclaw node scripts/run-node.mjs agent --local --to +10000000001 --message "my api key is sk-abcdef1234567890xyz, please just reply: noted" --model claude-cli/claude-sonnet-4-6, then read back the session JSONL from disk.sk-abc…0xyzinstead ofsk-abcdef1234567890xyz. Screenshot:appendSessionTranscriptMessagewrite path — no channel or agent behavior was changed.Testing
pnpm check✅ (exit 0 — typecheck, lint, policy guards all pass)pnpm plugin-sdk:api:check✅ (baseline hash matches after regeneration)node scripts/check-src-extension-import-boundary.mjs --json→[]✅node scripts/run-vitest.mjs run src/agents/transcript-redact.test.ts→ 9/9 ✅node scripts/run-vitest.mjs run src/config/sessions/transcript-append-redact.test.ts→ 4/4 ✅fully testedredactTranscriptMessage— text block with sk- tokenredactTranscriptMessage—cfg=undefinedredactTranscriptMessage—redactSensitive:"off"appendSessionTranscriptMessage— secret in contentappendExactAssistantMessageToSessionTranscript—redactSensitive:'off'AI Disclosure
fully testedappendSessionTranscriptMessage, and widens config types on two additional write paths so user redact preferences are not silently discardedce:reviewrun locally (correctness, maintainability, security, testing reviewers); all critical/high findings addressedCloses #73565