SuperAPI AI Directory

secrets-scan

Security & auditing

View repository

Updated 10-15 20:24

About this skill

Secrets Scan

Prompt contract

Treat $ARGUMENTS as a free-form RU/EN prompt. Modes/flags may appear anywhere; infer mode and scope from prose.

Mode EN keywords RU keywords Mutates?
scan (empty), scan, check, audit, find scan, check, audit, find no
fix fix, remediate, clean up, --fix fix, repair, patch yes
  1. Explicit --fix resolves to fix before stripping flags. An explicit mode token anywhere wins outright.
  2. Else score modes by distinct whole-word keyword hits (table above). Highest unique score wins; tie -> scan (read-only wins). All zero -> scan.
  3. Empty arguments -> scan; ask no routing question. The Phase 6 findings-triage offer requires approval before any remediation.
  4. fix is also auto-offered (not auto-run) whenever CRITICAL/HIGH findings exist, per Phase 6 — that offer is the outcome-changing AskUserQuestion, not a second resolution pass.
  5. Prose that is not a mode/flag is still input: treat it as scope narrowing (e.g. "scan the api folder") if the skill supports it, otherwise ignore for routing and scan the full repo.

Then print this block ONCE, before the first action:

PLAN — brewtools:secrets-scan
INPUT:  <arguments verbatim, or "(empty)">
MODE:   <resolved> — <explicit | matched keyword: X | default>
SCOPE:  <resolved paths / target / level / flags>
DO:     <2-5 imperative bullets>
RESULT: <what the user ends up holding>

Labels are literal; values follow the conversation language. Print once before Phase 1's first action; fill scope from the requested repository, then use the returned file list without repeating the PLAN.

Phase 1: Setup

EXECUTE using Bash tool:

bash "${CLAUDE_SKILL_DIR}/scripts/scan-init.sh" && echo "OK init" || echo "FAILED init"

The script resolves the repo root, creates the report dir under umask 077 + chmod 700, appends .claude/reports/ to .gitignore when nothing already ignores it, and writes the git-tracked file list to {DIR}/files.txt (chmod 600). It prints one line: DIR=..|REPO=..|TS=..|TOTAL=..|GITIGNORE=appended|already-ignored.

Why the .gitignore line. The report names where credentials live. .claude/ is ignored in some repos only by a personal global excludes file — in a consumer repo .claude/reports/ is committable, so the scan would publish its own findings. Report the GITIGNORE= value to the user.

Substitute the printed DIR value into every later command — the Bash tool starts a fresh shell each call, so nothing set by this script survives into the next block.

EXECUTE using Bash tool to read the chunk source:

cat "{DIR}/files.txt"

STOP if FAILED init — must run in a git repository.

Phase 2: Split & Launch 10 Agents

  1. Parse file list → split into 10 chunks (ceil(total/10))
  2. Send 10 Agent calls in parallel (single message)

Config: Agent(subagent_type="general-purpose", model="haiku", description="Agent N/10 scan")

Delegation

Start with a 10-way split: one agent owns one chunk, ~<=10 steps. Split oversized chunks further; spawn all Agent calls in ONE message.

Every brief must retain GOAL, ROLE (ownership/exclusions), SCOPE (paths/commands), CONTEXT (completed/parallel work), CONSUMER (next use/output shape), and DONE (acceptance/report).

Never use a bare one-line brief. The prompt below is that shape; reuse it verbatim per chunk.

GOAL: auditing this repo for leaked credentials before they reach a remote. Ten agents split the git-tracked file list; you own chunk {N} and the merged report depends on your JSON.

ROLE: read + classify only. Do NOT edit, delete, redact, or rewrite any file. Do NOT scan files outside your chunk.

SCOPE: in — exactly these files. Out — every other path in the repo, git history, .git/. FILES: {FILES}

CONTEXT: repo root {REPO}. The file list was already produced from git-tracked files and filtered in Phase 1 (binary/media extensions, vendor + build dirs, lock files dropped) — do not re-discover or re-filter it. git ls-files still returns files that are tracked AND .gitignored, so treat every path in your chunk as live. Nine sibling agents are scanning chunks 1..10 of that same list in parallel right now. Patterns, criticality ladder and skip rules below are final — do not invent extra categories.

CONSUMER: the skill merges the ten JSON objects into one Secrets Report the user triages before pushing. Every path in FILES must appear exactly once across scanned[] + skipped[]; the skill checks that invariant and re-spawns a chunk that fails it. Malformed JSON, or any prose around it, makes your chunk UNSCANNED and blocks the clean verdict for the whole repo.

REDACTION (mandatory): never quote, echo, paste or summarise a matched secret value — not in the JSON, not in prose, not in a thought. For each hit run node ${CLAUDE_SKILL_DIR}/scripts/redact.mjs <path> <line> and copy its match_len, sha256_12 and preview fields verbatim into your finding. status:"SKIP_ENV_REF" means it is an env reference — drop the finding. status:"NO_MATCH" means no pattern fired on that line — drop it too, and count the file as scanned.

