| name | dependency-hardening |
| description | Triage and eliminate npm dependency vulnerabilities: trace transitive CVEs to the one direct dep, remove dead deps, re-audit to 0, and lock the win with a CI audit gate. Includes Windows/MSYS edit-tool path pitfalls. |
| version | 1.0.0 |
| author | Hermes Agent |
| license | MIT |
| platforms | ["linux","macos","windows"] |
| metadata | {"hermes":{"tags":["npm","security","audit","dependencies","vulnerabilities","ci","supply-chain"],"related_skills":["systematic-debugging"]}} |
Dependency Hardening (npm)
npm audit reporting criticals/highs is normal and fixable. The vulns are almost
always transitive — never in your direct dependencies. The fix is to trace
each to the single direct dep that pulls it in, then remove or replace that dep.
Removing an unused direct dep silently deletes its entire vulnerable subtree.
The triage loop (fast, deterministic)
npm audit --omit=dev --json | python -c "
import json,sys
d=json.load(sys.stdin)
v=d.get('vulnerabilities',{})
for name,info in v.items():
sev=info.get('severity')
titles=[x.get('title') for x in info.get('via',[]) if isinstance(x,dict)]
print(f'{sev.upper():>8} {name}')
for t in titles: print(' -', t)
print(' direct:', info.get('isDirect'), '| fixAvailable:', bool(info.get('fixAvailable')))
"
Per vulnerable package this prints: severity, advisory titles, whether it's a
direct dependency, and whether a fix exists. The last two columns decide the move.
Decision tree
fixAvailable: true → npm audit fix, or npm install pkg@latest to bump.
Re-audit. (Bumping Remotion to latest 4.x cleared 21 of 29 in one pass.)
fixAvailable: false + isDirect: true → the direct dep is the root.
- Dead code? Search the codebase: zero callers, only probed via
try { require('x') } availability checks + type-union strings. Remove it.
This deletes the whole transitive chain with zero functional loss.
- Load-bearing? Find a maintained alternative or vendor the logic.
fixAvailable: false + isDirect: false → trace upward with
npm ls <vuln-pkg> to find the direct parent, fix that.
Removing a dep leaves dead call sites
npm uninstall x does NOT delete code that called x. After removal:
grep -rn "xSymbol" src --include=*.ts — find leftover calls, type unions
('x-fallback'), doc comments, and unused imports.
- Delete them. Re-run
npm run typecheck — it will flag the broken references.
- Real example: removing unused
gtts (dragged in abandoned request → 2
critical + 6 moderate) took the project 29 → 0 vulnerabilities, typecheck
clean, 57/57 tests green.
Proof it worked
npm audit --omit=dev 2>&1 | tail -1
npm run typecheck && npm run test:unit
Lock the win with a CI gate
So the "0 vulns" guarantee can't silently regress:
security:
name: Dependency Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm audit --audit-level=high
Validate the YAML before committing: python -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))".
When a CI run fails and you can't open the UI (no gh/token), recover the root
cause from the unauthenticated REST API + local repro. See
references/ci-failure-forensics.md (key trap: Lint & Format jobs usually fail
on prettier format:check, not eslint — eslint with warnings exits 0).
Pitfalls
- Pre-existing lint debt:
npm run lint may show hundreds of problems
(e.g. @typescript-eslint/no-explicit-any) unrelated to your change. Confirm
your edited files are clean (npx eslint <your-files>) and treat the rest as
separate backlog. Don't let it block a security pass.
- Windows / MSYS path bug: On MSYS/git-bash, the
patch and write_file
tools MIS-RESOLVE MSYS paths — /c/one/Project becomes C:\\c\\one\\Project
(outside workspace) and the edit is rejected. Always pass native Windows
paths (C:\\one\\Project\\file.ts) to patch/write_file. The terminal
tool accepts either form. Symlinks are unreliable here (core.symlinks=false,
ln -s fails) — prefer gitignore + regeneration over symlink dedup for binary
assets (e.g. a logo regenerated by a build script).
patch silently no-ops on backslash-heavy content (regex, escapes): When
the old/new string contains \\s, \\[, or similar, the patch tool reports
"success" but does NOT change the file (fuzzy matcher chokes on escaped
backslashes). Symptom: re-read the line and it's unchanged despite success.
Reliable fix: drive the edit from a script file via terminal (node
String.split(...).join(...) with literal backslashes, or String.raw for
source regexes). execute_code is BLOCKED in cron mode and the patch tool is
unreliable here — use the node-script route. See references/node-escape-edit.md.
no-useless-escape inside a character class is a REAL bug, not noise: When
linting shows no-useless-escape at e.g. script-parser.ts:71:61, it often
means [^\[] — the \[ escape is unnecessary inside a class (use [^[]).
Fix it; don't just downgrade the rule. (Don't confuse with the intentional
no-explicit-any / no-unused-vars → warn downgrade for green CI — those
are codebase-wide style, the escape one is a genuine defect.)
When NOT to remove
If the dep is the only free/no-API-key path for a feature (e.g. a fallback voice
engine on Linux where Windows SAPI doesn't exist), consider re-adding it via a
SAFE implementation (the maintained Python package, called like an existing
Python voice CLI) rather than the vulnerable npm wrapper. Same functionality,
no request-style abandoned transitive.
Verify a 3rd-party SDK integration WITHOUT installing it
When adding an adapter for a heavy SDK (e.g. googleapis for YouTube upload),
keep it verifiable offline so dry-run tests need zero external deps:
- Lazy dynamic import the SDK only inside the live branch:
const { google } = await import('googleapis'); — never a top-level
import { google } from 'googleapis'. Dry-run/sandbox modes then run with
googleapis uninstalled.
- Put OAuth/token logic behind a
dryRun flag that returns mocked tokens/results.
- Write tests that assert URL shape, token round-trips, and file validation —
all without network or credentials.
- Result:
npm test green with no googleapis in node_modules; live path
untested until real credentials exist (documented limitation, not a defect).
See references/node-escape-edit.md for the node-script technique to apply
backslash-heavy edits the patch tool can't.