Version: 0.1.0 Created: 2026-04-22 PRD Reference: docs/prd.md Status: Complete Target Release: v0.9.0 Shipped-In: v0.9.0 Last-Updated: 2026-04-22 Milestone: M3-pre-ga-hardening Companion-of: prompt-injection-defense
ADD agents read files freely during cycles, write checkpoint logs, run commit commands, and synthesize artifacts (learnings, handoff, retro, dashboard). Today there is no specific defense against an agent reading .env, ~/.aws/credentials, ~/.ssh/id_rsa, or a vendored secrets file into context — and once a secret is in context, it can leak into a learning entry, a handoff summary, a retro table, or be staged into a commit. The existing PII heuristic in core/rules/learning.md covers names/emails but not credentials.
This spec ships a narrow, high-ROI accidental-disclosure defense: an auto-loaded rule that forbids reading well-known secret-bearing paths and writing matching values into ADD storage, a .secretsignore template installed by /add:init, a pre-commit grep gate in core/skills/deploy/SKILL.md that catches secrets before they leave the workstation, and an upgraded redaction pass on the learning write path. Industry context grounds the choice of patterns: GitGuardian's 2025 State of Secrets Sprawl reported 28.6M secrets leaked to public GitHub (a 34% YoY increase) with repos using AI tooling 40% more likely to leak; Lasso/TurboGeek's 2026 research found a four-rule CLAUDE.md snippet covering common secret patterns to be the single highest-ROI mitigation among 30+ they evaluated; DZone, Aembit, and Help Net Security have all published 2026 coverage of the "AI agents exfiltrating secrets" cluster.
This spec is the companion to specs/prompt-injection-defense.md. They share the threat model in core/knowledge/threat-model.md but address different vectors — injection is adversarial content trying to redirect the agent; secrets handling is accidental disclosure of legitimate user data.
The boundary is explicit: ADD prevents accidental disclosure. ADD does not stop a determined exfiltration attack via a compromised tool — that requires capability-based runtime sandboxing (Claude Code's permission system), which is out of scope.
As an ADD user, I want the agent to refuse to read my .env and credential files by default, redact any secret-shaped string before it lands in a learning or handoff, and block any commit that contains a secret pattern, so that accidental leakage of my keys into a public repo or shared artifact becomes hard rather than easy.
| ID | Criterion | Priority |
|---|---|---|
| AC-001 | core/rules/secrets-handling.md exists, is auto-loaded by the maturity loader at all maturity levels (alpha+), and is under 80 lines. |
Must |
| AC-002 | The rule forbids the agent from reading these path patterns without explicit per-invocation human approval: .env*, *.pem, *.key, id_rsa*, id_ecdsa*, id_ed25519*, .aws/, .ssh/, .gnupg/, secrets/, credentials*, .netrc, .pgpass, *.kdbx. |
Must |
| AC-003 | The rule forbids writing any value matching the regex catalog into ANY ADD artifact: .add/learnings.json, .add/handoff.md, .add/retros/*, .add/observations.md, dashboard reports, telemetry exports. |
Must |
| AC-004 | The rule includes a redact-on-ingest directive: when a tool result (file read, bash output, etc.) contains a value matching the regex catalog, the agent must replace it with [REDACTED:{pattern_name}] before writing any summary or appending to ADD storage. |
Must |
| AC-005 | The rule cites the boundary explicitly: "ADD prevents accidental disclosure. For exfiltration defense, use Claude Code's permission system to deny Read on credential paths." |
Should |
| ID | Criterion | Priority |
|---|---|---|
| AC-006 | A documented regex catalog covers at minimum: AWS access key (AKIA[0-9A-Z]{16}), GitHub tokens (gh[pousr]_[A-Za-z0-9]{36,}), Stripe live keys ((sk|pk)_live_[A-Za-z0-9]{24,}), OpenAI keys (sk-(proj-)?[A-Za-z0-9]{32,}), Anthropic keys (sk-ant-[A-Za-z0-9_-]{32,}), JWTs (eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+), generic password\s*[:=]\s*['"][^'"]{8,}['"], and PEM headers (-----BEGIN (RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----). |
Must |
| AC-007 | A high-entropy heuristic flags base64-ish or hex strings of 32+ characters with Shannon entropy > 4.5 bits/char, but is suppressed inside known safe contexts (lockfile hashes, git SHAs in commit references, content-addressed identifiers). | Should |
| AC-008 | The catalog lives in a single source file (e.g., core/knowledge/secret-patterns.md) so the rule, the deploy gate, and the learning redactor all reference the same definitions. |
Must |
| ID | Criterion | Priority |
|---|---|---|
| AC-009 | core/templates/.secretsignore.template exists and uses gitignore-compatible pattern syntax. |
Must |
| AC-010 | Default content covers: .env, .env.*, !.env.example, *.pem, *.key, *.cer, id_rsa*, id_ecdsa*, id_ed25519*, .aws/, .ssh/, .gnupg/, secrets/, credentials*, .netrc, .pgpass, *.kdbx, terraform.tfvars, *.tfvars.json. |
Must |
| AC-011 | /add:init writes .secretsignore to the project root if absent; never overwrites an existing file. |
Must |
| AC-012 | /add:init prints a one-line notice when the file is created: Wrote .secretsignore (commit this — your team shares the policy). |
Should |
| ID | Criterion | Priority |
|---|---|---|
| AC-013 | core/skills/deploy/SKILL.md runs a secret scan on staged content (git diff --cached) before composing the commit message. |
Must |
| AC-014 | The scan applies every pattern from the regex catalog (AC-006) plus the high-entropy heuristic (AC-007). | Must |
| AC-015 | On any match, the gate aborts the commit, prints the file path, line number, matched pattern name (not the matched value), and a remediation suggestion: git restore --staged {file} and add to .gitignore or .secretsignore. |
Must |
| AC-016 | A --allow-secret flag exists. To use it the human must type the confirmation phrase I have verified this is not a real secret in full — case-sensitive, no abbreviation. |
Must |
| AC-017 | The --allow-secret invocation is recorded in .add/observations.md with timestamp, file, pattern name, and the human's stated reason. |
Should |
| AC-018 | The gate honors .secretsignore — files matching ignore patterns are never staged for scanning, and if they appear staged anyway (user override), the gate flags them as "this file should not be committed at all." |
Should |
| ID | Criterion | Priority |
|---|---|---|
| AC-019 | core/rules/learning.md (or its post-PR-#6 successor) extends its PII heuristic to call the regex catalog (AC-006) and replace matches with [REDACTED:{pattern_name}] at write time, before any append to .add/learnings.json or ~/.claude/add/library.json. |
Must |
| AC-020 | The same redaction pass runs when writing .add/handoff.md, .add/retros/*.md, and dashboard report exports. |
Must |
| AC-021 | A redaction event logs to .add/observations.md (or a new .add/redaction-log.json) so the human can audit what was suppressed without seeing the secret itself. |
Should |
| ID | Criterion | Priority |
|---|---|---|
| AC-022 | core/knowledge/threat-model.md (created by the prompt-injection-defense spec) gains a "Secrets Disclosure" section linking to this spec and summarizing the four defenses (read-deny, redact-on-ingest, pre-commit gate, learning redactor). |
Must |
| AC-023 | A fixture test suite under tests/secrets/ includes one positive sample per regex pattern in the catalog and asserts the gate fires; one negative sample per common safe content type (package-lock.json SHA, git log commit SHAs, UUIDs, base64-encoded asset hashes) and asserts the gate does NOT fire. |
Must |
| AC-024 | False-positive rate on a corpus of 10 popular open-source repos' lockfiles is zero. | Should |
Precondition: Project root contains .env with real-looking values. Rule is auto-loaded.
Steps:
- User asks agent: "Read .env and tell me the database URL."
- Rule fires before the Read tool call.
- Agent responds explaining the policy and asks for explicit confirmation.
Expected Result: Agent does not read
.env. Surfaces the rule citation. Asks: "Confirm reading .env this once?" Maps to: TBD
Precondition: A developer accidentally stages config/local.py containing AWS_KEY = "AKIAIOSFODNN7EXAMPLE".
Steps:
- Agent runs
/add:deploy. - Deploy skill runs the staged-content scan.
- AWS pattern matches.
Expected Result: Commit aborts. Output:
config/local.py:14 — AWS_ACCESS_KEY pattern detected. Run: git restore --staged config/local.py && add to .secretsignore.No commit is created. Maps to: TBD
Precondition: Test fixture file tests/fixtures/fake_keys.py contains intentionally-fake AKIA...EXAMPLE strings. Gate fires correctly.
Steps:
- User runs
/add:deploy --allow-secret. - Skill prompts for confirmation phrase.
- User types: "ok"
- Skill rejects.
- User types: "I have verified this is not a real secret"
- Skill accepts and proceeds.
Expected Result: Commit only proceeds after the exact phrase. The override is recorded in
.add/observations.mdwith timestamp, file, pattern, and reason. Maps to: TBD
Precondition: Agent just verified a deployment. Tool output included Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature from a curl response.
Steps:
- Post-deploy checkpoint fires.
- Agent drafts a learning entry summarizing what worked.
- Redaction pass runs on the entry body.
Expected Result: Stored learning text reads
Bearer [REDACTED:JWT]. The original token does not appear in.add/learnings.json. A redaction event is logged. Maps to: TBD
Precondition: A normal commit includes updates to package-lock.json with hundreds of sha512-... integrity hashes.
Steps:
- Agent runs
/add:deploy. - Gate scans the staged diff.
- Lockfile context is detected; entropy heuristic suppressed.
Expected Result: Commit proceeds. No false-positive output. No
--allow-secretneeded. Maps to: TBD
Precondition: Fresh project with no .secretsignore file.
Steps:
- User runs
/add:init. - Init copies
core/templates/.secretsignore.templateto./.secretsignore. Expected Result:.secretsignoreexists at project root with default content. One-line notice printed. Re-running/add:initdoes not overwrite. Maps to: TBD
Precondition: User accidentally pasted an SSH private key into a markdown doc and staged it. Steps:
- Agent runs
/add:deploy. - Gate detects
-----BEGIN OPENSSH PRIVATE KEY-----header. Expected Result: Commit aborts withPEM_PRIVATE_KEY pattern detected. Suggestion includesadd *.md path or rotate the key immediately if it was real. Maps to: TBD
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Stable identifier used in error messages and redaction tags (e.g., AWS_ACCESS_KEY, JWT, STRIPE_LIVE_SECRET) |
regex |
string | Yes | The pattern (PCRE-compatible) |
description |
string | Yes | Human-readable description for documentation |
provider |
string | No | aws | github | stripe | openai | anthropic | generic |
confidence |
enum | Yes | high (deterministic prefix) | medium (entropy + context) |
remediation |
string | Yes | Suggested action (e.g., "rotate key in AWS IAM console immediately") |
| Field | Type | Required | Description |
|---|---|---|---|
date |
string | Yes | ISO 8601 timestamp |
artifact |
string | Yes | Where the redaction occurred: learning | handoff | retro | dashboard |
pattern_name |
string | Yes | Which pattern matched |
count |
integer | Yes | Number of redactions in that write event |
source_skill |
string | No | Which skill triggered the write |
# Environment files
.env
.env.*
!.env.example
!.env.sample
# Private keys and certs
*.pem
*.key
*.cer
id_rsa*
id_ecdsa*
id_ed25519*
# Credential stores
.aws/
.ssh/
.gnupg/
.netrc
.pgpass
*.kdbx
# Secrets directories
secrets/
credentials*
# Infrastructure
terraform.tfvars
*.tfvars.json
N/A — pure markdown/JSON plugin. The "API" surface is the regex catalog file, the rule file, the template file, and the deploy skill behavior.
CLI output for the pre-commit gate (example):
SECRETS GATE — /add:deploy
Scanning 3 staged files...
✗ config/local.py:14 — AWS_ACCESS_KEY pattern
✗ docs/setup.md:88 — PEM_PRIVATE_KEY pattern
Commit aborted. Two options:
1. Remove the secrets:
git restore --staged config/local.py docs/setup.md
Add config/local.py to .secretsignore
Rotate any real secrets immediately
2. False positive (test fixture, example, etc.):
/add:deploy --allow-secret
(you will be asked to type a confirmation phrase)
| Case | Expected Behavior |
|---|---|
| Agent has already read a secret into context before the rule loaded | Redact at write time; warn human that context may contain leaked data; recommend /clear |
.secretsignore doesn't exist |
Fall back to built-in default patterns from the template; print one-time notice suggesting /add:init |
User legitimately needs to commit a .env.example |
!.env.example negation in default .secretsignore allows it; gate's content scan will not match because example files use placeholder values |
Regex matches inside a string clearly marked as a test fixture (path contains test, fixture, example, mock) |
Still flag, but downgrade severity in the message and suggest --allow-secret rather than full abort |
| Staged file is binary | Skip content scan; flag if filename matches .secretsignore patterns (e.g., *.kdbx) |
| Commit gate runs but git is not initialized | Skip silently; first-commit projects shouldn't be blocked |
| High-entropy string is a UUID, git SHA, content hash, or build ID | Suppressed by context heuristic; do not flag |
| Pattern catalog updated in plugin upgrade | Existing .secretsignore files left untouched (user owns them); rule reads catalog from plugin so updates flow automatically |
| Two sessions write learnings concurrently and both contain a secret | Both redact independently before write; last-write-wins on the JSON file is acceptable since both wrote redacted text |
| Custom/proprietary secret format not in catalog | Out of scope (see non-goals); user can add patterns to a future .add/secret-patterns.local.json (deferred) |
core/rules/secrets-handling.md— new auto-loaded rule (this spec creates it)core/templates/.secretsignore.template— new template (this spec creates it)core/knowledge/secret-patterns.md— new catalog (this spec creates it)core/skills/deploy/SKILL.md— gains pre-commit grep gatecore/skills/init/SKILL.md(or equivalent) — copies the template on initcore/rules/learning.md— extends PII heuristic to call the regex catalog (coordinate with PR #6 if it lands first)core/knowledge/threat-model.md— gains "Secrets Disclosure" section (created byspecs/prompt-injection-defense.md)specs/prompt-injection-defense.md— companion spec; shares threat model
| Category | Requirement |
|---|---|
| Environment variables | N/A |
| Registry images | N/A |
| Cloud quotas | N/A |
| Network reachability | N/A |
| CI status | N/A |
| External secrets | N/A |
| Database migrations | N/A |
Verification before implementation: Fixture corpus assembled in tests/secrets/ covering positive samples for every catalog pattern and negative samples for the top 10 lockfile/hash false-positive sources.
- Runtime sandbox or filesystem boundary enforcement (Claude Code's permission system, not ADD's)
- Network egress controls
- Encryption-at-rest for
.add/files - Detection of custom or proprietary secret formats not in the public catalog
- Rotating leaked secrets automatically (manual step, recommended in error message)
- Server-side secret scanning (GitHub push protection, GitGuardian) — complementary, not replaced by this spec
- Should
.secretsignorebe auto-added to.gitignoreby/add:init, or kept committed so the team shares the policy? (Lean: commit it. The patterns are not themselves sensitive, and a shared policy beats a private one.) - The high-entropy heuristic risks false positives on hashes, UUIDs, base64 commits. What is the right entropy threshold + length floor? (Initial proposal: Shannon entropy > 4.5 bits/char AND length >= 32 AND not inside a lockfile/SHA context. Tune with the false-positive test corpus.)
- Should
/add:learnings stats(or the dashboard) report "X potential secrets redacted in the last 30 days" as a transparency surface? (Lean: yes, low-cost trust signal.) - Should the rule allow reading
.env.exampleand.env.samplewithout prompt, since they are conventionally placeholder-only? (Lean: yes, allow*.exampleand*.samplesuffixes by default.)
Small. Estimated 1–1.5 days for the rule + template + grep gate + fixture tests. Suitable for Cycle 1 of M3 — early ship removes a GA blocker quickly.
| Date | Version | Author | Changes |
|---|---|---|---|
| 2026-04-22 | 0.1.0 | abrooke + Claude | Initial spec from /add:spec interview; companion to prompt-injection-defense |