Skip to content

fix: Fix Redaction Prompt Corruption - #178

Merged
droid-ash merged 3 commits into
mainfrom
260731-gz13-fix-redaction-prompt-corruption
Aug 1, 2026
Merged

fix: Fix Redaction Prompt Corruption#178
droid-ash merged 3 commits into
mainfrom
260731-gz13-fix-redaction-prompt-corruption

Conversation

@droid-ash

@droid-ash droid-ash commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Meta

Change ID Type Confidence Plan Review
gz13 fix 4.2/5.0 11/11 tasks, 18/18 acceptance ✓ ✓ 2 cycles
Impact +/− Net
true +803 / −20 +783
└ impl +691 / −20 +671
└ tests +112 / −0 +112

generated by fab-kit v2.16.8

Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr

Summary

redactResolvedValue (packages/common/src/repoPlaceholders.ts) rewrote secret values via unanchored substring matching, which PR #175 newly wired into the LLM prompt path. A secret value equal to its own key name nested into an invalid ${secrets.${secrets.KEY}} token, and a short/common secret value could mangle arbitrary prose in the prompt — both verified reproductions from CodeRabbit's review of PR #175 and independent operator testing against the built dist. This fixes the shared function so existing placeholder tokens survive redaction and values under 3 characters are never redacted, since substituting them corrupts text without protecting anything.

Changes

  • redactResolvedValue — protect placeholder tokens from rewriting (fixes token-nesting corruption)
  • redactResolvedValue — minimum redactable value length of 3 characters (fixes prose-destruction corruption from short values)
  • No word-boundary anchoring for surviving values (deliberate non-change — would reopen the leak the redaction exists to close)
  • Regression tests (packages/common/src/test/repoPlaceholders.test.ts, packages/goal-executor/src/ai/test/AIAgent.test.ts)
  • Comment updates falsified by the fix (repoPlaceholders.ts, AIAgent.ts, testCompiler.ts)
  • Two doc corrections on change 260731-gzzl-fix-secret-prompt-leak-comment's artifacts (CodeRabbit findings, verified at intake)

Summary by CodeRabbit

  • Bug Fixes

    • Improved secret redaction to prevent prompt corruption when secrets overlap with placeholder tokens.
    • Preserved authored ${secrets.KEY} placeholders during redaction.
    • Added consistent handling for embedded and overlapping secret values.
    • Clarified that secrets shorter than three characters remain unredacted.
  • Documentation

    • Expanded guidance on secret exposure through prompts, screenshots, reports, spans, and errors.
    • Documented redaction limits, matching behavior, and residual exposure scenarios.
  • Tests

    • Added regression coverage for placeholder preservation, short secrets, embedded values, and overlapping secrets.

redactResolvedValue used unanchored substring matching, so a secret
value equal to (or a substring of) its own placeholder token could
nest into invalid tokens, and short secret values could mangle
arbitrary prose once PR #175 wired redaction into the LLM prompt
path. Protect existing placeholder tokens as a trailing alternation
branch and skip values under 3 characters, with regression tests and
corrected comments/docs.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change updates shared secret redaction to preserve placeholder tokens and exclude secrets shorter than three characters. It adds regression tests and documents prompt, report, span, screenshot, and provider exposure boundaries.

Changes

Secret redaction contract

