Audit Dependencies for Vulnerabilities
Take a small project with intentionally outdated dependencies, run a vulnerability audit, read the report like a security engineer, fix the vulnerable packages, and confirm the audit comes back clean — the exact "audit" step of the DARE Ralph Loop.
Problem
Every application you ship is built on top of hundreds of pieces of code you did not write: your dependencies, and *their* dependencies (the "transitive" ones you never even chose directly). Any one of them can carry a known, publicly disclosed vulnerability — a CVE (Common Vulnerabilities and Exposures) — that an attacker can exploit the moment your app is reachable on a network. This is not a hypothetical risk. Supply-chain attacks and known-vulnerable dependencies are one of the most common ways real production systems get compromised, precisely *because* they are invisible in day-to-day code review — nobody reads the diff of a `package-lock.json` line by line. The fix is not "trust your dependencies blindly" nor "review every line of every library" (impractical). It is **automated dependency auditing**: tooling that cross-references every package and version in your project against a vulnerability database and tells you, in seconds, exactly which ones are dangerous, how dangerous, and what to do about it. In this lab you will work with a small sample project that has a handful of dependencies pinned to old, vulnerable versions on purpose. You will run an audit tool, learn to read its report (severity, CVE identifier, the dependency path that pulled the vulnerable package in), fix each finding by upgrading or replacing the dependency, and re-run the audit until it is clean. You will finish by connecting this to the DARE method directly: audit is the fourth and final gate of the **Ralph Loop** (build → test → lint → audit), and a task is never DONE while a HIGH/CRITICAL CVE remains open. > This lab is stack-agnostic. Instructions are given for both a Node.js > sample (using `npm audit`) and a Python sample (using `pip-audit`) — pick > whichever stack you already have installed. Only work against your own > local sample project; never scan or attack a project you do not own.
Objectives
By the end of this lab you will be able to:
- Explain what a CVE is, how severity scores (CVSS: low/moderate/high/critical)
work, and why transitive dependencies matter as much as direct ones. - Run
npm auditorpip-auditagainst a real project and read the report:
package name, installed version, vulnerable range, patched version,
severity, and the dependency path. - Distinguish a direct vulnerable dependency from a transitive one,
and know the different fix strategies for each. - Fix vulnerabilities by upgrading a version, using an automated fixer
(npm audit fix), or replacing a package entirely when no patched version
exists. - Re-run the audit to confirm zero HIGH/CRITICAL findings remain, and
explain why a moderate/low finding might be accepted with a documented
reason instead of blocking a release. - Connect dependency auditing to the DARE Ralph Loop and explain why
"audit" is a mandatory, non-optional gate before marking a task DONE.
Prerequisites
- Node.js 18+ with
npmor Python 3.9+ withpip(only one stack is
required; pick whichever you already have). -
pip-auditinstalled if you choose the Python path:pip install pip-audit. - A terminal and a plain-text editor.
- Internet access (the audit tools query a public vulnerability database).
- No prior security experience required — this lab teaches the workflow
from scratch.
What you will build
A tiny sample project (Node.js or Python, your choice) with a handful of
dependencies pinned to old, known-vulnerable versions. You will audit it,
fix it, and prove — with tool output, not guesswork — that it is clean.
Why "audit" is its own Ralph Loop step
The DARE Ralph Loop is build → test → lint → audit. The first three
steps tell you your own code works. Audit tells you whether the code you
are standing on is safe. These are orthogonal concerns:
- A project can build, pass every test, and be perfectly linted — and still
ship a Redis client with a remote-code-execution CVE because nobody
bumped it in fourteen months. - Audit findings are not "your bug" in the sense that you wrote the flaw,
but they are your responsibility the moment you ship them — the
vulnerability runs with your application's privileges, in your
production environment, against your users' data.
This is why the DARE rule is blunt: a CVE rated HIGH or CRITICAL in your
dependency tree means the task is FAILED, not DONE, until it's fixed.
Moderate/low findings may sometimes be accepted with a written justification
(e.g. "only reachable in a dev-only code path"), but HIGH/CRITICAL never
gets a pass.
Anatomy of a vulnerability report
Every audit tool reports roughly the same shape of information per finding:
| Field | Meaning |
|---|---|
| Package | The name of the vulnerable dependency |
| Installed version | The version currently in your lockfile |
| Vulnerable range | The version range affected (e.g. < 4.17.21) |
| Patched version | The first version where the fix landed |
| Severity | low / moderate / high / critical (derived from a CVSS score) |
| Identifier | A CVE ID (e.g. CVE-2021-23337) or advisory ID (e.g. GHSA-...) |
| Path | The chain of dependencies that pulled this package in — is it direct or transitive? |
The path is the single most useful field for deciding your fix strategy.
If your package.json lists the vulnerable package directly, you can often
just bump the version yourself. If it is three levels deep inside someone
else's dependency, you may need to update the parent package instead, or
wait for the maintainer to release a patched version.
How to work through this lab
Pick one stack (Node.js or Python) and follow that track through all
five steps — the sample project, the vulnerable dependencies, and the exact
commands differ slightly by stack, but the workflow (audit → read → fix →
re-audit → connect to Ralph Loop) is identical either way.
Steps
-
Set up a sample project with vulnerable dependencies
Create a fresh local directory and pin a handful of dependencies to
old, known-vulnerable versions on purpose. Pick your stack.Node.js track
mkdir audit-lab && cd audit-lab npm init -y npm install lodash@4.17.15 minimist@1.2.5 axios@0.21.0These three specific old versions are a deliberate choice:
-
lodash@4.17.15— prototype pollution (fixed in 4.17.21). -
minimist@1.2.5— prototype pollution (fixed in 1.2.6). -
axios@0.21.0— Server-Side Request Forgery via redirect handling
(fixed in later 0.21.x/1.x releases).
Python track
mkdir audit-lab && cd audit-lab python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install "requests==2.25.1" "urllib3==1.26.4" "Jinja2==2.11.2" pip freeze > requirements.txtThese are also deliberately old:
-
requests==2.25.1andurllib3==1.26.4— several CVEs around
proxy/credential handling and retry behavior in that era. -
Jinja2==2.11.2— a sandbox-escape class of vulnerability, fixed in
later 2.11.x/3.x releases.
Checkpoint
Confirm the packages actually installed at the old, pinned versions:
# Node npm ls lodash minimist axios # Python pip show requests urllib3 Jinja2You should see exactly the old version numbers you specified — if npm
or pip silently resolved something newer, re-pin with the exact
version syntax shown above.Deliverable for this step: a local project directory with a
lockfile (package-lock.jsonorrequirements.txt) that pins these
old, vulnerable versions, confirmed by the checkpoint command output. -
-
Run the audit and read the raw report
Now run the audit tool for your stack and look at the raw, unfiltered
output before doing anything else — resist the urge to jump straight to
--fix. You need to be able to read this report by hand.Node.js track
npm auditYou will see, for each finding, something like:
# npm audit report lodash <4.17.21 Severity: high Prototype Pollution in lodash - https://github.com/advisories/GHSA-... fix available via `npm audit fix` node_modules/lodash axios <=0.21.1 Severity: moderate Server-Side Request Forgery in axios - https://github.com/advisories/GHSA-... fix available via `npm audit fix` node_modules/axios 2 vulnerabilities (1 moderate, 1 high)For a machine-readable version (useful for CI or scripting):
npm audit --json > audit-report.jsonPython track
pip-audit -r requirements.txtOutput looks like:
Name Version ID Fix Versions -------- -------- ------------------- ------------ requests 2.25.1 PYSEC-2021-136 2.25.1... (see fix versions) urllib3 1.26.4 PYSEC-2021-108 1.26.5 Jinja2 2.11.2 PYSEC-2021-66 2.11.3For JSON output:
pip-audit -r requirements.txt -f json > audit-report.jsonRead every column before you touch anything
For each finding in your report, write down (on paper or in a
scratch file) these four things:- Package + installed version — what you actually have.
- Severity — low / moderate / high / critical.
-
Identifier — the CVE or GHSA/PYSEC ID, so you could look up the
full advisory if you needed more detail. -
Is it direct or transitive? Check your
package.json/requirements.txt
— if the package is listed there by name, it's direct; if it only
shows up in the lockfile pulled in by something else, it's transitive.
Checkpoint
You should be able to answer, without re-running the tool: "Which
finding is the most severe, and is it a package I installed directly or
one that came along for the ride?" If you cannot answer that from your
notes, re-read the report.Deliverable for this step: a short written list of every finding
(package, severity, ID, direct/transitive) plus the saved JSON report
file. -
Fix each finding — upgrade, auto-fix, or replace
There are three fix strategies, in order of preference. Try them in
this order for each finding.Strategy 1 — automated fixer
The audit tools ship a fixer that bumps to the minimum patched version
that satisfies your existing semver range, and re-installs.# Node npm audit fix # Python has no direct equivalent; upgrade manually (Strategy 2)npm audit fixis safe for patch/minor bumps within your declared
range. It will not cross a major version bump on its own (that
needsnpm audit fix --force, which you should treat as a real
upgrade — read the changelog first, it can be a breaking change).Strategy 2 — manual version bump
When the fixer can't reach far enough, or you're on Python, bump the
version explicitly to (or past) the patched version from the report.# Node — bump a direct dependency past the patched version npm install lodash@^4.17.21 axios@^0.21.4 # Python — edit requirements.txt, then reinstall pip install --upgrade "requests>=2.31.0" "urllib3>=1.26.18" "Jinja2>=3.1.3" pip freeze > requirements.txtAlways re-read your project's actual code after a version bump —
especially across a major version — to check for breaking API
changes. This is where your test suite earns its keep (see the
checkpoint below).Strategy 3 — replace the dependency
Sometimes there is no patched version (the package is abandoned) or the
patched version requires a runtime you can't yet adopt. In that case:- Look for an actively maintained fork or an alternative package that
covers the same functionality. - If the vulnerable code path isn't actually reachable from your
application (e.g. a dev-only tool), you may vendor a patch or accept
the risk with a written justification — never silently.
Fix your lab's findings now
Apply the fixer/upgrade for every package you audited in Step 2. For
this lab's specific packages:# Node npm audit fix npm install lodash@^4.17.21 # if audit fix didn't reach it # Python pip install --upgrade "requests>=2.31.0" "urllib3>=1.26.18" "Jinja2>=3.1.3" pip freeze > requirements.txtCheckpoint
After every fix, run whatever passes for "tests" in your sample
project (even a trivial smoke script that imports/requires each
package and calls one function) to confirm nothing broke:# Node node -e "console.log(require('lodash').chunk([1,2,3,4],2))" # Python python -c "import requests, jinja2; print('ok')"A clean audit that broke your app is not actually done — it just moved
the problem. This is exactly why "audit" sits after "test" in the
Ralph Loop, not before: you re-run test after every fix, not just at
the very end.Deliverable for this step: updated lockfile/
requirements.txt
with patched versions, plus confirmation the smoke check still passes. - Look for an actively maintained fork or an alternative package that
-
Re-run the audit and confirm a clean report
Run the exact same audit command from Step 2 again. This is the
verification step — you are not done because you think you fixed it,
you are done because the tool says so.# Node npm audit # Python pip-audit -r requirements.txtExpected clean output:
# npm audit report found 0 vulnerabilitiesNo known vulnerabilities foundIf a finding remains
-
Re-check severity. If it's HIGH/CRITICAL, go back to Step 3 —
this is not optional under the DARE Ralph Loop rule. -
If it's moderate/low and no fix exists yet, this is the one case
where you may proceed — but only with a written, dated note
explaining: which package, which CVE, why it's not reachable or why
the risk is accepted, and a reminder to re-check next time you touch
dependencies. Never let an accepted-risk note go unwritten — an
undocumented "I'll deal with it later" is how vulnerabilities survive
for years.
Set the CI gate for real
In a real project, this check should run automatically and fail the
build on HIGH/CRITICAL, so no one can merge a newly-vulnerable
dependency by accident:# Node — non-zero exit if HIGH or above is found npm audit --audit-level=high # Python — pip-audit exits non-zero on any finding by default pip-audit -r requirements.txtTry running
npm audit --audit-level=highyourself right now and
check its exit code:npm audit --audit-level=high; echo "exit code: $?"An exit code of
0means clean; anything else means the audit gate
would block a CI pipeline — exactly the behavior you want.Checkpoint
You should now have:
- A "before" report (from Step 2) showing at least one HIGH/moderate
finding. - An "after" report showing zero HIGH/CRITICAL findings (or a
documented accepted-risk note for anything remaining). - A command (
npm audit --audit-level=highorpip-audit) that exits
0— the exact command you would wire into CI.
Deliverable for this step: the clean audit output, saved
side-by-side with your Step 2 "before" report, plus the CI-gate command
and its exit code. -
Re-check severity. If it's HIGH/CRITICAL, go back to Step 3 —
-
Wire audit into the DARE Ralph Loop (submission)
You have practiced the full workflow by hand. Now formalize it as the
fourth step of your Ralph Loop, the way a real DARE task would define
it in its Definition of Done.Write the Ralph Loop command sequence
For your sample project, write out the four commands a task's DoD
would require, in order — this is exactly what an AI executing a DARE
task would run before marking anything DONE:# 1. Build # (Node) — no compile step needed for plain JS, or: npm run build # (Python) — python -m py_compile *.py, or your framework's build step # 2. Test # (Node) — npm test # (Python) — python -m pytest # 3. Lint # (Node) — npx eslint . # (Python) — ruff check . # 4. Audit <-- what you practiced in this lab npm audit --audit-level=high # or pip-audit -r requirements.txtWrite the DoD rule in plain language
Add this line to a
NOTES.md(or your project'sTASKS.md, if you
have one) — it's the rule you'll now apply to every future task:## Definition of Done — dependency audit A task is DONE only if: - `npm audit --audit-level=high` (or `pip-audit`) exits 0, AND - Any remaining moderate/low finding has a written, dated justification explaining why it is not reachable or why the risk is accepted. A HIGH or CRITICAL finding anywhere in the dependency tree means the task is FAILED, regardless of how complete the feature code is.Why this belongs at the end of the loop, not the start
Audit runs last because it should gate everything else — there is no
point auditing dependencies before your code even builds. But it must
still run on every task that touches dependencies, not just once at
project setup: a new PR that adds a single new package is a new audit
surface, and skipping it is how vulnerable dependencies slip into
"trusted" codebases.Submission criteria
Submit when all of the following hold:
- A before/after pair of audit reports for your sample project (Step 2
and Step 4), showing at least one HIGH or moderate finding fixed. - The exact CI-gate command for your stack (
npm audit --audit-level=high
orpip-audit -r requirements.txt) with a confirmed0exit code. - A short
NOTES.mdwith the Ralph Loop's four-command sequence and
the Definition of Done rule for dependency audits, written in your
own words. - If any finding remains unresolved, a dated, written justification
for it — never a silently ignored finding.
- A before/after pair of audit reports for your sample project (Step 2