Repository navigation
feat: run on Node or Bun — Bun is no longer an install prerequisite - #9
Merged
Merged
Conversation
Asked whether Bun could be shipped with the skill. It can (a ~34MB binary per
platform, five platforms, Git LFS, and an unsigned runtime vendored inside a
SECURITY toolkit), but it should not be, and it turns out not to be necessary:
the toolkit's entire Bun dependency was four lines of dead code.
const f = Bun.file(path); // created
text = require("node:fs").readFileSync(path, "utf8"); // actually read via Node
void f; // explicitly discarded
That was the ONLY Bun.* call anywhere in Tools/ or the CLI. What remained binding
the project to Bun was cosmetic: `#!/usr/bin/env bun` shebangs and the CLI
hardcoding spawnSync("bun", ...).
- Removed the dead Bun.file block. Zero Bun APIs now remain.
- `stpa` dispatches to process.execPath — whichever runtime is executing it — so
the child always matches the parent and there is no PATH probing and no
ambiguity about which runtime produced an analysis. Node <22.18 gets
--experimental-strip-types automatically; Bun gets no flag.
- `stpa` is now CommonJS with a node shebang. It is the extensionless entry
point, and Node only auto-detects ESM in extensionless files from 22.7 onward,
so CJS is unambiguous on every Node >=12. `bun stpa` still works.
Also fixes two ways the new gates misbehaved on the shipped worked example, which
is a design-document analysis:
- ControlInventory hard-failed for want of candidates.json. A Modality-B analysis
has no code scan, so there is nothing to cross-check and passing is correct
rather than lenient. It still HARD-FAILS a codebase analysis with no scan,
which is the case where every absence claim is unfalsifiable (verified both
ways).
- Examples/ledgerline now declares trustRoot on all six process-model variables
(five attacker-input, one database), so the worked example demonstrates the
field rather than failing the gate that requires it.
Gate run-artifacts (review-scorecard.json, evidence-gate.json,
control-inventory.json) are gitignored; they regenerate and were never tracked.
VERIFIED with Bun genuinely absent from PATH, on Node v22.23: ./stpa --help via the
shebang, fixture grid, the worked example at 100%, every gate individually, the full
run chain, and the live 74-finding analysis with all four gates green. The five CI
steps were replicated locally under node before pushing. CI itself runs Bun, so the
Bun path is covered there for real — I have no genuine Bun here and am not claiming
to have exercised it.
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The question was whether Bun could be shipped with the skill. It can — and it shouldn't, because it isn't necessary.
The toolkit's entire Bun dependency was four lines of dead code:
That was the only
Bun.*call inTools/or the CLI. What remained binding the project to Bun was cosmetic: the shebangs, and the CLI hardcodingspawnSync("bun", …).Why not vendor Bun
Download-on-first-use is smaller but breaks the promise the toolkit makes repeatedly: no API keys, no network calls, no telemetry, fully offline.
What this does instead
Bun.fileblock — zero Bun APIs remain.stpadispatches toprocess.execPath, so the child runtime always matches the parent. No PATH probing, no ambiguity about which runtime produced an analysis. Node <22.18 gets--experimental-strip-typesautomatically.stpabecomes CommonJS with a node shebang — it's the extensionless entry point, and Node only auto-detects ESM in extensionless files from 22.7 onward, so CJS is unambiguous on every Node ≥12.bun stpastill works and is still first-class.Also fixes the new gates on the worked example
Examples/ledgerlineis a design-document analysis, and two gates from #6 misbehaved on it:ControlInventoryhard-failed for want ofcandidates.json. A Modality-B analysis has no code scan, so there is nothing to cross-check — passing is correct, not lenient. It still hard-fails a codebase analysis with no scan, which is the case where every absence claim is unfalsifiable. Verified both ways.trustRooton all six process-model variables, so it demonstrates the field instead of failing the gate that requires it.Verification
With Bun genuinely removed from PATH, on Node v22.23:
All five CI steps replicated locally under Node before pushing.
One honest limit: I have no genuine Bun in this environment, so I have not exercised the real Bun path — CI runs Bun and covers it there. I'm not claiming otherwise.