Layer / File(s) Summary
Redaction contract and implementation plan
fab/changes/260731-gz13-fix-redaction-prompt-corruption/*, fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/*
The change records placeholder preservation, minimum-length filtering, matching order, prompt serialization behavior, acceptance criteria, and verification requirements.
Shared redaction implementation
packages/common/src/repoPlaceholders.ts, packages/common/src/test/repoPlaceholders.test.ts
redactResolvedValue preserves authored placeholder tokens, ignores secret values shorter than three characters, and retains longest-first unanchored matching. Tests cover placeholders, short values, embedded values, and overlaps.
Prompt integration and validation
packages/goal-executor/src/ai/AIAgent.ts, packages/goal-executor/src/ai/test/AIAgent.test.ts, packages/cli/src/testCompiler.ts, docs/memory/cli/test-compiler.md, docs/memory/cli/index.md
Prompt comments and tests document and verify placeholder preservation, short-secret pass-through, reformatted-value limitations, and runtime exposure through screenshots and rendered values.
Documentation and workflow records
docs/memory/common/*, docs/memory/index.md, docs/memory/cli/report-writer.md, fab/changes/260731-gz13-fix-redaction-prompt-corruption/.history.jsonl, fab/changes/260731-gz13-fix-redaction-prompt-corruption/.status.yaml
Memory documentation and workflow records describe placeholder resolution, redaction boundaries, residual exposure cases, and completed change stages.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: documentation

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: fixing prompt corruption caused by secret redaction.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 260731-gz13-fix-redaction-prompt-corruption

Comment @coderabbitai help to get the list of available commands.

@droid-ash
droid-ash marked this pull request as ready for review August 1, 2026 02:33
@droid-ash
droid-ash merged commit 2edb4f5 into main Aug 1, 2026
1 of 2 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/memory/cli/report-writer.md`:
- Around line 20-26: Update the guarantee in the opening paragraph to say that
every textual value reaching a file crosses redactResolvedValue, rather than
claiming every value is redacted. Preserve the existing explanation that binary
screenshot bytes are outside this textual redaction path and do not broaden the
guarantee beyond textual file contents.
- Line 24: Update the authored `${secrets.KEY}` token statement in the
report-writer documentation to qualify verbatim pass-through: it applies only
when no eligible secret-value branch matches at the token’s start. Preserve the
existing explanation that the authored-token alternation branch consumes and
emits the token unchanged in cases where that value-branch condition is not met.

In `@docs/memory/common/repo-placeholders.md`:
- Around line 124-126: Update the short-secret documentation in
repo-placeholders.md to remove the claim that 1–2 character values are
inference-proof, and state only that leaving them raw is an accepted residual
risk to avoid corrupting arbitrary text.
- Around line 146-150: Update the rationale in the paragraph beginning “Why” to
replace the claim that reports are only read by humans with wording that covers
both machine consumers, including the report viewer and runIndex, and human
readers. Preserve the existing explanation about shared matching semantics and
security-relevant implementations.

In `@packages/common/src/repoPlaceholders.ts`:
- Around line 85-90: Correct the token-branch explanations: in
packages/common/src/repoPlaceholders.ts:85-90, state that no secret-value
alternative wins at the token’s start position, that the full token is not a
placeholderBySecretValue key, and qualify the memory claim by this precedence
rule. Apply the corresponding accepted-edge rationale corrections in
fab/changes/260731-gz13-fix-redaction-prompt-corruption/intake.md:60 and
plan.md:15, update the R6 requirement in plan.md:60, and state in
docs/memory/cli/test-compiler.md:63-68 that token protection applies when a
value alternative does not match at the token start.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a8c25f43-5f55-4d64-a5cc-00766b44e472

📥 Commits

Reviewing files that changed from the base of the PR and between bc6e634 and 872d219.

📒 Files selected for processing (17)
  • docs/memory/cli/index.md
  • docs/memory/cli/report-writer.md
  • docs/memory/cli/test-compiler.md
  • docs/memory/common/index.md
  • docs/memory/common/repo-placeholders.md
  • docs/memory/index.md
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/.history.jsonl
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/.status.yaml
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/intake.md
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/plan.md
  • fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/intake.md
  • fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/plan.md
  • packages/cli/src/testCompiler.ts
  • packages/common/src/repoPlaceholders.ts
  • packages/common/src/test/repoPlaceholders.test.ts
  • packages/goal-executor/src/ai/AIAgent.ts
  • packages/goal-executor/src/ai/test/AIAgent.test.ts

Comment on lines +20 to +26
Every value that reaches a file crosses `redactResolvedValue(value, bindings)` (exported from `@finalrun/common`, defined in its `repoPlaceholders.ts` — [/common/repo-placeholders.md](/common/repo-placeholders.md)), which rewrites a resolved secret value back to its `${secrets.<key>}` placeholder: runner-log lines, a test record's `message` and `analysis`, step JSON (action text and url, analysis, thought, trace and timing detail strings), the synthetic failure message, and the copied device log.

The environment snapshots carry reference forms only — `env.snapshot.yaml` mirrors the *config* `secrets` map (the `${ENV_VAR}` placeholder form) and `env.json` carries `secretReferences` (key plus env-var name). No resolved secret value is written anywhere beneath the run directory, and `reportWriter.test.ts` holds that as a whole-`runDir` sweep over every emitted file after a full lifecycle rather than as per-site assertions.
The environment snapshots carry reference forms only — `env.snapshot.yaml` mirrors the *config* `secrets` map (the `${ENV_VAR}` placeholder form) and `env.json` carries `secretReferences` (key plus env-var name). No **redactable** secret value is written anywhere beneath the run directory, and `reportWriter.test.ts` holds that as a whole-`runDir` sweep over every emitted file after a full lifecycle rather than as per-site assertions.

The contract is bounded in two ways worth naming, because the sweep's breadth invites reading it as global. It covers **write paths only** — the LLM prompt path is a separate seam, redacted per input inside `AIAgent` when runtime bindings are wired, and stdout INFO output crosses neither ([/cli/test-compiler.md](/cli/test-compiler.md)). And the sweep is a **byte-level** check on emitted files, so `tests/<testId>/screenshots/NNN.jpg` passes it while still showing a typed secret in its pixels: the report carries secret-bearing images even though it carries no secret strings.
"Redactable" is the operative word, and the sweep cannot show more than it: its fixture secret is a 17-character value, so what the sweep proves is bounded by the shared function's own bounds. A secret value shorter than `MIN_REDACTABLE_SECRET_LENGTH` (3) is never redacted, so a 1–2 character secret value **does** reach files beneath the run directory raw — an accepted residual, since substituting such a value would rewrite every incidental occurrence of that character sequence in a log line or step record instead. Conversely, an authored `${secrets.KEY}` token already present in report text — test steps like `Enter ${secrets.email}` carry them routinely — is consumed whole by the redaction alternation's last branch and emitted verbatim, never rewritten into a nested token.

The contract is bounded in two further ways worth naming, because the sweep's breadth invites reading it as global. It covers **write paths only** — the LLM prompt path is a separate seam, redacted per input inside `AIAgent` when runtime bindings are wired, and stdout INFO output crosses neither ([/cli/test-compiler.md](/cli/test-compiler.md)). And the sweep is a **byte-level** check on emitted files, so `tests/<testId>/screenshots/NNN.jpg` passes it while still showing a typed secret in its pixels: the report carries secret-bearing images even though it carries no secret strings.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Limit the guarantee to textual values.

Line 20 says every value that reaches a file crosses redactResolvedValue, but Line 26 says binary screenshot bytes are outside this path. Use “textual value” for the guarantee so readers do not interpret the whole run directory as redacted.

Proposed wording
-Every value that reaches a file crosses `redactResolvedValue(value, bindings)` ...
+Every textual value that reaches a file crosses `redactResolvedValue(value, bindings)` ...

-The sweep is a byte-level check on emitted files ...
+This is a string-redaction guarantee. The sweep is a byte-level check on emitted files ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Every value that reaches a file crosses `redactResolvedValue(value, bindings)` (exported from `@finalrun/common`, defined in its `repoPlaceholders.ts`[/common/repo-placeholders.md](/common/repo-placeholders.md)), which rewrites a resolved secret value back to its `${secrets.<key>}` placeholder: runner-log lines, a test record's `message` and `analysis`, step JSON (action text and url, analysis, thought, trace and timing detail strings), the synthetic failure message, and the copied device log.
The environment snapshots carry reference forms only — `env.snapshot.yaml` mirrors the *config* `secrets` map (the `${ENV_VAR}` placeholder form) and `env.json` carries `secretReferences` (key plus env-var name). No resolved secret value is written anywhere beneath the run directory, and `reportWriter.test.ts` holds that as a whole-`runDir` sweep over every emitted file after a full lifecycle rather than as per-site assertions.
The environment snapshots carry reference forms only — `env.snapshot.yaml` mirrors the *config* `secrets` map (the `${ENV_VAR}` placeholder form) and `env.json` carries `secretReferences` (key plus env-var name). No **redactable** secret value is written anywhere beneath the run directory, and `reportWriter.test.ts` holds that as a whole-`runDir` sweep over every emitted file after a full lifecycle rather than as per-site assertions.
The contract is bounded in two ways worth naming, because the sweep's breadth invites reading it as global. It covers **write paths only** — the LLM prompt path is a separate seam, redacted per input inside `AIAgent` when runtime bindings are wired, and stdout INFO output crosses neither ([/cli/test-compiler.md](/cli/test-compiler.md)). And the sweep is a **byte-level** check on emitted files, so `tests/<testId>/screenshots/NNN.jpg` passes it while still showing a typed secret in its pixels: the report carries secret-bearing images even though it carries no secret strings.
"Redactable" is the operative word, and the sweep cannot show more than it: its fixture secret is a 17-character value, so what the sweep proves is bounded by the shared function's own bounds. A secret value shorter than `MIN_REDACTABLE_SECRET_LENGTH` (3) is never redacted, so a 1–2 character secret value **does** reach files beneath the run directory raw — an accepted residual, since substituting such a value would rewrite every incidental occurrence of that character sequence in a log line or step record instead. Conversely, an authored `${secrets.KEY}` token already present in report text — test steps like `Enter ${secrets.email}` carry them routinely — is consumed whole by the redaction alternation's last branch and emitted verbatim, never rewritten into a nested token.
The contract is bounded in two further ways worth naming, because the sweep's breadth invites reading it as global. It covers **write paths only** — the LLM prompt path is a separate seam, redacted per input inside `AIAgent` when runtime bindings are wired, and stdout INFO output crosses neither ([/cli/test-compiler.md](/cli/test-compiler.md)). And the sweep is a **byte-level** check on emitted files, so `tests/<testId>/screenshots/NNN.jpg` passes it while still showing a typed secret in its pixels: the report carries secret-bearing images even though it carries no secret strings.
Every textual value that reaches a file crosses `redactResolvedValue(value, bindings)` (exported from `@finalrun/common`, defined in its `repoPlaceholders.ts`[/common/repo-placeholders.md](/common/repo-placeholders.md)), which rewrites a resolved secret value back to its `${secrets.<key>}` placeholder: runner-log lines, a test record's `message` and `analysis`, step JSON (action text and url, analysis, thought, trace and timing detail strings), the synthetic failure message, and the copied device log.
The environment snapshots carry reference forms only — `env.snapshot.yaml` mirrors the *config* `secrets` map (the `${ENV_VAR}` placeholder form) and `env.json` carries `secretReferences` (key plus env-var name). No **redactable** secret value is written anywhere beneath the run directory, and `reportWriter.test.ts` holds that as a whole-`runDir` sweep over every emitted file after a full lifecycle rather than as per-site assertions.
"Redactable" is the operative word, and the sweep cannot show more than it: its fixture secret is a 17-character value, so what the sweep proves is bounded by the shared function's own bounds. A secret value shorter than `MIN_REDACTABLE_SECRET_LENGTH` (3) is never redacted, so a 1–2 character secret value **does** reach files beneath the run directory raw — an accepted residual, since substituting such a value would rewrite every incidental occurrence of that character sequence in a log line or step record instead. Conversely, an authored `${secrets.KEY}` token already present in report text — test steps like `Enter ${secrets.email}` carry them routinely — is consumed whole by the redaction alternation's last branch and emitted verbatim, never rewritten into a nested token.
The contract is bounded in two further ways worth naming, because the sweep's breadth invites reading it as global. It covers **write paths only** — the LLM prompt path is a separate seam, redacted per input inside `AIAgent` when runtime bindings are wired, and stdout INFO output crosses neither ([/cli/test-compiler.md](/cli/test-compiler.md)). This is a string-redaction guarantee. The sweep is a **byte-level** check on emitted files, so `tests/<testId>/screenshots/NNN.jpg` passes it while still showing a typed secret in its pixels: the report carries secret-bearing images even though it carries no secret strings.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/memory/cli/report-writer.md` around lines 20 - 26, Update the guarantee
in the opening paragraph to say that every textual value reaching a file crosses
redactResolvedValue, rather than claiming every value is redacted. Preserve the
existing explanation that binary screenshot bytes are outside this textual
redaction path and do not broaden the guarantee beyond textual file contents.

The environment snapshots carry reference forms only — `env.snapshot.yaml` mirrors the *config* `secrets` map (the `${ENV_VAR}` placeholder form) and `env.json` carries `secretReferences` (key plus env-var name). No **redactable** secret value is written anywhere beneath the run directory, and `reportWriter.test.ts` holds that as a whole-`runDir` sweep over every emitted file after a full lifecycle rather than as per-site assertions.

The contract is bounded in two ways worth naming, because the sweep's breadth invites reading it as global. It covers **write paths only** — the LLM prompt path is a separate seam, redacted per input inside `AIAgent` when runtime bindings are wired, and stdout INFO output crosses neither ([/cli/test-compiler.md](/cli/test-compiler.md)). And the sweep is a **byte-level** check on emitted files, so `tests/<testId>/screenshots/NNN.jpg` passes it while still showing a typed secret in its pixels: the report carries secret-bearing images even though it carries no secret strings.
"Redactable" is the operative word, and the sweep cannot show more than it: its fixture secret is a 17-character value, so what the sweep proves is bounded by the shared function's own bounds. A secret value shorter than `MIN_REDACTABLE_SECRET_LENGTH` (3) is never redacted, so a 1–2 character secret value **does** reach files beneath the run directory raw — an accepted residual, since substituting such a value would rewrite every incidental occurrence of that character sequence in a log line or step record instead. Conversely, an authored `${secrets.KEY}` token already present in report text — test steps like `Enter ${secrets.email}` carry them routinely — is consumed whole by the redaction alternation's last branch and emitted verbatim, never rewritten into a nested token.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Qualify the authored-token pass-through claim.

Line 24 promises verbatim output for every authored token. docs/memory/common/repo-placeholders.md Lines 88-92 document an exact-token secret-value case where the value branch rewrites the token to another placeholder. Restrict “emitted verbatim” to cases where no eligible value branch matches at the token start.

Proposed wording
-... is consumed whole by the redaction alternation's last branch and emitted verbatim, never rewritten into a nested token.
+... is passed through verbatim when no eligible value branch matches at the token start; an exact-token secret value may be rewritten placeholder-to-placeholder, but never into a nested token.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Redactable" is the operative word, and the sweep cannot show more than it: its fixture secret is a 17-character value, so what the sweep proves is bounded by the shared function's own bounds. A secret value shorter than `MIN_REDACTABLE_SECRET_LENGTH` (3) is never redacted, so a 1–2 character secret value **does** reach files beneath the run directory raw — an accepted residual, since substituting such a value would rewrite every incidental occurrence of that character sequence in a log line or step record instead. Conversely, an authored `${secrets.KEY}` token already present in report text — test steps like `Enter ${secrets.email}` carry them routinely — is consumed whole by the redaction alternation's last branch and emitted verbatim, never rewritten into a nested token.
"Redactable" is the operative word, and the sweep cannot show more than it: its fixture secret is a 17-character value, so what the sweep proves is bounded by the shared function's own bounds. A secret value shorter than `MIN_REDACTABLE_SECRET_LENGTH` (3) is never redacted, so a 1–2 character secret value **does** reach files beneath the run directory raw — an accepted residual, since substituting such a value would rewrite every incidental occurrence of that character sequence in a log line or step record instead. Conversely, an authored `${secrets.KEY}` token already present in report text — test steps like `Enter ${secrets.email}` carry them routinely — is passed through verbatim when no eligible value branch matches at the token start; an exact-token secret value may be rewritten placeholder-to-placeholder, but never into a nested token.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/memory/cli/report-writer.md` at line 24, Update the authored
`${secrets.KEY}` token statement in the report-writer documentation to qualify
verbatim pass-through: it applies only when no eligible secret-value branch
matches at the token’s start. Preserve the existing explanation that the
authored-token alternation branch consumes and emits the token unchanged in
cases where that value-branch condition is not met.

Comment on lines +124 to +126
- **Short values reach every sink raw.** A 1–2 character secret value is never redacted anywhere —
prompts, report artifacts, spans, error strings. That is a real (documented) exposure, traded
against corrupting arbitrary text; a value that short is also close to inference-proof by nature.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the inference-proof claim for short secrets.

A one- or two-character secret can still be a PIN, access code, or password fragment. The implementation leaves these values raw by design, so this wording gives unsafe assurance. State that the residual is accepted to avoid text corruption, not because the value is safe to expose.

Proposed wording
-a value that short is also close to inference-proof by nature.
+short values can still be sensitive; this trade-off does not make them safe to expose.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **Short values reach every sink raw.** A 1–2 character secret value is never redacted anywhere —
prompts, report artifacts, spans, error strings. That is a real (documented) exposure, traded
against corrupting arbitrary text; a value that short is also close to inference-proof by nature.
- **Short values reach every sink raw.** A 1–2 character secret value is never redacted anywhere —
prompts, report artifacts, spans, error strings. That is a real (documented) exposure, traded
against corrupting arbitrary text; short values can still be sensitive; this trade-off does not make them safe to expose.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/memory/common/repo-placeholders.md` around lines 124 - 126, Update the
short-secret documentation in repo-placeholders.md to remove the claim that 1–2
character values are inference-proof, and state only that leaving them raw is an
accepted residual risk to avoid corrupting arbitrary text.

Comment on lines +146 to +150
**Why**: The corruption these guards prevent is a property of the matching algorithm, not of any one
call site: every consumer feeds the function text that legitimately contains placeholder tokens, and
every consumer is equally exposed to a short or prose-colliding value. Forking the semantics would
leave two security-relevant implementations of the same rewrite to keep in sync, and the report path
would keep corrupting silently because reports are read by humans rather than parsed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Correct the report-consumer rationale.

Line 150 says reports are read by humans rather than parsed. docs/memory/cli/report-writer.md Line 67 states that the report viewer and runIndex consume the emitted files. Update the rationale to cover machine consumers and human readers.

Proposed wording
-report path would keep corrupting silently because reports are read by humans rather than parsed.
+report path would keep corrupting silently because report files are parsed by the report viewer and `runIndex` and then read by humans.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Why**: The corruption these guards prevent is a property of the matching algorithm, not of any one
call site: every consumer feeds the function text that legitimately contains placeholder tokens, and
every consumer is equally exposed to a short or prose-colliding value. Forking the semantics would
leave two security-relevant implementations of the same rewrite to keep in sync, and the report path
would keep corrupting silently because reports are read by humans rather than parsed.
**Why**: The corruption these guards prevent is a property of the matching algorithm, not of any one
call site: every consumer feeds the function text that legitimately contains placeholder tokens, and
every consumer is equally exposed to a short or prose-colliding value. Forking the semantics would
leave two security-relevant implementations of the same rewrite to keep in sync, and the report path
would keep corrupting silently because report files are parsed by the report viewer and `runIndex` and then read by humans.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/memory/common/repo-placeholders.md` around lines 146 - 150, Update the
rationale in the paragraph beginning “Why” to replace the claim that reports are
only read by humans with wording that covers both machine consumers, including
the report viewer and runIndex, and human readers. Preserve the existing
explanation about shared matching semantics and security-relevant
implementations.

Comment on lines +85 to +90
// no nested, invalid token. Accepted edges: a secret whose value is EXACTLY
// a well-formed token is matched by its own value alternative and rewritten
// placeholder-to-placeholder via the map — harmless (nothing raw on either
// side) and identical to pre-change behavior; consequently a token matched
// by the token branch matches no secret value, so the replacer's `?? match`
// passes it through verbatim. Value matching stays UNANCHORED on purpose:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the token-branch explanation.

A token selected at $ can still contain a configured secret value. For example, ${secrets.PASSWORD} contains PASSWORD. The token branch wins because it starts earlier, not because no secret value exists.

State that no secret-value alternative wins at the token start position. State that the full token is not a placeholderBySecretValue key. Qualify the memory claim with the same precedence rule.

  • packages/common/src/repoPlaceholders.ts#L85-L90: Replace the “matches no secret value” explanation with the token-start precedence and map-key explanation.
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/intake.md#L60-L60: Correct the accepted-edge claim to match the implementation.
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/plan.md#L15-L15: Correct the accepted-edge rationale.
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/plan.md#L60-L60: Correct the R6 comment requirement.
  • docs/memory/cli/test-compiler.md#L63-L68: State that token protection applies when a value alternative does not match at the token start.
📍 Affects 4 files
  • packages/common/src/repoPlaceholders.ts#L85-L90 (this comment)
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/intake.md#L60-L60
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/plan.md#L15-L15
  • fab/changes/260731-gz13-fix-redaction-prompt-corruption/plan.md#L60-L60
  • docs/memory/cli/test-compiler.md#L63-L68
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/common/src/repoPlaceholders.ts` around lines 85 - 90, Correct the
token-branch explanations: in packages/common/src/repoPlaceholders.ts:85-90,
state that no secret-value alternative wins at the token’s start position, that
the full token is not a placeholderBySecretValue key, and qualify the memory
claim by this precedence rule. Apply the corresponding accepted-edge rationale
corrections in
fab/changes/260731-gz13-fix-redaction-prompt-corruption/intake.md:60 and
plan.md:15, update the R6 requirement in plan.md:60, and state in
docs/memory/cli/test-compiler.md:63-68 that token protection applies when a
value alternative does not match at the token start.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant