Skip to content

fix: Fix Secret-Leak Guarantee Comment and Assess the Prompt-Path Secret Leak - #175

Merged
droid-ash merged 3 commits into
mainfrom
260731-gzzl-fix-secret-prompt-leak-comment
Jul 31, 2026
Merged

fix: Fix Secret-Leak Guarantee Comment and Assess the Prompt-Path Secret Leak#175
droid-ash merged 3 commits into
mainfrom
260731-gzzl-fix-secret-prompt-leak-comment

Conversation

@droid-ash

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

Copy link
Copy Markdown
Owner

Meta

Change ID Type Confidence Plan Review
gzzl fix 4.5/5.0 6/6 tasks, 13/13 acceptance ✓ ✓ 2 cycles
Impact +/− Net
true +889 / −35 +854
└ impl +674 / −34 +640
└ tests +215 / −1 +214

generated by fab-kit v2.16.8

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

Summary

packages/cli/src/testCompiler.ts claimed secret values never enter the LLM prompt, provider logs, or compiled artifacts — true only for the compiled objective. The comment is rewritten to scope the guarantee correctly and to state plainly that resolved secret values DO reach the model provider via the post-typing accessibility hierarchy (grounder-leg text/hintText/error) and via screenshots. A partial mitigation is also implemented: AIAgent now redacts exact resolved-secret values to ${secrets.KEY} placeholders per-input, before assembly/serialization, gated on grounding proof (existing + 2 new regression tests).

Changes

  • Comment fix (required deliverable): rewrote the security comment in packages/cli/src/testCompiler.ts to scope the guarantee to placeholder substitution in the compiled objective, describe the runtime leak chain and the redaction wiring accurately, and warn against enabling full prompt logging.
  • Mitigation (implemented, gated on grounding proof): AIAgent (packages/goal-executor) accepts optional RuntimeBindings (wired from sessionRunner) and redacts exact resolved-secret values to ${secrets.KEY} placeholders per-input before assembly/serialization — every free-text input (objective, act, history, remember entries, preContext, appKnowledge) and every element/availableApps string field. Reuses redactResolvedValue (longest-first). Hierarchy structure (index/bounds/id/class) is never mutated; the no-bindings path is a strict no-op.
  • Rework (1 cycle): an initial whole-text redaction pass corrupted serialized element JSON when a numeric secret equaled a coordinate value, and missed JSON-escaped secrets inside remember entries. Replaced with per-input pre-serialization redaction; 2 regression tests added.
  • Honest residuals (documented in code comments + docs/memory/cli/test-compiler.md): screenshots still carry secrets to the provider and report artifacts and cannot be redacted cheaply without blinding the planner; app-rendered transformations (truncation/reformat/masking) can escape exact-match redaction; exact-match redaction can over-match structural strings (degrades grounding text, never JSON structure); formatPlannerReasoning still prints planner thought/action/reason raw to stdout — a known should-fix from review, deliberately not addressed in this change.
  • Memory hydrated: new docs/memory/cli/test-compiler.md (exposure contract + 4 design decisions); carve-out added to docs/memory/cli/report-writer.md; downstream-redaction pointer added to docs/memory/common/hierarchy.md; indexes regenerated.
  • Tests: full workspace 558 pass (7 redaction tests incl. 2 regressions); typecheck clean across 7 workspaces.

Origin: independent adversarial review of PRs #168#172.

Summary by CodeRabbit

  • Security Improvements

    • Prevented exact runtime secret values from appearing in AI planning and grounding prompts, logs, and serialized context.
    • Preserved hierarchy data for grounding while avoiding mutation of on-screen values.
    • Clarified that screenshots and transformed or structurally embedded secrets may still require caution.
  • Documentation

    • Added guidance on secret handling during test compilation, runtime execution, prompt processing, and report generation.
    • Documented the scope and limitations of secret redaction behavior.

testCompiler.ts overstated its guarantee — secrets never enter the
compiled objective, but resolved values still reach the model via the
post-typing accessibility hierarchy and screenshots. Comment rewritten
to scope the claim correctly and document the redaction wiring.

AIAgent now accepts optional RuntimeBindings (wired from sessionRunner)
and redacts exact resolved-secret values to ${secrets.KEY} placeholders
per-input before assembly/serialization, across every free-text input
and element/availableApps string field. No-bindings path is a no-op.

