Skip to content

Secret hardening for HITL surfaces: serve-time re-redaction + raw-carrier strip - #679

Merged
ginccc merged 2 commits into
chore/remove-agent-fatherfrom
fix/hitl-secret-hardening
Aug 14, 2026
Merged

Secret hardening for HITL surfaces: serve-time re-redaction + raw-carrier strip#679
ginccc merged 2 commits into
chore/remove-agent-fatherfrom
fix/hitl-secret-hardening

Conversation

@ginccc

@ginccc ginccc commented Aug 14, 2026

Copy link
Copy Markdown
Member

Follow-up to the redaction-filter fix (943cd11) on this base branch — the filter fix closed the pattern gaps; this closes the architecture gaps that let a stale or missed redaction reach a human. Targets chore/remove-agent-father (#672) since all HITL machinery lives there.

Serve-time re-redaction

argumentsRedacted is computed once, at pause time, with whatever filter version existed then — a pause stored before a filter improvement kept serving its old, leaky redaction forever (exactly the observed sk-ant-… case). Every surface where pending-call arguments leave the server now re-runs SecretRedactionFilter at serve time: the approval-status summary (arguments + full request preview: uri, body, query params, headers), the detail=full snapshot, the MCP mirror, and the Slack approval card.

Raw carriers stripped from the approver surface

approval-status?detail=full returned the whole snapshot with only fingerprints stripped — argumentsRaw (unredacted by definition), the frozen LLM transcript (chatTranscriptJson) and the running trace (traceSoFar) rode along, each carrying the raw arguments the redaction beside them had masked. New sanitizePendingToolCallsForApprover strips all three and subsumes the fingerprint marker contract. The persisted document is untouched; resume reads from the store, not this view.

The tool trace records redacted values from the start

ToolLoopRunner stored the model's raw arguments (and raw tool results) in the trace — the same trace that feeds task summaries, SSE task_complete, memory, and the chat activity list. Both now pass through the filter at record time. Execution and the model's own view keep the raw values — only the display record is filtered.

Tests

Six new: five on the approver sanitizer (raw-carrier strip, stale-redaction re-redaction using an underscored key only the current filter catches, all four preview surfaces, fingerprint marker contract, null-safety at every level) and one end-to-end orchestrator test pinning that a credential embedded in tool arguments never survives into the trace.

The Manager-side counterpart (an inlineCredential escalation flag on the approval card) is a separate PR on EDDI-Manager.

🤖 Generated with Claude Code

CI note

This PR targets chore/remove-agent-father, and ci.yml only triggers for PRs against main — so no CI runs here (only CodeRabbit registers, skipped for non-main bases). Validation instead: full compile plus 555 tests across every affected area (secrets, memory utilities, orchestrator/tool loop, streaming, Slack HITL) run green locally in an isolated worktree. Full CI covers this content once it lands on the #672 branch, which is a PR to main.

…action + raw-carrier strip

The filter fix (943cd11) closed pattern gaps; this closes the
architecture gaps that let a stale or missed redaction reach a human:

- Serve-time re-redaction everywhere pending-call arguments leave the
  server: argumentsRedacted is computed once, at pause time, with the
  filter of that day - a pause stored before a filter improvement kept
  serving its old, leaky redaction forever. The approval-status summary,
  the detail=full snapshot, the MCP mirror and the Slack approval card now
  re-run SecretRedactionFilter over every served string.

- The raw carriers never leave the server on the approver surface:
  detail=full returned the whole snapshot with only fingerprints stripped;
  argumentsRaw, chatTranscriptJson and traceSoFar rode along, each
  carrying the raw arguments the redaction beside them masked.
  sanitizePendingToolCallsForApprover strips all three (persisted document
  untouched; resume unaffected).

- The tool trace records redacted arguments AND results from the start:
  the trace feeds task summaries, SSE, memory and the chat activity list.
  Execution and the model's own view keep the raw values.

Six new tests incl. an end-to-end orchestrator pin that a credential
embedded in tool arguments never survives into the trace.
@ginccc
ginccc requested a review from rolandpickl as a code owner August 14, 2026 12:12
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b8526b01-b4ea-4348-914c-08a79a6d4b1b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

…cret

${vault:...} is a POINTER to a secret, and the correct alternative to
writing one down. The key name it carries is ordinary configuration an
admin reads in the agent document anyway. Masking it cost real
information and bought nothing:

- on an approval card it hid WHICH credential a request uses, which is
  exactly what an approver needs to judge it;
- it made every correct, vault-referencing request display a <REDACTED>
  marker, training approvers to read that marker as normal - and that
  marker is precisely the signal the Manager uses to warn that a request
  embedded a secret LITERAL. A control that fires on the safe case is a
  control people learn to ignore.

A resolved secret does not look like a vault reference (it is the raw
value, caught by the remaining rules), so nothing is weakened. The generic
key=value rule cannot match one either - its value class excludes braces.

Three tests pin that references stay legible: plain, inside JSON, and the
legacy ${eddivault:...} spelling.
@ginccc
ginccc merged commit 21288c5 into chore/remove-agent-father Aug 14, 2026
2 checks passed
pull Bot pushed a commit to Stars1233/EDDI that referenced this pull request Aug 17, 2026
… live guard

A four-reviewer audit of labsai#679-labsai#689 plus an end-to-end operator-path trace,
then a second adversarial round over this fix itself. Everything confirmed
is addressed here; docs/changelog.md carries the full account.

- Cross-version placeholder stranding: dropPendingApprovalPlaceholder now
  recognises the previous builds' default wordings (legacy constant,
  suffix-less tool-named), so the first post-upgrade resume of an in-flight
  pause no longer renders [stale placeholder, answer]. Two upgrade-boundary
  tests simulate a pre-upgrade pause.
- Self-conversation guard is now enforced on the LIVE path, including the
  mixed-batch pause branch the second review round caught (ungated calls
  execute before the pause is thrown and are never rechecked). Shared core
  extracted; same NOT_EXECUTED envelope and trace everywhere.
- labsai#684 contract narrowed: failed results stay out of ApiCallsTask's
  cross-call template merge and out of the RAG system prompt; error bodies
  (and the status-message fallback) are redacted before reaching the model.
  Memory-side *Error keys unchanged.
- Test-drive read-back: a blank returningFields entry means NO filter -
  [""] no longer nulls steps/outputs/properties out of the snapshot.
- Generated tools: body $refs resolve one level (schemas namespace only),
  so descriptions name real fields - a guessed say-body bound to InputData
  defaults and silently sent an empty message; enum values and defaults now
  reach parameter descriptions (the environment typo->production trap).
- padDataLines normalises bare CR; RFC 7615 headers join the credential
  response deny-list; labsai#688's shared-path stripping disclosed in changelog.

264 tests across the affected suites, including mutation-informed pins:
same-tool ordinal drop, refused-mid-batch pairing, blank-filter recovery,
redaction survival of the failure reason.
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