| name | dep-triage |
| description | Reviews dependency-scan findings against repo source to mark each as confirmed (real risk) or false_positive (not exploitable in this codebase context). Use after /dep-scan emits CVE findings; reduces reviewer noise before human review. |
Dependency Triage
Overview
Reads upstream scan findings, evaluates each in the repo's actual usage
context, and emits the same findings augmented with triage_outcome
(confirmed | false_positive) and a one-sentence triage_reason.
This is the triage step — does not run scanners itself. Always
downstream of /dep-scan (or another scan agent emitting the same
finding shape: cve, package, installed_version, fixed_version).
When to use
- After
/dep-scan in a chained agent (needs: [scan-deps]).
- Whenever raw scanner output needs filtering before human review.
- Periodically (e.g., scheduled re-triage to update prior-run outcomes
as the codebase evolves).
Required tools
Read, Grep, Glob — to inspect repo source for package usage
Bash(git diff:*) — to scope which dependencies the PR touched
Skill — invocation only
Write — auto-granted; for stage-output.json
Inputs
upstream/scan-deps/stage-output.json — scan findings from the
upstream scan-deps agent (or compatible scanner)
Triage rubric
For each finding under payload.findings[]:
Confirmed (triage_outcome: confirmed)
Mark confirmed if all three hold:
- The package is imported by repo source under
apps/, libs/, or
packages/.
- The vulnerable code path is plausibly reachable from a request,
user input, or build output.
- The package is NOT exclusively a dev-time dependency.
False positive (triage_outcome: false_positive)
Mark FP if any one of these holds:
- Not a runtime dep. Listed only under
devDependencies /
peerDependencies and not exposed at build/runtime (test fixtures,
type generators, bundler internals).
- Vulnerable function unused. The specific CVE-affected
function/API is not called anywhere in
apps/, libs/, or
packages/.
- Network/host context excludes risk. E.g., DoS vuln on internal
service the project doesn't expose; SSRF on URL the project never
constructs; XSS in a sanitizer the project doesn't reach.
- Already mitigated transitively. Repo's package overrides /
resolutions pin a safe version.
- Out-of-scope target. Scanner flagged a vendored binary,
build artifact, or test fixture not part of the deployed
application.
Conservative tie-break
If you can't establish FP via one of the rules above, mark
confirmed. Better one extra reviewer click than missing a real CVE.
Capture uncertainty in triage_reason so the reviewer can re-evaluate
quickly.
Process
-
Read upstream output. upstream/scan-deps/stage-output.json —
parse payload.findings[].
-
For each finding, follow the rubric:
- Use
Grep to find imports of package in apps/, libs/,
packages/.
- If imports exist,
Read the importing files to check whether the
vulnerable API is actually used.
- Inspect
package.json files to determine dependencies vs
devDependencies (path matters — root package.json vs
workspace package.jsons differ in semantics).
- Consider transitive resolutions: check
pnpm-lock.yaml /
package-lock.json for pinned-safe versions.
-
Augment each finding with two new fields:
triage_outcome: "confirmed" or "false_positive"
triage_reason: one short sentence explaining the call. Reference
the rule number (1-5) for FPs.
-
Recompute summary. Update the top-level summary field to
reflect post-triage counts:
"trivy: <N> raw → <C> confirmed, <F> false_positive".
Output
Same top-level shape as the upstream scan, with two augmentations:
{
"stage": "triage-deps",
"status": "<see below>",
"summary": "trivy: <N> raw → <C> confirmed, <F> false positive",
"payload": {
"findings": [
{
"severity": "high",
"file": "package-lock.json",
"message": "<CVE>: ... (unchanged from scan)",
"suggested_fix": "...",
"cve": "...",
"package": "lodash",
"installed_version": "4.17.20",
"fixed_version": "4.17.21",
"triage_outcome": "confirmed",
"triage_reason": "Imported by libs/foo and the vulnerable .pickBy() is called in src/utils/groupBy.ts."
},
{
"severity": "high",
"file": "package-lock.json",
"message": "<CVE>: ...",
...,
"triage_outcome": "false_positive",
"triage_reason": "FP rule 2: vulnerable .template() function is not called anywhere under apps/ or libs/."
}
],
"triage_summary": {
"raw": <N>,
"confirmed": <C>,
"false_positive": <F>
}
}
}
Status
passed — all findings false_positive (no confirmed real risks)
passed_with_findings — confirmed findings exist but only at
info/low/medium severity
failed — any confirmed finding at high or critical severity
Severity discipline
Do not downgrade severity. A confirmed-but-low-likelihood finding
keeps its original severity (high/critical); the human reviewer
decides whether to accept the risk. Triage filters the noise; it does
not soften the signal.
Heuristics
- Grep first, read second. Don't blindly Read every file in
libs/. A focused Grep for from '<package>' or
require('<package>') scopes the work.
- Multiple CVEs per package are independent. Same package can have
one confirmed CVE (vulnerable function called) and one FP CVE
(different function unused). Evaluate each on its own.
- Capture audit context in
triage_reason. A future scheduled
re-triage can diff outcomes only if the reasoning is preserved.
- Don't fabricate file paths. If you can't find an import of the
affected package in the repo, that's FP rule 1 (not a runtime dep)
or rule 5 (out-of-scope target). Mark it accordingly, don't invent
evidence.
Output preservation vs sticky-comment filtering
Emit all findings (confirmed and FP) under payload.findings[] for
the artifact — the artifact is the audit trail. The renderer (which
posts the sticky PR comment) can filter to confirmed-only for the
human view; the artifact retains the full record.