DONE: the JSON object specified under OUTPUT, nothing else. No prose, no markdown fence.

PATTERNS:

Category Match
Passwords password/passwd/secret/pwd + = or :
API Keys api_key, access_key, apikey, api_secret
Tokens token, bearer, auth_token, access_token
AWS AKIA[0-9A-Z]{16}, aws_secret, aws_access_key
DB URLs jdbc/mongodb/mysql/postgres with credentials
Keys -----BEGIN.*PRIVATE KEY-----, client_secret, encryption_key

CRITICALITY:

Level Criteria
CRITICAL Real credentials, private keys, DB connection strings
HIGH Real API keys/tokens, AWS creds
MEDIUM Suspicious hardcoded values
LOW Placeholders: changeme, YOUR_KEY, xxx, dummy

SKIP: env refs (process.env.*, ${VAR}, os.getenv()), placeholders, docs/comments.

OUTPUT (JSON):

{"agent":{N},"scanned":["f1","f2"],"skipped":[{"path":"x","reason":"binary"}],"findings":[{"path":"f","line":1,"category":"Passwords","match_len":12,"sha256_12":"a1b2c3d4e5f6","preview":"hunt***","desc":"Hardcoded pwd","crit":"HIGH"}]}

No findings: "findings":[]. There is no field for the raw value — a finding carrying one is a defect, not a better report.

Phase 3: Reconcile & Merge

Every chunk is accounted for before anything is merged. A vanished chunk would leave ~10% of the repo unread while Summary still claimed Files: {TOTAL}.

  1. Write each agent's raw JSON to {DIR}/agent-{N}.json and its assigned chunk to {DIR}/assigned-{N}.txt (one path per line).
  2. EXECUTE using Bash tool, per agent:
    node "${CLAUDE_SKILL_DIR}/scripts/reconcile.mjs" "{DIR}/assigned-{N}.txt" "{DIR}/agent-{N}.json"; echo "rc=$?"
    
    rc=0 OK → accept. rc=1 MISMATCH (paths in missing[]/extra[]) or rc=2 MALFORMED (unparsable JSON, missing scanned[]/skipped[]) → step 3.
  3. Re-spawn that ONE chunk once, with the identical prompt. Reconcile the new response the same way.
  4. Still not OK after the re-spawn → record the chunk as UNSCANNED with its missing[] paths, and refuse the clean verdict for the whole scan (Phase 4 + Phase 5).
  5. Merge scanned[], skipped[], findings[] from the reconciled chunks only.
  6. Dedupe by path+line
  7. Sort: CRITICAL → HIGH → MEDIUM → LOW

A scan with any UNSCANNED chunk is INCOMPLETE, never clean — regardless of how many findings the reconciled chunks returned.

Phase 4: Generate Report

Write {DIR}/report.md, then restrict it — the report is a map of where credentials live:

EXECUTE using Bash tool:

chmod 600 "{DIR}/report.md" && echo "OK perms" || echo "FAILED perms"

Scan: {TS} | Repo: {REPO} | Files: {TOTAL} | Agents: 10 Verdict: {CLEAN | FINDINGS | INCOMPLETE}

Values are redacted by construction: Fingerprint is sha256(value)[:12], Preview reveals at most 4 leading characters, Len is the character count. The raw value is never stored here.

Summary

Metric Count
Scanned {N}
Skipped {N}
Unscanned {N}
CRITICAL {N}
HIGH {N}
MEDIUM {N}
LOW {N}

Findings

CRITICAL ({N})

# File Line Category Len Fingerprint Preview Description
{ROWS}

HIGH / MEDIUM / LOW

(same table format)

Agent Stats

Agent Assigned Accounted Reconciled Findings
1-10 ... ... OK / UNSCANNED ...
Total {N} {N} {N} {N}

Unscanned ({N})

Present only when a chunk failed reconciliation twice. Every path here was NEVER read — the scan is INCOMPLETE and cannot be used as a pre-push clearance.

# Path Agent Reason
{UNSCANNED}

File Inventory

Scanned ({N})

# Path Agent
{ALL}

Skipped ({N})

# Path Reason
{SKIP}

Phase 5: Display Summary

## Secrets Scan Complete — {CLEAN | FINDINGS | INCOMPLETE}

| Metric | Value |
|--------|-------|
| Files | {N} |
| Unscanned | {N} |
| CRITICAL | {N} |
| HIGH | {N} |
| MEDIUM | {N} |
| LOW | {N} |

Report: {DIR}/report.md (mode 600)

Verdict rule: any UNSCANNED chunk → INCOMPLETE; else any finding → FINDINGS; else CLEAN. CLEAN means "no pattern matched in the current tracked worktree" — not that the repo is safe. Never report a scan with UNSCANNED chunks as clean, and never restate a redacted value in the chat summary either.

Phase 6: Fix Mode

Trigger: --fix arg OR CRITICAL/HIGH findings exist → AskUserQuestion

Option Action
Fix interactively Review each: delete, move to env var, add to .gitignore, skip, mark false positive
Add to .gitignore Append paths
Skip Done