Skip to content

Commit c21f260

Browse files
rhyslbwclaude
andcommitted
fix: patch dependency vulnerabilities (critical→low) with audit & skill
Closes open Dependabot/npm advisory alerts across all severities using a low-risk, prefer-relock-over-resolution strategy, and adds the supporting documentation. Remediation: - Critical/high/medium/low transitive deps relocked in-range (no manifest change) where their existing semver ranges already permit the patched version; convict bumped in packages/e2e; a single protobufjs resolution used as a last resort (Trezor pins it exactly, no shippable release reaches the fix). Major parent-bumps and no-upstream-fix packages deferred to issues #1701#1708. - Validated: yarn build across all 21 workspaces; hardware-trezor 80/80; crypto 76/76. axios deferred (consumer-facing HTTP client, needs CI). Documentation (docs/security/dependency-vulnerability-audit-2026-06-19.md): - per-dependency audit (vuln status, license, prod/dev + consumer reachability, provenance) of all 129 updated dependencies - per-tier dependency blast-radius trees - supply-chain provenance & publisher-continuity (npm audit signatures 100%; 36 SLSA-provenance; 24 reviewed maintainer transitions) - external-DB cross-reference (OSV.dev, CISA KEV: 0 actively-exploited) Adds the reusable dependency-vulnerability-remediation skill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2e00296 commit c21f260

5 files changed

