Skip to content

Keep the knowingly-vulnerable demo target out of the dependency graph - #194

Merged
patchstackdave merged 1 commit into
mainfrom
fix/dependency-signal
Aug 31, 2026
Merged

Keep the knowingly-vulnerable demo target out of the dependency graph#194
patchstackdave merged 1 commit into
mainfrom
fix/dependency-signal

Conversation

@patchstackdave

@patchstackdave patchstackdave commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

A knowingly vulnerable package named in a committed manifest enters this repository's dependency graph, where its advisories cannot be told apart from advisories about the package that ships. The demos exploit lodash@4.17.11 deliberately, so it stays out of the graph.

The dependency signal

The demos install their target on demand (npm run setup). examples/protect/demo-target.mjs is the single place the version is written down, and the example's lockfile is gitignored.

The version is part of what the demos prove, not an installation detail: against a patched version the exploit fails on its own, both demos still print their proof, and the guard is credited for a block that never happened. tests/demo-target.test.ts pins it and asserts the target is absent from every dependency-graph section of both the example and root manifests — dependencies, devDependencies, optionalDependencies, peerDependencies and both bundled spellings — through one shared list, so neither manifest can be checked against a shorter one than the other.

.github/dependabot.yml scopes update pull requests to the root manifest and the workflows. Worth being exact about what that file does: it selects which manifests get update pull requests, not which produce alerts. Alerts follow the dependency graph, so an ignore entry would suppress maintenance while leaving the graph — and the alerts — unchanged.

Dev-dependency advisories are resolved: esbuild via an override to ^0.28.1 (tsup pins ^0.27.0, so an override is the available route — build, typecheck, tests, side-effect audit and bundled proof all pass on 0.28.2), and nanoid via the available fix. npm audit reports zero.

The demos

They load the built runtime, which is what an application loads. The instructions give the sequence — root install and build, then npm run setup, then the demo — and the gallery section states that it needs the build but not the target.

Two runtime-load failures stay distinguishable, because presence is checked before loading: an absent build exits 2 with the command that produces it, and a build that exists but fails to load keeps its own error rather than being reported as a missing build.

npm run test:demos checks each demo for four things independently: exit status, absence of a failed-step marker, its own verdict line, and its proof. Separate assertions, because a process can exit zero having printed failures, and can print a banner naming a CVE while demonstrating nothing. The proof patterns are ones an inert run cannot satisfy — the gallery's requires a non-zero, fully-passing count — and the gallery treats zero demonstrations as a failure. Wired into CI.

The demo rules

tests/demo-rules.test.ts puts both demo bundles through validateBundle — the gate a delivered bundle passes — and requires the surviving rule count to equal the declared count, so a rule dropped for any reason is caught.

Every rule in the demo bundle has an exploit it blocks and a benign control it allows. The benign half is what makes each case evidence: a rule that blocks its exploit and everything else has not been shown to discriminate, and a demo bundle is where an over-broad rule looks fine.

Rule parameters name keyed sources only. raw takes no key, so a keyed raw.<name> parameter is not enforceable under the contract — which the bundle validation above now holds the bundles to.

Checks

1749 tests, typecheck, build, npm audit zero, demos, bundled proof, consumer matrix. Commit signed.

@coderbuds

coderbuds Bot commented Aug 31, 2026

Copy link
Copy Markdown

Demo isolation is clearly implemented with solid error handling and tests.

🎯 Quality: 80% Excellent · 📦 Size: Large — consider splitting if possible

🛡️ Standards: no pre-flight fit check ran for this change — wire assess-change-fit into your coding agents to catch size before opening.

📈 This month: Your 151st PR — above team average · Averaging Good

See how your team is trending →

A knowingly vulnerable package named in a committed manifest enters this
repository's dependency graph, where its advisories cannot be told apart from
advisories about the package that ships. The demos exploit lodash@4.17.11
deliberately, so they install it on demand (`npm run setup`) rather than
declaring it.

`examples/protect/demo-target.mjs` is the single place the version is written
down. The version is part of what the demos prove: against a patched version the
exploit fails on its own, both demos still print their proof, and the guard is
credited for a block that never happened. `tests/demo-target.test.ts` pins it and
asserts the target is absent from every dependency-graph section of both the
example and root manifests — optional, peer and both bundled spellings included,
through one shared list so neither manifest is checked against a shorter one.

`.github/dependabot.yml` scopes update pull requests to the root manifest and the
workflows. It selects which manifests get update pull requests, not which produce
alerts: alerts follow the dependency graph, so keeping the package out of the
graph is what keeps them meaningful. The example's lockfile is gitignored for the
same reason.

The demos load the built runtime, which is what an application loads, and the
instructions give the sequence: root install and build, then `npm run setup`,
then the demo. The gallery needs the build but not the target. An absent build
exits with the command that produces it; a build that exists and fails to load
keeps its own error, since presence is checked before loading.

`npm run test:demos` asserts, per demo and independently, that it exits zero,
prints no failed step, reaches its own verdict line, and prints its proof — with
a proof pattern an empty or inert run cannot satisfy. The gallery treats zero
demonstrations as a failure.

`tests/demo-rules.test.ts` puts both demo bundles through `validateBundle`, the
gate a delivered bundle passes, and requires the surviving rule count to equal
the declared count. Every rule in the demo bundle has an exploit that it blocks
and a benign control that it allows, so each rule is shown to discriminate rather
than merely to fire. Rule parameters name keyed sources only: `raw` takes no key,
and a keyed `raw.<name>` is not enforceable under the contract.

Dev-dependency advisories are resolved: esbuild via an override to `^0.28.1`,
since tsup pins `^0.27.0`, and nanoid via the available fix. `npm audit` reports
zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

@patchstackdave
patchstackdave merged commit 09425dd into main Aug 31, 2026
14 checks passed
@patchstackdave
patchstackdave deleted the fix/dependency-signal branch August 31, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants