Dependency Watch — the advisory-driven deps lane, end to end
Paths. analysis-results/… and progress-tracker/… in this skill are the
default workspace layout. They resolve through locations.yaml in
$TRAUST_CONFIG_HOME (docs/setup.md, Storage locations); substitute your
configured roots.
Closes the loop the coverage model calls "new CVE disclosed in a
dependency" (docs/continuous-operations.md): from advisory publication to
an accountable finding — owner, SLA clock, Jira path, UI visibility —
in one pass, the same day. Before this skill the chain's pieces all
existed but stopped at analysis: affected classifications reached
findings.db's impact table and no further (no baseline entry, no
ledger event — the filing gap named 2026-07-28).
Orchestrator-neutral by design: any scheduler that can start a harness
session runs this identically — operator daily session, cron, the
Source Code Intelligence worker, Konflux, or an enterprise-native tool.
Nothing here assumes or requires a particular front-end; every output
lands in the one per-repo baseline + disposition-ledger pipeline that
all reporting surfaces (findings-current, SLA view, team reports,
executive summaries, SCI) already read.
Procedure
Refresh feeds (deterministic):
python3 -m traust.cli feeds fetch — OSV / Go
vulndb / EPSS / CISA KEV pull-through caches. /drift-watch flags
stale caches independently.
Determine the advisory set. With a CVE-ID argument, that CVE
only. Otherwise: advisories newly published/modified since the last
run (--since <date>, default = the previous run's stamp from the
worklist/ledger context). Skip advisories already fully routed
(their artifact exists AND route_impact_findings.py reports
0 targets outstanding — re-runs are idempotent regardless).
Bootstrap / full-baseline mode (bootstrap argument, first run,
quarterly re-baseline, or after portfolio-graph rebuilds): run
python3 traust/harnessing/3-audit/dependency-watch/scripts/fleet_sweep.py
— clone-free fleet inventory (graph depends_on pairs × the OSV
batch API, deduped by advisory display id — CVE alias when present,
else the OSV id itself, so CVE-less malicious-package (MAL-)
advisories are never dropped — the Shai-Hulud class — severity-banded,
with malicious rows forced CRITICAL and sorted first) persisted to
analysis-results/impact/fleet-osv-worklist-<date>.json with a staged
worklist of ready-to-run impact/filing commands. --ecosystem defaults
to all and sweeps every ecosystem present in the graph per-ecosystem
against OSV (Go plus npm, PyPI, Maven, Cargo, RubyGems, NuGet, GitHub
Actions); pass a comma list (e.g. --ecosystem Go,npm) to scope a run
to specific ecosystems. Docker and Helm carry blast-radius edges in the
graph but have no OSV-by-name lane (no ecosystem covers a base-image
tag or a chart version) — they are graph-only and skipped from the OSV
sweep by design, logged rather than silently dropped. Staging discipline is built in: CVEs whose in-range blast radius exceeds
--deep-scan-cap (default 100 repos) get graph-only impact
artifacts (exposure dashboard-visible immediately) with deep scans
left to explicit operator staging; bounded CVEs get deep-scan +
filing commands. Worklist rows carry argv arrays
(impact_argv/route_argv) — module names originate in target
repos' go.mod and versions in OSV, both attacker-influenceable, so
rows are executed as argument vectors exactly as given (one Bash
call per array, no shell string joining, no edits) in order
(severity, then blast radius). Inputs failing the charset gates are
listed under rejected_inputs — review them, never hand-repair
them into commands (assessment 2026-07-31 H1). Commit the sweep
artifact.
Auto-verify malicious hits (--verify, opt-in). OSV flags a
malicious package purely by name+version match, which is blind to npm
aliasing: a manifest line "legacy-swc-helpers": "npm:@swc/helpers@0.4.14"
declares the legitimate @swc/helpers under a local alias name, not
the malicious registry package — an OSV name-match false positive that
historically only manual source inspection caught. With --verify, each
malicious worklist hit is checked against its recorded manifest
provenance BEFORE emission (harnessing/3-audit/dependency-watch/scripts/verify_dep_provenance.py): the exact
source manifest is fetched and re-parsed (npm aliases resolved to their
real package), and each row is annotated verification: {status, evidence, checks}. A positively refuted hit (present only as an alias
to a different package, or genuinely absent from every recorded manifest,
with no confirmation anywhere) is moved out of the actionable worklist
into refuted_findings with its reason — an alias/false-positive is
auto-dropped with a paper trail, never silently and never as a live
finding. Confirmed hits and unverifiable ones (no provenance recorded,
or fetch/parse failed) stay actionable (fail-safe — a finding is never
dropped for lack of data). --verify is off by default (preserves
the deterministic offline sweep); it adds gh api repos/<org>/<name>/contents/<path> raw fetches — the sweep's only
network egress, routed through the same vetted argv-list fetcher the
portfolio-graph build uses (no shell string, no secret in argv). Enable
it when a run surfaces malicious hits worth confirming before they page
an owner.
Blast radius + reachability per advisory affecting a module the
portfolio uses: run /impact-analysis (or its script directly):
python3 -m traust.cli impact analyze <CVE> --module <m> --vulnerable-range '<r>' --fixed-version <v> ... —
writes analysis-results/impact/<cve>-impact-analysis.json.
Precondition: a fresh portfolio graph — the graph is the blast
radius; check /drift-watch's portfolio-graph item first and say
so if stale rather than silently analyzing against old topology.
Severity from the advisory, never invented: take severity from
the OSV/advisory record (CVSS mapping: 9.0+ critical / 7.0+ high /
4.0+ medium / else low), noting KEV listing in the rationale if
present (KEV escalates dispatch urgency, not severity).
File (deterministic, idempotent):
python3 -m traust.cli route impact-findings analysis-results/impact/<cve>-impact-analysis.json --severity <s> [--include-likely] [--dry-run] — for every affected repo with a
baseline: campaign-ID finding carried on its ledger event (event.finding, contracts >= 0.4.4; the baseline is never written — gate A15)
(origin: impact-analysis, category: supply-chain,
validation_status: not_verified), claim hash pinned, ledger birth
event (source.type: impact_report, machine-static class 3),
cumulative *-findings-current.{json,md} rebuilt. Repos without a
baseline are listed, never silently dropped — they are unaudited
inventory, which is a census matter, not a filing one.
Report what was routed: per CVE — affected/filed/already-filed/
no-baseline counts and the finding IDs, so the run's output is
reviewable at a glance. Criticals routed here inherit the standing
SLA (validated-or-blocked ≤7d) like any other critical.
Constraints
- Deterministic steps route and record; the affectedness judgment is
/impact-analysis's (evidence ladder) and downstream adjudication is
/triage and /validate-findings' — this skill never overrides either.
- Severity comes from the advisory record; when no CVSS exists, file at
medium with the uncertainty named in the description rather than
guessing high or suppressing.
- Never file
likely_affected by default; --include-likely is an
explicit operator choice (precision posture).
Integrations
Consumes: advisory feed caches (producer: python3 -m traust.cli feeds fetch);
analysis-results/impact/*-impact-analysis.json (producer:
/impact-analysis — python3 -m traust.cli impact analyze); portfolio graph
(producer: /portfolio-graph; freshness gated via /drift-watch);
findings.db repos baselines (producer: python3 -m traust.cli corpus findings-db).
Produces: analysis-results/impact/fleet-osv-worklist-<date>.json —
terminal beyond this skill (a staging inventory consumed by the same
run's worklist execution and by operator review of deep-scan staging;
no downstream skill reads it; carries a refuted_findings list of
malicious hits auto-refuted against their source manifests under
--verify, via harnessing/3-audit/dependency-watch/scripts/verify_dep_provenance.py); baseline appends +
disposition-ledger birth events via
python3 -m traust.cli route impact-findings (consumers: /track-findings
cumulative rebuild, /triage adjudication, /verify-remediation
(resolution), owner routing / SLA view / team reports /
/executive-summary-findings / /file-security-defect, and any ledger
front-end such as Source Code Intelligence). The per-run routing report
is terminal (human review of the day's filings).
Related: the change-triggered manifest path (rule 3b in the rescan
router) covers dependency changes on our side; this skill covers
advisory arrival on their side. The router's cve event lane
(rescan-events.jsonl) queues ad-hoc CVE requests into the same chain.
1---2name: dependency-watch3description: Use on the daily continuous-operations cadence, or when the user asks to "check for new dependency CVEs", "run the deps lane", "watch dependencies", or "file dependency vulnerabilities" — runs the advisory-driven dependency chain end to end: refresh the vulnerability feeds, sweep new advisories against the audited fleet, run /impact-analysis reachability on hits, and route affected repos' findings into their disposition ledgers as event-carried findings (gate A15 — the baseline is never written), so a newly disclosed dependency CVE becomes an owned, SLA-clocked finding the same day. Orchestrator-neutral — invocable identically from an operator session, cron, Source Code Intelligence, or any enterprise scheduler.4---56# Dependency Watch — the advisory-driven deps lane, end to end78> **Paths.** `analysis-results/…` and `progress-tracker/…` in this skill are the9> default workspace layout. They resolve through `locations.yaml` in10> `$TRAUST_CONFIG_HOME` (`docs/setup.md`, Storage locations); substitute your11> configured roots.121314Closes the loop the coverage model calls "new CVE disclosed in a15dependency" (docs/continuous-operations.md): from advisory publication to16an **accountable finding** — owner, SLA clock, Jira path, UI visibility —17in one pass, the same day. Before this skill the chain's pieces all18existed but stopped at analysis: `affected` classifications reached19findings.db's `impact` table and no further (no baseline entry, no20ledger event — the filing gap named 2026-07-28).2122Orchestrator-neutral by design: any scheduler that can start a harness23session runs this identically — operator daily session, cron, the24Source Code Intelligence worker, Konflux, or an enterprise-native tool.25Nothing here assumes or requires a particular front-end; every output26lands in the one per-repo baseline + disposition-ledger pipeline that27all reporting surfaces (findings-current, SLA view, team reports,28executive summaries, SCI) already read.2930## Procedure31321. **Refresh feeds** (deterministic):33 `python3 -m traust.cli feeds fetch` — OSV / Go34 vulndb / EPSS / CISA KEV pull-through caches. `/drift-watch` flags35 stale caches independently.36372. **Determine the advisory set.** With a `CVE-ID` argument, that CVE38 only. Otherwise: advisories newly published/modified since the last39 run (`--since <date>`, default = the previous run's stamp from the40 worklist/ledger context). Skip advisories already fully routed41 (their artifact exists AND `route_impact_findings.py` reports42 0 targets outstanding — re-runs are idempotent regardless).4344 **Bootstrap / full-baseline mode (`bootstrap` argument, first run,45 quarterly re-baseline, or after portfolio-graph rebuilds):** run46 `python3 traust/harnessing/3-audit/dependency-watch/scripts/fleet_sweep.py`47 — clone-free fleet inventory (graph `depends_on` pairs × the OSV48 batch API, deduped by advisory display id — CVE alias when present,49 else the OSV id itself, so CVE-less malicious-package (`MAL-`)50 advisories are never dropped — the Shai-Hulud class — severity-banded,51 with `malicious` rows forced CRITICAL and sorted first) persisted to52 `analysis-results/impact/fleet-osv-worklist-<date>.json` with a staged53 worklist of ready-to-run impact/filing commands. `--ecosystem` defaults54 to `all` and sweeps every ecosystem present in the graph per-ecosystem55 against OSV (Go plus npm, PyPI, Maven, Cargo, RubyGems, NuGet, GitHub56 Actions); pass a comma list (e.g. `--ecosystem Go,npm`) to scope a run57 to specific ecosystems. Docker and Helm carry blast-radius edges in the58 graph but have no OSV-by-name lane (no ecosystem covers a base-image59 tag or a chart version) — they are graph-only and skipped from the OSV60 sweep by design, logged rather than silently dropped. Staging discipline is built in: CVEs whose in-range blast radius exceeds61 `--deep-scan-cap` (default 100 repos) get graph-only impact62 artifacts (exposure dashboard-visible immediately) with deep scans63 left to explicit operator staging; bounded CVEs get deep-scan +64 filing commands. Worklist rows carry **argv arrays**65 (`impact_argv`/`route_argv`) — module names originate in target66 repos' go.mod and versions in OSV, both attacker-influenceable, so67 rows are executed as argument vectors exactly as given (one Bash68 call per array, no shell string joining, no edits) in order69 (severity, then blast radius). Inputs failing the charset gates are70 listed under `rejected_inputs` — review them, never hand-repair71 them into commands (assessment 2026-07-31 H1). Commit the sweep72 artifact.7374 **Auto-verify malicious hits (`--verify`, opt-in).** OSV flags a75 malicious package purely by name+version match, which is blind to npm76 aliasing: a manifest line `"legacy-swc-helpers": "npm:@swc/helpers@0.4.14"`77 declares the *legitimate* `@swc/helpers` under a local alias name, not78 the malicious registry package — an OSV name-match false positive that79 historically only manual source inspection caught. With `--verify`, each80 `malicious` worklist hit is checked against its recorded `manifest`81 provenance BEFORE emission (`harnessing/3-audit/dependency-watch/scripts/verify_dep_provenance.py`): the exact82 source manifest is fetched and re-parsed (npm aliases resolved to their83 real package), and each row is annotated `verification: {status,84 evidence, checks}`. A **positively refuted** hit (present only as an alias85 to a different package, or genuinely absent from every recorded manifest,86 with no confirmation anywhere) is moved out of the actionable worklist87 into `refuted_findings` with its reason — an alias/false-positive is88 auto-dropped *with a paper trail*, never silently and never as a live89 finding. Confirmed hits and `unverifiable` ones (no provenance recorded,90 or fetch/parse failed) stay actionable (fail-safe — a finding is never91 dropped for lack of data). `--verify` is **off by default** (preserves92 the deterministic offline sweep); it adds `gh api93 repos/<org>/<name>/contents/<path>` raw fetches — the sweep's only94 network egress, routed through the same vetted argv-list fetcher the95 portfolio-graph build uses (no shell string, no secret in argv). Enable96 it when a run surfaces malicious hits worth confirming before they page97 an owner.98993. **Blast radius + reachability** per advisory affecting a module the100 portfolio uses: run `/impact-analysis` (or its script directly):101 `python3 -m traust.cli impact analyze <CVE>102 --module <m> --vulnerable-range '<r>' --fixed-version <v> ...` —103 writes `analysis-results/impact/<cve>-impact-analysis.json`.104 **Precondition:** a fresh portfolio graph — the graph is the blast105 radius; check `/drift-watch`'s `portfolio-graph` item first and say106 so if stale rather than silently analyzing against old topology.1071084. **Severity from the advisory, never invented:** take severity from109 the OSV/advisory record (CVSS mapping: 9.0+ critical / 7.0+ high /110 4.0+ medium / else low), noting KEV listing in the rationale if111 present (KEV escalates dispatch urgency, not severity).1121135. **File** (deterministic, idempotent):114 `python3 -m traust.cli route impact-findings115 analysis-results/impact/<cve>-impact-analysis.json --severity <s>116 [--include-likely] [--dry-run]` — for every `affected` repo with a117 baseline: campaign-ID finding carried on its ledger event (`event.finding`, contracts >= 0.4.4; the baseline is never written — gate A15)118 (`origin: impact-analysis`, `category: supply-chain`,119 `validation_status: not_verified`), claim hash pinned, ledger birth120 event (`source.type: impact_report`, machine-static class 3),121 cumulative `*-findings-current.{json,md}` rebuilt. Repos without a122 baseline are listed, never silently dropped — they are unaudited123 inventory, which is a census matter, not a filing one.1241256. **Report** what was routed: per CVE — affected/filed/already-filed/126 no-baseline counts and the finding IDs, so the run's output is127 reviewable at a glance. Criticals routed here inherit the standing128 SLA (validated-or-blocked ≤7d) like any other critical.129130## Constraints131132- Deterministic steps route and record; **the affectedness judgment is133 /impact-analysis's** (evidence ladder) and downstream adjudication is134 /triage and /validate-findings' — this skill never overrides either.135- Severity comes from the advisory record; when no CVSS exists, file at136 `medium` with the uncertainty named in the description rather than137 guessing high or suppressing.138- Never file `likely_affected` by default; `--include-likely` is an139 explicit operator choice (precision posture).140141## Integrations142143Consumes: advisory feed caches (producer: python3 -m traust.cli feeds fetch);144`analysis-results/impact/*-impact-analysis.json` (producer:145`/impact-analysis` — python3 -m traust.cli impact analyze); portfolio graph146(producer: `/portfolio-graph`; freshness gated via `/drift-watch`);147findings.db `repos` baselines (producer: python3 -m traust.cli corpus findings-db).148149Produces: `analysis-results/impact/fleet-osv-worklist-<date>.json` —150terminal beyond this skill (a staging inventory consumed by the same151run's worklist execution and by operator review of deep-scan staging;152no downstream skill reads it; carries a `refuted_findings` list of153malicious hits auto-refuted against their source manifests under154`--verify`, via `harnessing/3-audit/dependency-watch/scripts/verify_dep_provenance.py`); baseline appends +155disposition-ledger birth events via156python3 -m traust.cli route impact-findings (consumers: `/track-findings`157cumulative rebuild, `/triage` adjudication, `/verify-remediation`158(resolution), owner routing / SLA view / team reports /159`/executive-summary-findings` / `/file-security-defect`, and any ledger160front-end such as Source Code Intelligence). The per-run routing report161is terminal (human review of the day's filings).162163Related: the change-triggered manifest path (rule 3b in the rescan164router) covers dependency *changes on our side*; this skill covers165advisory arrival on *their* side. The router's `cve` event lane166(`rescan-events.jsonl`) queues ad-hoc CVE requests into the same chain.