Lines changed: 1598 additions & 758 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
---
2+
name: dependency-vulnerability-remediation
3+
description: >-
4+
Remediate Dependabot / npm advisory vulnerabilities in this Yarn-3 monorepo with a low-risk,
5+
prefer-relock-over-resolution strategy, then produce a per-dependency security audit
6+
(vulnerability status, license, prod/dev + consumer reachability, blast radius, and
7+
supply-chain provenance/publisher-continuity). Use when asked to patch security alerts,
8+
bump vulnerable dependencies, assess dependency blast radius, or do a supply-chain review.
9+
---
10+
11+
# Dependency vulnerability remediation & supply-chain audit
12+
13+
A repeatable process for closing dependency vulnerabilities here with minimal blast radius,
14+
and for producing the audit artifacts that make the PR easy to approve with confidence.
15+
16+
Environment: **Yarn 3 (berry), `nodeLinker: node-modules`**, workspaces under `packages/*`,
17+
published packages are the non-`private` `@cardano-sdk/*`. Generated docs go to `typedoc/`;
18+
`docs/` holds tracked documentation (security audits live in `docs/security/`).
19+
20+
## Principles (in priority order)
21+
22+
1. **In-range relock > manifest bump > resolution.** If a package's existing semver range
23+
already permits the patched version, just relock — it's semver-safe by definition and needs
24+
no manifest change. Resolutions are a **last resort**, used only when an upstream pins a
25+
vulnerable version exactly and no shippable release reaches the fix.
26+
2. **Prioritise by consumer impact.** What ships in the production closure of published
27+
packages matters most; dev/build-only deps are real supply-chain surface but rank lower.
28+
3. **Defer, don't force.** Major parent-bumps, scoped multi-major resolutions, and
29+
no-upstream-fix packages go to tracked issues with reasoning — never jammed into a low-risk patch.
30+
4. **Validate every batch** with `yarn build` (all workspaces) + targeted suites for packages
31+
whose runtime deps moved.
32+
5. **Never trust a "fix" blindly.** Check provenance — a deprecated package republished under a
33+
new version (e.g. vm2) or a publisher-account change is a supply-chain signal, not a green light.
34+
35+
## Process
36+
37+
### 1. Triage alerts by severity
38+
```bash
39+
gh api repos/<org>/<repo>/dependabot/alerts -X GET -f state=open -f severity=critical --paginate \
40+
| jq -r 'group_by(.dependency.package.name)[] | "\(.[0].dependency.package.name)\t[\(.[0].dependency.scope)]\tx\(length)\tpatched>=\([.[].security_vulnerability.first_patched_version.identifier//"none"]|unique|max)\tvuln: \([.[].security_vulnerability.vulnerable_version_range]|unique|join(" ; "))"'
41+
```
42+
Repeat per severity. Note packages with `patched=none` (no upstream fix → defer/monitor).
43+
44+
### 2. Classify each package
45+
For each vulnerable package, read the lockfile entries and the requesting ranges:
46+
```bash
47+
node -e 'const fs=require("fs");for(const b of fs.readFileSync("yarn.lock","utf8").split("\n\n")){const k=b.split("\n").find(l=>l&&!l.startsWith("#")&&!l.startsWith(" "));const v=b.split("\n").find(l=>l.trim().startsWith("version:"));if(k&&v&&/^"?PKG@/.test(k))console.log(v.trim(),"<-",k)}' # replace PKG
48+
```
49+
- Range already permits patch → **relock** (Step 3).
50+
- First-party direct dep → **bump the manifest range**.
51+
- Upstream pins vulnerable version exactly, no release reaches fix → **resolution** (last resort).
52+
- Only fix is a major bump of a transitive parent, or no fix exists → **defer to an issue**.
53+
54+
### 3. In-range relock (the workhorse — no manifest change)
55+
`yarn up` only targets workspace-declared deps. For transitives, drop their lockfile blocks and
56+
reinstall; Yarn re-resolves each to the highest version its existing ranges allow:
57+
```bash
58+
node -e '
59+
const fs=require("fs");const targets=["pkg-a","pkg-b"]; // packages to relock
60+
const esc=s=>s.replace(/[.*+?^${}()|[\]\\\/]/g,"\\$&");
61+
const kept=fs.readFileSync("yarn.lock","utf8").split("\n\n").filter(b=>{
62+
const f=b.split("\n").find(l=>l.trim()&&!l.trim().startsWith("#"))||"";
63+
return !targets.some(t=>new RegExp("(^|, )\"?"+esc(t)+"@npm:").test(f));});
64+
fs.writeFileSync("yarn.lock",kept.join("\n\n"));'
65+
YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install
66+
```
67+
Then verify each resolved version clears its advisory range (use the `semver` package, required
68+
from the repo root so it resolves). Multi-major packages relock each major line independently;
69+
old-major lines with no backported fix simply won't move — those are the defer cases.
70+
71+
### 4. Resolution (last resort, documented)
72+
Add to root `package.json` `resolutions` only when justified, and open an issue to remove it once
73+
the proper direct bump becomes possible. Example justification: `protobufjs``@trezor/protobuf`
74+
pins it exactly and only the latest `@trezor/connect` reaches the patched line (a heavy bump
75+
deferred), so a `"protobufjs": "<patched>"` resolution is the low-risk same-day fix.
76+
77+
### 5. Validate
78+
```bash
79+
yarn build # all workspaces (type-level)
80+
yarn workspace @cardano-sdk/<pkg> test # packages whose runtime deps moved
81+
```
82+
A bump that breaks (or can't be validated for) a consumer-facing runtime package is **not**
83+
low-risk → defer it (e.g. `axios`, whose HttpProvider tests need real sockets — push to CI).
84+
85+
### 6. Commit per severity batch, then create deferral issues
86+
One commit per severity tier. For deferred work, open issues grouped by owning direct dependency
87+
(e.g. "bump the Express stack", "bump artillery to drop vm2"), and a tracking issue for
88+
no-upstream-fix packages (monitor/risk-accept).
89+
90+
## Audit artifacts (`docs/security/dependency-vulnerability-audit-<date>.md`)
91+
92+
### Changed-dependency set
93+
Diff the lockfile vs the base branch to get the exact set being updated:
94+
```bash
95+
git show master:yarn.lock > /tmp/base.lock
96+
# parse both into name -> sorted version set, report names whose set differs
97+
```
98+
99+
### Production / consumer reachability (BFS over `dependencies`-only edges)
100+
A package is consumer-facing iff reachable via production edges from a **non-private** workspace's
101+
`dependencies`. Walk `node_modules` with `require.resolve(<dep>/package.json, {paths:[fromDir]})`,
102+
following only `dependencies`+`optionalDependencies`. Compare prod-closure vs dev-inclusive closure
103+
to split prod vs dev-only impact.
104+
105+
### Blast radius — one dependency tree per sensitivity tier
106+
Assign packages to sensitivity tiers (Tier 1 = keys/crypto/consensus/tx: `crypto`, `key-management`,
107+
`core`, `tx-construction`, `input-selection`, `governance`; Tier 2 = wallet/hardware/dApp; Tier 3 =
108+
services/data; Tier 4 = utils/dev). Render **one Mermaid tree per tier**: edges from each updated
109+
security-relevant dependency to the tier's packages that pull it in production. Fan-out = blast
110+
radius; fan-in = a package's exposure; green node = untouched. The headline is usually that Tier 1
111+
is nearly untouched.
112+
113+
### Supply-chain provenance & publisher continuity
114+
```bash
115+
npm audit signatures # registry signature + SLSA provenance coverage (works under node-modules)
116+
```
117+
Per changed dependency, from the npm registry:
118+
- **Provenance:** `npm view <pkg>@<newver> --json | jq '.dist.attestations.provenance'` (SLSA via OIDC).
119+
- **Publisher:** `_npmUser` is a **string** `"name <email>"` in the full manifest — parse it; or query
120+
`npm view <pkg>@<ver> _npmUser.name`. Compare publisher of the **new** vs **base** version.
121+
- **Classify changes:** publisher → CI/OIDC bot = benign provenance adoption; human→human = surface
122+
for review (annotate known governance transitions, e.g. jshttp/Express-TC; flag anonymous/new
123+
accounts or single-version hijacks as high risk).
124+
125+
Columns per dependency in the audit table: version (base→new), vuln status (CLOSED/PARTIAL/OPEN),
126+
scope (direct|transitive · prod|dev · consumer:yes/no), license, provenance/publisher.
127+
128+
### Cross-reference external vulnerability databases
129+
Don't rely on a single source. Cross-check the GitHub Advisory DB (Dependabot) + `npm audit` against:
130+
- **OSV.dev** — aggregates GHSA, the npm registry, GitLab, etc. Free, no auth, accepts package+version
131+
(often returns *more* records than Dependabot — vm2@3.9.18 returned 31):
132+
```bash
133+
curl -s -X POST https://api.osv.dev/v1/query -H 'Content-Type: application/json' \
134+
-d '{"package":{"ecosystem":"npm","name":"<pkg>"},"version":"<ver>"}' | jq -r '[.vulns[].id]'
135+
# or scan the whole lockfile: osv-scanner --lockfile=yarn.lock
136+
```
137+
- **CISA KEV** (actively-exploited catalogue) — prioritisation signal; intersect your CVEs with it.
138+
If an open CVE is in KEV, it is being exploited in the wild → do not defer:
139+
```bash
140+
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json | jq -r '[.vulnerabilities[].cveID]'
141+
```
142+
- **NVD** (CVE/CVSS authority; API rate-limited), **Snyk DB** (often earliest), **Sonatype OSS Index**,
143+
**EPSS** (exploit-likelihood score), **Trivy** / **Grype** (lockfile scanners with their own aggregated DBs).
144+
145+
Record the cross-reference in the audit (e.g. "0 of N open CVEs appear in CISA KEV; OSV corroborates").
146+
147+
## Gotchas
148+
149+
- **`docs/` is the TypeDoc output** (gitignored, Pages-published, wiped each build) unless moved —
150+
tracked docs must not live there. Here the output was relocated to `typedoc/` so `docs/` is tracked.
151+
- **BSD awk lacks `gensub`** — use Node for lockfile parsing, not awk.
152+
- **`semver` / per-package scripts** must run from the repo root so `require("semver")` resolves.
153+
- **vm2 lesson:** a deprecated package that reappears un-deprecated with a new release cadence is a
154+
supply-chain red flag — eliminate it (bump the parent), don't pin to the suspicious line.
155+
- **Relock dedupe can regress** a shared transitive to an older copy (e.g. dropped a patched
156+
`serialize-javascript` 6.0.2 for 6.0.0) — always diff the changed set vs base and check for
157+
unintended downgrades.
158+
159+
## Reference
160+
A worked example of all of the above: `docs/security/dependency-vulnerability-audit-2026-06-19.md`
161+
and PR #1709, with deferral issues #1701#1708.

0 commit comments

Comments
 (0)