fix: ledger redaction — preserve write paths and close a curl -u credential leak introduced by #1117
Issue for this PR
Closes #1245
Type of change
- Bug fix
What does this PR do?
Security-relevant / expedited — please review promptly. origin/main's TypeScript CI job has been red since ~2026-09-04 07:28Z, blocking all PRs. This fixes both the broken-main failure and a live credential-leak regression it's coupled to.
Root cause: #1117 (14ba9bb9e7) added a filesystem-path masking rule to Telemetry.maskString (packages/opencode/src/altimate/telemetry/index.ts). SessionCompaction.redactLedgerDetail (packages/opencode/src/session/compaction.ts) calls Telemetry.maskString(value) before running its own -u/--user curl-credential redaction. That ordering has two consequences:
- Over-masking (4 of the 5 failing tests): every write path (2+ path segments) collapses to the literal
<path>, so the session ledger — whose whole purpose is to tell the model which files it already wrote — can no longer report file paths at all. - Credential leak (the 5th test, and the important one): when curl is invoked path-qualified (
/usr/bin/curl -u alice password ...),maskString's path rule replaces thecurl/curl.exetoken with<path>beforeredactLedgerDetail's curl-context lookback ever runs. With thecurltoken gone, the lookback can't find it,curlContextcomes back false, and the space-separated (non-colon-shaped)-u alice passwordidiom isn't otherwise recognized as credential-shaped — so it passes throughredactLedgerDetailcompletely unredacted. This ledger text is persisted into a later model prompt across compaction (see the comment atcompaction.ts:~657): a real credential leaking across a session/provider boundary.
The leak is main-only — not present in v0.10.0 or any released version.
Fix:
Telemetry.maskStringgains an opt-out for its path-masking pass:maskString(value, opts?: { maskPaths?: boolean }), defaultmaskPaths: true— byte-for-byte unchanged for every existing caller. WhenmaskPaths: false, only the #1117 path rule is skipped; every other mask (api keys, bearer tokens, emails, internal hosts, quote collapsing) still applies.redactLedgerDetailnow callsmaskString(value, { maskPaths: false }). This restores write-path fidelity (fixes the 4 over-mask tests), and — because thecurltoken survives — the curl-context lookback works again, closing the leak (fixes the 5th test).- Belt-and-suspenders:
redactLedgerDetail's curl-context detection is also derived independently from the pre-mask raw value (correlated ordinally against the masked-string matches, with a safe fallback to the prior masked-only check if the two ever disagree). This makes curl detection structurally immune to any future masking rule that happens to eat the command-name token — it no longer depends solely onmaskPaths: false. - No other
maskStringcall site was touched — they all keepmaskPaths: true(mcp/index.ts,sql-execute.ts,tool.ts,prompt.ts,register.ts,dispatcher.ts,registry.ts,warehouse-add.ts,project-scan.ts). #1117's telemetry path-masking protection is fully intact for its intended consumers — verified bytest/telemetry/mask-file-paths.test.tsstaying green.
Both telemetry/index.ts and compaction.ts are upstream-shared files; edits are wrapped in altimate_change markers. The marker guard (bun run script/upstream/analyze.ts --markers --base origin/main --strict) reports clean.
Related: this touches the same session-state-ledger security work referenced in compaction.ts (redactLedgerDetail's existing credential-redaction suite).
How did you verify your code works?
bun test packages/opencode/test/session/compaction-ledger.test.ts packages/opencode/test/session/compaction-ledger-history.test.ts— 54 pass, 0 fail (was 5 failing on main).- Added an explicit adversarial test reproducing the exact leak shape:
redactLedgerDetail("/usr/bin/curl -u alice hunter2 https://example.com")and a/usr/local/bin/curl.exevariant, asserting the output contains neitheralicenorhunter2. Confirmed it fails against pre-fix code viagit stashon the two source files (leaving the new test in place) — reproduced the leak verbatim (<path> -u alice hunter2 https://example.com/) — then confirmed it passes aftergit stash pop. bun test test/telemetry/mask-file-paths.test.ts test/altimate/telemetry-signals.test.ts test/mcp/mcp-bearer-auth.test.ts— 211 pass, 0 fail (confirms #1117's default-maskPathspath masking is untouched for its other callers).- Broader regression sweep:
bun testacross alltest/session/compaction-*.test.ts,observation-mask,task-pin,uncounted-tail— 241 pass, 0 fail, 0 regressions. bun run typecheck— clean across all 15 workspace packages.bun run script/upstream/analyze.ts --markers --base origin/main --strict— clean.- Pushed and confirmed the
TypeScriptCI job goes green on the pushed commit.
Not independently re-verified: the pre-existing flaky timeouts in test/session/prompt.test.ts (loop calls LLM and returns assistant message, loop surfaces content-filter finishes as session errors) — confirmed these fail identically against unmodified origin/main (network/mock-provider timeout issue, unrelated to this change) and are not part of this PR's scope.
Screenshots / recordings
N/A — no UI change.
Checklist
- I have tested my changes locally
- I have not included unrelated changes in this PR
This PR should NOT be merged by an automated agent — human review requested given the security sensitivity.
[!NOTE]
High Risk
Changes credential redaction on compaction ledger text that is persisted into later model prompts; the fix is targeted but security-critical.
Overview
Fixes a credential leak and ledger over-masking caused by running filesystem path masking on session-ledger text before compaction-specific redaction.
Telemetry.maskString now accepts optional { maskPaths?: boolean } (default true), so only the #1117 path pass can be skipped; API keys, bearer tokens, emails, and other rules are unchanged for existing callers. redactLedgerDetail calls maskString(value, { maskPaths: false }) so write paths stay visible in the state ledger and path-qualified commands like /usr/bin/curl are not collapsed to <path> before curl-context detection.
Hardening: -u/--user handling uses a shared USER_FLAG_RE, and curl context is inferred from both the masked string and the raw pre-mask value (ordinal-aligned), so a future mask rule that strips curl is less likely to reopen the leak.
Adds a regression test for path-qualified curl with space-separated -u user password.
Reviewed by Cursor Bugbot for commit 3463f0985af64e003a0aab18d5eb03ef961e26ee. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
-
Bug Fixes
- Improved protection for credentials included in path-qualified
curlcommands, including space-separated username and password formats. - Preserved command paths in session activity details while continuing to redact sensitive credentials and tokens.
- Added safeguards to ensure credential redaction remains reliable across different command representations.
- Improved protection for credentials included in path-qualified
-
Tests
- Added regression coverage for credential leakage in path-qualified
curlcommands.
- Added regression coverage for credential leakage in path-qualified
Summary by cubic
Fixes a credential leak and broken write-path reporting in the session ledger that #1117 introduced. Telemetry.maskString's new path-masking pass collapsed /usr/bin/curl to <path> before redactLedgerDetail's curl-context lookback ran, so path-qualified curl -u user password leaked unredacted into ledger text persisted across compaction; write paths also collapsed to <path>, breaking the ledger's file-reporting purpose. The leak is main-only, not in any release, and the change fixes the five tests that have been red on origin/main's TypeScript CI since ~2026-09-04 07:28Z.
Bug Fixes
Telemetry.maskStringgains amaskPathsopt-out (defaulttrue, byte-for-byte unchanged for all existing callers); only the path pass is skipped, all other masks still apply.redactLedgerDetailnow callsmaskString(value, { maskPaths: false }), restoring write-path fidelity and the curl-context lookback.- Curl-context detection is also derived from the raw pre-mask value (ordinally correlated with the masked matches), so a future mask rule that eats the command token can't reopen the leak.
- Adds regression tests for path-qualified
curlandcurl.exewith space-separated-u user password, confirmed failing against pre-fix code.