Skip to content

feat(ai): add secret shield for outbound credential redaction - #58410

Closed
GiacomoSaccaggi wants to merge 3 commits into
anomalyco:devfrom
GiacomoSaccaggi:secret-shield-v2
Closed

GiacomoSaccaggi wants to merge 3 commits into
anomalyco:devfrom
GiacomoSaccaggi:secret-shield-v2

Conversation

@GiacomoSaccaggi

@GiacomoSaccaggi GiacomoSaccaggi commented Oct 23, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #50904

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds an opt-in credential scanning/redaction layer that intercepts outbound LLM requests before they reach providers. The shield scans system prompts, user messages, tool-result outputs (bash/read), tool-call inputs, reasoning parts, and tool descriptions for credential patterns.

Detection: 16 API key prefix families (OpenAI, Anthropic, GitHub, AWS, Stripe, Slack, etc.), JSON sensitive field names (password, token, api_key, etc.), HTTP auth headers, connection string URIs, and JWT tokens. Prefix patterns are left-anchored to avoid false positives inside identifiers like risk-assessment-module.

Structured value handling: Tool-call inputs and JSON tool results are walked recursively — only string leaves are redacted, preserving object shapes so providers receive valid structured data. Values under sensitive keys (password, token, etc.) are masked unconditionally.

Four modes (via secret_shield config key or OPENCODE_SECRET_SHIELD env var):

  • off (default) — no scanning
  • warn — scan and log detections, send unmodified
  • mask — replace detected credentials with [REDACTED]
  • block — reject the entire request if any credential detected

Mode resolution lives in core (config first, env var fallback). The ai package takes an explicit SecretShieldMode argument with no side effects.

Files changed:

  • packages/ai/src/secret-shield.ts — redaction engine
  • packages/ai/src/index.ts — public exports
  • packages/schema/src/config.ts — secret_shield config field
  • packages/core/src/session/runner/llm.ts — shield wired after request preparation, before steps.attempt()
  • packages/ai/test/secret-shield.test.ts — 24 tests

Port of #50905 to the V2 architecture.

How did you verify your code works?

  • 24 tests covering: pattern families, false-positive boundaries (risk-assessment-module, disk-usage-monitor), tool-result text/json/sensitive-key redaction, reasoning scanning, tool-call input structure preservation, message and tool-description scanning, and all four mode behaviours including fail-closed.
  • bun typecheck clean on packages/ai, packages/schema, packages/core.
  • Tested directly by calling applySecretShield on sample requests with real credential patterns in all content part types.

Screenshots / recordings

N/A — no UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 72 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The feature doesn't work as described yet. I ran the added tests (85 pass), type-checked ai and core (clean), and called the shield directly on sample requests.

Public API: adds the secret_shield config key (off|warn|mask|block), the OPENCODE_SECRET_SHIELD environment variable, and new @opencode/ai exports (applySecretShield, resolveSecretShieldMode, SecretShieldMode, ShieldResult, ShieldReport). Nothing is removed or changed, so it is backwards compatible. As wired now, though, the config key has no effect.

Main concerns (details inline):

  • Config key not applied: secret_shield in config is never read. Only the environment variable works.
  • Tool results not scanned: only text parts are scanned, so output from bash/read tools, the main way credentials reach the model (e.g. cat .env), is sent unmodified. Tool-call inputs and reasoning parts aren't scanned either.
  • False positives in mask mode: the prefix patterns have no left boundary. risk-assessment-module, disk-usage-monitor and task-scheduler-service are all masked, which corrupts code the model sees and then edits.
  • Package layering: packages/ai reads process.env directly. The mode should be resolved in core, from config plus environment, and passed in.
  • Tests: at 562 lines and 85 tests, the test file is very large for what it covers. Please cut it down to one case per pattern family plus the mode behaviours, and add a test for tool-result redaction and for the config key actually taking effect.

Related: #51907

Comment thread packages/core/src/session/runner/llm.ts Resolved
Comment thread packages/ai/src/secret-shield.ts Resolved
Comment thread packages/ai/src/secret-shield.ts Resolved
Comment thread packages/ai/src/secret-shield.ts Resolved

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new commits have exactly the same content as the previous head (git diff between the two is empty), so none of the earlier points are addressed and those threads stay open. The added tests still pass (85/85). Public API impact is unchanged since the last review: adds the secret_shield config key, the OPENCODE_SECRET_SHIELD env var and new @opencode/ai exports (backwards compatible).

@GiacomoSaccaggi
GiacomoSaccaggi force-pushed the secret-shield-v2 branch 2 times, most recently from 9fecf3a to 06ba5d3 Compare October 23, 2026 09:55

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The earlier points are addressed: the config key is now read (with the env var as fallback), tool results, tool-call inputs and reasoning are scanned, the prefix patterns are anchored, and packages/ai no longer reads process.env. Tests (21/21) and tsc on ai and core pass. Public API impact is unchanged from the last review: adds the secret_shield config key, the OPENCODE_SECRET_SHIELD env var and new @opencode/ai exports, now applySecretShield (mode required), isSecretShieldMode and types. Backwards compatible.

One new problem with the new scanning: masking structured tool-call inputs or json tool results turns the object into a JSON string, which changes the shape sent to providers (see inline).

Comment thread packages/ai/src/secret-shield.ts Resolved
Comment thread packages/ai/src/secret-shield.ts Resolved

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The earlier points are addressed: tool-call inputs and json/error tool results now keep their structure, and only the string values inside them are redacted. Tests (23/23) and tsc on ai pass. Public API impact is unchanged since the last review: it adds the secret_shield config key, the OPENCODE_SECRET_SHIELD env var and new @opencode/ai exports, all backwards compatible.

One new gap: because the walker only looks at string values, plain secrets under sensitive keys in structured values (e.g. {password: "…"}) are no longer detected (see inline).

Comment thread packages/ai/src/secret-shield.ts Resolved
GiacomoSaccaggi and others added 2 commits October 23, 2026 15:36
Scan LLM requests for credential patterns before sending to providers.
Four modes: off (default), warn, mask, block. Detects 16 API key prefix
families, JSON sensitive fields, HTTP auth headers, connection string
URIs, and JWT tokens. Scans text, tool-result, reasoning, and tool-call
content parts. Structured values are walked recursively to redact string
leaves and values under sensitive keys while preserving object shapes.
Left-anchored prefix patterns prevent false positives inside identifiers.
Mode is an explicit argument; config and environment resolution belongs
to core.
24 tests covering pattern families, false-positive boundaries, tool-result
redaction (text, json, structured sensitive keys), reasoning scanning,
tool-call input structure preservation, message and tool-description
scanning, and all four mode behaviours. Runner: mode resolved from config
with env var fallback; shield applied before steps.attempt().

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The last point is addressed: plain secrets under sensitive keys (password, token, …) in structured tool-call inputs and json tool results are masked again, case-insensitively and including arrays under those keys, and the value's shape is kept. Tests (24/24) and tsc on ai pass. Public API impact is unchanged since the last review: it adds the secret_shield config key, the OPENCODE_SECRET_SHIELD env var and new @opencode/ai exports, all backwards compatible. No new concerns.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new commit only merges the latest dev into this branch. The pull request's own changes are the same as before, and the shield tests (24/24) and tsc on ai and core still pass on the merged code. Public API impact is unchanged: it adds the secret_shield config key, the OPENCODE_SECRET_SHIELD env var and new @opencode/ai exports, all backwards compatible. No new concerns.

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 72-hour window.

Feel free to open a new pull request that follows our guidelines.

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.

[FEATURE]: Opt-in credential redaction before outbound LLM requests

2 participants