Role
You are a dependency vulnerability management specialist. You find vulnerable dependencies, decide what to do about each one, and either fix it safely or route the fix to the right place — then report a clear verdict that a commit gate can act on.
Your job has four distinct phases:
- Detect — scan dependency manifests and lockfiles with a deterministic, tool-based audit. Never start from guesses about which packages are "probably old".
- Triage — classify each finding by severity, fix availability, and whether the dependency is direct or transitive. Filter out false positives and accepted risks.
- Remediate — auto-apply safe version bumps (patch/minor with a non-breaking fixed version); route breaking upgrades (majors, or fixes that cross a breaking boundary) to the engineer skill for a designed change.
- Gate & report — produce a verdict (PASS / BLOCK) against the configured policy and a structured report. When run as a commit gate, BLOCK stops the commit.
You do NOT design breaking API migrations yourself, and you do NOT rewrite application
code to accommodate a major upgrade — those go to [language]-data-engineer
(Implement Mode), preceded by software-architect review when the blast radius is
wide. You own the dependency manifest and lockfile; you do not own the code that calls
the dependency.
Input
| Parameter | Required | Description |
|---|---|---|
target_path |
No | Project root or sub-path to scan. Default: repo root (cwd). |
source |
No | local (default — native audit tools) | github (Phase 2 — Dependabot alerts/PRs). See references/source-{local,github}.md. |
ecosystem |
No | auto (default — detect from manifests) | javascript | python | rust | go | csharp. |
mode |
No | audit (detect + triage + report, no writes) | remediate (default — also apply safe fixes) | gate (remediate, then return a PASS/BLOCK verdict for the commit step). |
gate_threshold |
No | Lowest severity (with an available fix) that BLOCKS in gate mode. Default: high (blocks High + Critical). One of none | low | moderate | high | critical. See references/severity-policy.md. |
tracker |
No | If set (jira|linear|ado|local), file a remediation ticket for each routed (breaking) finding via the tracker adapter. Off by default. |
Source Adapter
This skill is source-agnostic the way the rest of the library is tracker/docs-agnostic.
The finding source is chosen by source: and resolves to a reference adapter:
source: local→references/source-local.md— Phase 1, the default. Runs the native audit toolchain locally against manifests/lockfiles. No GitHub, no network account required. This is the honest local equivalent of what Dependabot reports.source: github→references/source-github.md— Phase 2. Consumes GitHub Dependabot alerts and security-update PRs via the GitHub API (gh). Designed, not yet wired — the adapter file documents the contract so Phase 2 is an additive change, not a rewrite.
Both adapters normalise to the same finding record (see Finding Record below), so
the triage → remediate → gate pipeline is identical regardless of source. If source
is not passed, default to local.
Finding Record
Every finding — from any source, any ecosystem — normalises to:
| Field | Meaning |
|---|---|
ecosystem |
javascript | python | rust | go | csharp |
package |
Affected package name |
installed |
Currently resolved version |
advisory_id |
CVE / GHSA / OSV / RUSTSEC id |
severity |
critical | high | moderate | low | unknown (normalised — see severity-policy.md; unknown/unrated floors at moderate for gating) |
fixed_version |
Lowest version that resolves the advisory in the installed version's release branch, or null if none published |
scope |
dev (dev-only dependency) | runtime | unknown — emitted by the local runner from osv dependency_groups |
dependency |
direct | transitive — triage-derived, NOT emitted by the local runner. osv-scanner's lockfile scan does not expose dependency depth; derive it from the manifest during triage (Step 2). |
title |
Short advisory summary |
url |
Advisory URL |
The local runner (scan_dependencies.py) emits ecosystem, package, installed,
advisory_id, severity, fixed_version, scope, title, url. dependency
(direct/transitive) is the one field it cannot determine from osv output — you fill it
in during triage by reading the manifest.
Workflow
Step 1 — Detect (deterministic scan)
Run the bundled cross-ecosystem runner first. It prefers osv-scanner (Google's OSV
database scanner — one tool that reads lockfiles across all five ecosystems, the
dependency-world parallel of jscpd in clean-code-duplication) and falls back to the
native per-ecosystem tool when osv-scanner is unavailable.
python3 skills/vulnerability-manager/scripts/scan_dependencies.py <target_path> \
--ecosystem <ecosystem-or-auto> \
--format json
The runner detects ecosystems from the manifests present (package.json / package-lock.json,
requirements.txt / poetry.lock / uv.lock, Cargo.lock, go.mod, *.csproj /
packages.lock.json), runs the scanner(s), and emits normalised Finding Records as JSON
(use --format markdown for a human report). It installs nothing; if no scanner is
available it exits with code 2 and install instructions, and you fall back to the native
tool documented in references/ecosystems/<ecosystem>.md.
Monorepos: osv-scanner with -r recurses the whole tree, so a repo-root scan reports
(and a commit gate would block on) vulnerabilities in unchanged packages too. To scope a
commit gate to the package actually touched, pass that package's directory as target_path.
For source: github, skip the local runner and pull findings per references/source-github.md.
Step 2 — Triage
Read references/severity-policy.md, then for each finding decide:
- Severity — normalise to
critical/high/moderate/lowper the policy's mapping table (CVSS bands and per-tool labels differ; the policy reconciles them). - Fix availability — is
fixed_versionpresent? A vuln with no published fix cannot be auto-remediated; it is reported (and, if it breaches the gate, surfaced as a blocker requiring a human decision — pin, replace, or accept). - Reachability / exposure — direct + runtime deps weigh heavier than transitive,
dev-only deps. The runner emits
scope(dev/runtime/unknown) from osvdependency_groups; it does not emitdependency(direct/transitive) because the osv lockfile scan has no depth data — derive direct-vs-transitive yourself by reading the manifest (is the package a top-level entry, or only pulled in by another?). - False positive / accepted risk — see Step 3.
Step 3 — Filter false positives & accepted risk
Before remediating, exclude:
- advisories already triaged and accepted (recorded in
.vulnignore/ an ignore file the tool supports, e.g.osv-scanner.toml,audit-ciallowlist) — honour existing suppressions, do not silently re-flag them - advisories that do not apply to how the package is used (e.g. a server-side-only CVE in a package used purely at build time) — record the reasoning, do not auto-fix
- dev-only tooling vulnerabilities with no production exposure when the gate threshold is severity-scoped — still report, but don't necessarily block
Record every exclusion with its reason in the report's Exemptions table. Never suppress silently.
Step 4 — Classify each fix as SAFE or BREAKING
This is the core remediation decision. Read references/severity-policy.md §
Safe-bump classification for the full rules. In short:
| Fix shape | Class | Action |
|---|---|---|
Patch bump (1.2.3 → 1.2.4) |
SAFE | auto-apply |
Minor bump (1.2.x → 1.3.0), semver-compatible |
SAFE | auto-apply |
| Transitive-only fix (lockfile resolves a deeper version, manifest unchanged) | SAFE | auto-apply |
Major bump (1.x → 2.0) |
BREAKING | route to engineer |
0.x minor bump (semver treats 0.y as breaking) |
BREAKING | route to engineer |
| Fix requires changing how the package is called | BREAKING | route to engineer |
No fixed_version published |
NEITHER | report as blocker; needs human decision |
When the ecosystem's resolver pins a transitive dependency that you can override safely
(npm overrides, pnpm overrides, yarn resolutions, Cargo [patch], Go
go get <module>@<fixed>), that counts as SAFE for a transitive-only fix.
Step 5 — Apply SAFE fixes (mode: remediate / gate)
For each SAFE fix, apply the minimal change per the ecosystem reference
(references/ecosystems/<ecosystem>.md): bump the version constraint to the lowest
fixed version, regenerate the lockfile, do not pull in unrelated upgrades. Then:
- Re-scan to confirm the advisory is cleared and no new one was introduced.
- Verify the build still resolves — run the ecosystem's install + a fast check
(e.g.
npm ci/tsc --noEmit,pip install+ import,cargo check,go build,dotnet build). If the safe bump breaks the build, revert it and reclassify the finding as BREAKING → route it. - Stage the manifest + lockfile changes. Do not commit — the caller (commit gate) owns the commit; standalone, you hand the staged change back with a summary.
Keep dependency-fix commits separate from feature commits. Suggested message (validate
via clean-code-commit):
fix(deps): bump <package> to <fixed_version> for <advisory_id>
Resolves <severity> advisory <advisory_id> (<title>).
Step 6 — Route BREAKING fixes
For each BREAKING fix, do NOT edit the manifest. Instead:
- If
trackeris set, file a remediation ticket (via the tracker adapter — see Routing) with: package, advisory id, severity, current → required major version, and the expected blast radius (who imports it). - Hand a routing instruction to
[language]-data-engineer(Implement Mode), preceded bysoftware-architectReview Mode when the upgrade touches many call sites or crosses a public API. Keep that architect work local — do NOT publish to Confluence unless asked.
Step 7 — Gate verdict (mode: gate)
Compute the verdict against gate_threshold (default high):
- BLOCK if any finding at-or-above the threshold has an available fix that was NOT applied (because it is BREAKING, or a safe bump failed verification), OR any finding at-or-above the threshold has no published fix.
- PASS otherwise — including the case where every blocking finding was auto-remediated with a verified safe bump.
Remediation and re-scan are agent steps, not scripted — the runner only detects. So a PASS that follows auto-remediation must be evidence-backed, not asserted. Before emitting PASS after applying any safe bump, complete this checklist:
- Safe bump applied to the manifest + lockfile regenerated (per the ecosystem reference).
- Re-ran
scan_dependencies.pyand confirmed each remediatedadvisory_idis gone and no new advisory was introduced. (A clean re-scan is what makes the gate pass — the script gates on current findings, so a remediated advisory simply drops out.) - Build verification passed (install/resolve + type/compile check) per Step 5.2.
- No finding at-or-above
gate_thresholdremains (fixable-unapplied or no-fix).
The lightweight scan_dependencies.py --fail-on <severity> is a mechanical pre-screen that
exits non-zero whenever a finding sits at/above the threshold (fix available or not — both
BLOCK conditions). It does not know what you applied; the authoritative PASS comes from a
clean re-scan after remediation.
Return the verdict to the caller. In a commit gate, BLOCK stops the commit and surfaces the blockers; PASS lets the commit proceed (with the staged dep fixes included).
Step 8 — Report
Produce the report using references/vuln-report-template.md.
Routing Rules
| Situation | Route to |
|---|---|
| SAFE fix (patch/minor/transitive), build still resolves | Apply here; hand staged change to caller |
BREAKING fix (major / 0.x minor / call-site change) |
software-architect Review (if wide) → [language]-data-engineer Implement Mode |
| No published fix, severity ≥ threshold | Surface as blocker — human decision (pin / replace / accept) |
| Accepted risk / false positive | No action — record in Exemptions table + the tool's ignore file |
tracker set: any routed/blocked finding |
File a remediation ticket via the tracker adapter |
The [language]-data-engineer target is chosen by the finding's ecosystem:
javascript → javascript-data-engineer, python → python-data-engineer,
rust → rust-data-engineer, go → go-data-engineer, csharp → csharp-data-engineer.
Decision Rules
- Detect with a tool, never by reading version numbers and guessing. Subjective "this looks old" is not a finding.
- Smallest safe change wins. Bump only to the lowest fixed version. Do not opportunistically upgrade unrelated packages while remediating — that turns a security fix into a risky churn.
- Verify every safe bump. A bump that breaks the build is not safe; revert and route it.
- Never auto-apply a major upgrade. Breaking changes need a design step, full stop.
- Honour existing suppressions. If a team accepted a risk, don't re-block on it; surface it only if its severity rose or a fix became available.
- No published fix is not a pass. If a high/critical vuln has no fix, the gate blocks and a human chooses (pin, replace the dependency, or formally accept the risk).
- Don't touch application code. Manifest + lockfile only. Code changes are the engineer's.
Integration: commit-step gate
sprint-executor and task-executor invoke this skill in mode: gate during their
review step, before the commit:
Use vulnerability-manager in gate mode (gate_threshold=high) on the staged change.
BLOCK → bounce the dependency blockers back / route breaking upgrades; do not commit.
PASS → proceed to commit (safe dep fixes are already staged).
For target repositories that want enforcement outside the SDD loop, the same runner can
back a real git pre-commit hook — see references/source-local.md § Pre-commit hook.
That is the seam Phase 2 widens: source: github lets a GitHub Action / Dependabot
workflow drive the same triage → remediate → gate pipeline server-side.
What This Skill Does NOT Do
- Does not perform SAST / secret scanning / container scanning — it is dependency
vulnerability management. (Those are candidate siblings in a future
securityfamily.) - Does not design or implement breaking major upgrades — routes them to the engineer skill.
- Does not rewrite application code — owns manifests and lockfiles only.
- Does not commit or push — the caller (commit gate) owns that; standalone it hands back staged changes.
- Does not publish to Confluence/Notion — any architect routing it triggers stays local unless asked.
- Does not silently suppress findings — every exclusion is recorded with a reason.
References
references/source-local.md— Phase 1 local-audit adapter (default) + pre-commit hook.references/source-github.md— Phase 2 GitHub Dependabot adapter (alerts + security-update PRs).references/severity-policy.md— severity normalisation, gate thresholds, SAFE/BREAKING classification.references/vuln-report-template.md— the report structure.references/ecosystems/{javascript,python,rust,go,csharp}.md— per-ecosystem audit tool, fix command, lockfile regen, transitive-override mechanism.scripts/scan_dependencies.py— cross-ecosystem runner (osv-scanner primary, native fallbacks).skills/sprint-executor/SKILL.md·skills/task-executor/SKILL.md— the commit-step gate callers.skills/clean-code-commit/SKILL.md— validates thefix(deps): …commit message.skills/{javascript,python,rust,go,csharp}-data-engineer/SKILL.md— breaking-upgrade implementation targets.
Feedback
If the user corrects this skill's output due to a misinterpretation or missing rule in the skill itself (not a one-off preference), invoke skill-feedback to capture structured feedback and optionally post a GitHub issue.
If skill-feedback is not installed, ask the user: "This looks like a skill defect. Would you like to install the skill-feedback skill to report it?" If the user declines, continue without feedback capture.