Reworked once: an initial whole-text redaction pass corrupted
serialized element JSON when a numeric secret matched a coordinate and
missed JSON-escaped secrets in remember entries. Replaced with
per-input pre-serialization redaction; 2 regression tests added.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change documents secret handling during test objective compilation and runtime execution. It passes runtime bindings into AIAgent, redacts resolved secrets from planner, grounder, and diagnostic text, preserves hierarchy data, and adds regression tests. Screenshots and non-exact secret renderings remain exposed.

Changes

Secret exposure contract and workflow records

Layer / File(s) Summary
Exposure contract and implementation plan
docs/memory/cli/index.md, docs/memory/cli/test-compiler.md, docs/memory/cli/report-writer.md, docs/memory/common/hierarchy.md, packages/cli/src/testCompiler.ts, fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/*
Documents compile-time placeholder behavior, runtime exposure paths, exact-value prompt redaction, hierarchy immutability, residual screenshot exposure, and implementation acceptance criteria.

Runtime secret redaction

Layer / File(s) Summary
Runtime binding propagation
packages/cli/src/sessionRunner.ts, packages/goal-executor/src/ai/AIAgent.ts
Passes optional config.runtimeBindings into AIAgent and stores the bindings for redaction.
Prompt and diagnostic redaction
packages/goal-executor/src/ai/AIAgent.ts
Redacts resolved secrets from planner and grounder inputs, hierarchy elements, available apps, action text, request logs, and detail payloads before serialization.
Redaction behavior validation
packages/goal-executor/src/ai/test/AIAgent.test.ts
Tests typed, escaped, numeric, and absent-binding cases, plus hierarchy immutability and planner/grounder prompt coverage.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant sessionRunner
  participant AIAgent
  participant Planner
  participant Grounder
  sessionRunner->>AIAgent: pass config.runtimeBindings
  AIAgent->>AIAgent: redact resolved secret values
  AIAgent->>Planner: send redacted objective and hierarchy
  AIAgent->>Grounder: send redacted action, hierarchy, and app records
Loading

Possibly related PRs

Suggested labels: bug, documentation

Suggested reviewers: arnoldlaishram

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% 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 secret-leak comment correction and prompt-path leak work addressed by the pull request.
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-gzzl-fix-secret-prompt-leak-comment

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

droid-ash and others added 2 commits July 31, 2026 11:32
No external review was obtained: Copilot code review is not enabled on
this repository (request polled 10 minutes, zero reviews landed) and the
CodeRabbit check's pass state is a rate-limit decline, not an approval.
Stage finished per the user's standing directive to not block on
external bots and treat the operator-side pipeline review as the
substitute — that review caught and fixed two must-fix redaction
defects in one rework cycle before the PR was opened.

Includes the pending review-pr telemetry line so main records the
change as completed (PR #170 precedent: unpushed finishing commit left
it permanently in-flight).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@droid-ash
droid-ash marked this pull request as ready for review July 31, 2026 17:14
@droid-ash
droid-ash merged commit 4854e40 into main Jul 31, 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: 3

🤖 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 `@fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/intake.md`:
- Line 16: Correct the description of Hierarchy.toPromptElementsForPlanner to
state that eligible image or button nodes contribute contentDesc, not node.text.
Keep node.text attribution only for Hierarchy.toPromptElementsForGrounder,
without changing the surrounding security analysis.

In `@fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/plan.md`:
- Around line 126-128: Update the A-002 acceptance-record wording to describe
redaction only on individual grounder and planner inputs before assembly,
including element fields where applicable; remove the claim that assembled
prompt text is redacted, while preserving the existing placeholder and reused
redactResolvedValue details.

In `@packages/goal-executor/src/ai/AIAgent.ts`:
- Around line 259-262: Update shared redactResolvedValue logic used by
_redactPromptText to preserve valid ${secrets.KEY} placeholders before matching
or replacing resolved values, preventing secret key/value collisions from
producing nested tokens; add a regression test covering this collision and
verify the original placeholder reaches the model unchanged.
🪄 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: a8137235-f510-48f4-b9b3-ac64a848daae

📥 Commits

Reviewing files that changed from the base of the PR and between 7fdebd0 and 8e95af9.

📒 Files selected for processing (12)
  • docs/memory/cli/index.md
  • docs/memory/cli/report-writer.md
  • docs/memory/cli/test-compiler.md
  • docs/memory/common/hierarchy.md
  • fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/.history.jsonl
  • fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/.status.yaml
  • 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/sessionRunner.ts
  • packages/cli/src/testCompiler.ts
  • packages/goal-executor/src/ai/AIAgent.ts
  • packages/goal-executor/src/ai/test/AIAgent.test.ts


## Why

**The pain point.** The comment at `packages/cli/src/testCompiler.ts:3-11` (written by PR #171's security-control documentation pass) claims that because `VARIABLE_REFERENCE_PATTERN` never matches `${secrets.*}` tokens, "secret VALUES never enter the LLM prompt, the model provider's logs, or compiled test artifacts." That guarantee is true only for the **compiled objective text**. It is false for the prompt as a whole: once `ActionExecutor._executeType` resolves a `${secrets.*}` placeholder (`resolveRuntimePlaceholders`, `packages/goal-executor/src/ActionExecutor.ts:363-365`) and types the real value into a field, the very next planner/grounder call captures a fresh accessibility hierarchy in which that field's `node.text` **is the secret value**. `Hierarchy.toPromptElementsForGrounder` (`packages/common/src/models/Hierarchy.ts:214-215`) and `toPromptElementsForPlanner` emit `node.text` verbatim; `AIAgent.ts` serializes those elements into the prompt (`~500-502` grounder, `~410-421` planner) and additionally attaches screenshots (`request.screenshot` / `request.postActionScreenshot`) showing the same screen. Nothing in `packages/goal-executor/src/ai/` redacts anything — `redactResolvedValue` is wired only into `reportWriter.ts` and `ActionExecutor._redactRuntimeString` (spans/errors/report artifacts), never the prompt path. All of this was independently re-verified at intake.

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 planner hierarchy description.

Line 16 says toPromptElementsForPlanner emits node.text. It emits contentDesc only for eligible image or button nodes. Keep node.text attribution limited to toPromptElementsForGrounder.

🤖 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 `@fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/intake.md` at line 16,
Correct the description of Hierarchy.toPromptElementsForPlanner to state that
eligible image or button nodes contribute contentDesc, not node.text. Keep
node.text attribution only for Hierarchy.toPromptElementsForGrounder, without
changing the surrounding security analysis.

Comment on lines +126 to +128
- [x] A-001 R1: The rewritten `testCompiler.ts` comment scopes the guarantee to the compiled objective, states the runtime hierarchy + screenshot exposure with the concrete file chain, states where redaction is and is not wired, warns against enabling full prompt logging, and keeps the pattern-widening warning
- [x] A-002 R3: `AIAgent` accepts optional bindings and redacts exact resolved-secret occurrences from grounder and planner prompt text (element fields pre-serialization plus the assembled text), replacing them with `${secrets.KEY}` placeholders via the reused `redactResolvedValue`
- [x] A-003 R3: `sessionRunner.createSessionExecutor` passes `config.runtimeBindings` to the `AIAgent`, and constructing `AIAgent` without bindings leaves prompt assembly byte-identical to the pre-change behavior

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

Correct A-002 to exclude assembled-text redaction.

Line 127 states that redaction also applies to “the assembled text.” AIAgent intentionally redacts each input before assembly. Replace this phrase with the per-input behavior so the acceptance record does not endorse a JSON-corrupting post-serialization pass.

🤖 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 `@fab/changes/260731-gzzl-fix-secret-prompt-leak-comment/plan.md` around lines
126 - 128, Update the A-002 acceptance-record wording to describe redaction only
on individual grounder and planner inputs before assembly, including element
fields where applicable; remove the claim that assembled prompt text is
redacted, while preserving the existing placeholder and reused
redactResolvedValue details.

Comment on lines +259 to +262
private _redactPromptText(value: string): string {
if (!this._bindings) return value;
return redactResolvedValue(value, this._bindings) ?? value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve existing secret placeholders during redaction.

Line 261 redacts testObjective, which intentionally contains ${secrets.KEY}. If bindings.secrets.PASSWORD is PASSWORD, redactResolvedValue rewrites ${secrets.PASSWORD} into ${secrets.${secrets.PASSWORD}}. The model then receives an invalid logical token.

Protect valid ${secrets.KEY} tokens before matching resolved values, preferably in shared redactResolvedValue. Add a regression test for a secret key/value collision.

🤖 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/goal-executor/src/ai/AIAgent.ts` around lines 259 - 262, Update
shared redactResolvedValue logic used by _redactPromptText to preserve valid
${secrets.KEY} placeholders before matching or replacing resolved values,
preventing secret key/value collisions from producing nested tokens; add a
regression test covering this collision and verify the original placeholder
reaches the model unchanged.

droid-ash added a commit that referenced this pull request Aug 1, 2026
* fix: Fix Redaction Prompt Corruption

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.

* Update ship status and record PR URL

* Update review-pr status

---------

Co-authored-by: droid-ash <droid-ash@users.noreply.github.com>
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