---
name: secret-finding-triage
description: "Use after a secret scanner has already flagged a match — decides whether the hit is a real leaked credential or a false positive (test fixture, documented example, placeholder), then sets a rotation-urgency tier from liveness, blast radius, and where it was exposed. Applies the verdict taxonomy and urgency ladder a model does not reach for on its own, including the rule that a secret removed from current code but still in git history must still be rotated. Do NOT use for the initial scan/detection (see secret-scanner), for building a pre-commit hook, or for executing the rotation."
metadata:
  decimalai-skill-id: "271237e1-9e57-458f-8045-198464cf8745"
  decimalai-version-id: "4ca176a0-e334-46e6-8334-c164d308a312"
  decimalai-version: "1"
  decimalai-body-sha256: "3e725d00a039411ff958d0a08da2fce10a887e91a7914806819a1ec7b92704fc"
  decimalai-name: "secret-finding-triage"
  decimalai-format: "1"
  decimalai-invocation: "model"
  decimalai-source: "https://app.decimal.ai/s/secret-finding-triage@1/SKILL.md"
---
# Secret Finding Triage

A scanner has flagged a candidate secret. Detection is done; the questions now are two, and they are independent:

- **Axis A — is it real?** Real leaked credential, or a false positive (test fixture, documented example, placeholder)?
- **Axis B — how fast must it rotate?** A rotation-urgency tier driven by liveness, blast radius, and where the secret was exposed.

Answer both on every finding. A high-entropy string is not automatically a leak, and a "real" verdict does not automatically mean rotate-this-second — the two axes are set separately.

## When to Activate

Activate when a scanner, alert, or reviewer has **already produced a match** and the decision now is disposition and priority:

- Deciding whether a scanner hit is a genuine leak or a placeholder/test value
- Triaging a GitHub / GitLab secret-scanning alert
- Ranking several confirmed hits by which to rotate first

Do **not** activate for the detection scan itself, for writing prevention tooling, or for carrying out the rotation.

## Axis A — Real leak vs false positive

Weigh the signals below. Any single strong false-positive signal (known-dummy value, unmistakable placeholder, test-mode marker in a test path) is usually decisive; otherwise a real-leak signal wins ties, because the cost of missing a live credential dwarfs the cost of a false alarm.

**Points toward REAL (treat as a live credential):**

- **Live/production marker in the token itself** — `sk_live_`, `rk_live_`, `pk_live_`, `AKIA…` outside docs, a connection URI with a real production hostname. Liveness in the value outranks path context.
- **Structurally valid high-entropy value** — Shannon entropy above ~4.0 over the random segment, correct length and charset for the provider, no repeated-character runs.
- **Sits next to real config** — assigned to `API_KEY`, `PASSWORD`, `SECRET`, `Authorization: Bearer`, or inside `.env`, `config/prod.*`, deployment manifests, CI secrets.
- **The value's own history** — it appeared in a real commit by a real author, not a fixture generator.

**Points toward FALSE POSITIVE (no live credential):**

- **Known public/dummy value** — the well-documented examples that ship in vendor docs and always trip scanners:
  - AWS: `AKIAIOSFODNN7EXAMPLE`, secret `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`
  - Stripe test key prefixes `sk_test_` / `pk_test_` / `rk_test_`
  - Any value ending in `EXAMPLE`, `SAMPLE`, `DUMMY`, `FAKE`, `XXXX…`
- **Placeholder shape** — `your_api_key_here`, `<token>`, `changeme`, `REPLACE_ME`, `...`, or a run of one repeated character (low entropy).
- **Path/context says fixture** — `test/`, `tests/`, `__tests__/`, `*_test.*`, `fixtures/`, `.env.example`, `.env.sample`, docs (`*.md`, `docs/`), or a lockfile.
- **Non-live by construction** — a JWT with `"alg":"none"`, an already-expired token, a key the provider has already revoked.

If real-leak and false-positive signals conflict (e.g. a real-looking key in a test file), **default to REAL and rotate** — a leaked credential parked in a test directory is still leaked.

## Axis B — Rotation urgency

Set the tier from **liveness × blast radius × exposure reach**. Exposure reach is often the dominant term: anything pushed to a shared or public remote must be assumed already harvested by scrapers.

| Tier | Meaning | Trigger |
|------|---------|---------|
| **P0 — rotate now** | Assume active compromise | Real credential that is live AND high blast radius (cloud root/admin key, live payment key, prod DB URI, org-wide token) **OR** any real credential exposed in a **public** repo/remote — regardless of privilege |
| **P1 — rotate today** | Real, contained exposure | Real credential in a **private** repo/remote with limited access, moderate privilege |
| **P2 — rotate soon** | Real but low leverage | Real but read-only / narrowly scoped / short-lived; or exposure window is closed but the value **still exists in git history** |
| **P3 — no rotation** | Confirmed false positive | Documented example, placeholder, test-mode key, or already-revoked/expired value |

Two rules the reflex answer gets wrong:

1. **History counts.** Deleting the secret from the current file does **not** close the finding. If the value is still reachable via `git log`/`git history`, it must still be rotated (at least P2, higher if live+public). "I already removed it" is not remediation.
2. **Public exposure escalates everything to P0.** Once a real secret lands on a public remote, treat it as compromised even if it looks low-privilege — the rotation is non-negotiable and immediate.

## Output

For each finding, state the two axes and the one-line reason:

```
Finding: <provider> match in <file>:<line>
Verdict: REAL_LEAK | FALSE_POSITIVE
Urgency: P0 | P1 | P2 | P3
Why:     <the single signal that set the verdict + the factor that set the tier>
Action:  <rotate now / rotate today / rotate soon / no action> [+ purge from history if applicable]
```

Rank a multi-finding report P0 → P3 so the reader rotates the dangerous ones first.

## Edge cases

- **Real key in a test file** — REAL, not P3. Path context lowers suspicion but never overrides a live, high-entropy value.
- **Placeholder that is also high-entropy** — some generators emit random-looking but fake values; a matching known-dummy value or an explicit `.example`/fixture context still makes it FALSE_POSITIVE.
- **Restricted/read-only token** — REAL, but caps urgency at P2 unless it is public (then P0).
- **Rotated-but-not-purged** — the account is safe going forward, but if the old value persists in history and the provider does not hard-revoke on rotation, still purge; note residual risk.
- **Encoded secret** (base64 in a JWT/K8s manifest) — decode before judging entropy; the encoding is not protection.
