The supply-chain attacks landing in 2026 don't live inside any one layer — they
live in the path between layers, where nothing is looking. Installing a dependency
runs its lifecycle scripts with the authority of wherever they run; handing a coding
agent shell access is a standing capability an attacker can drive. A repository is an
execution surface, and its real danger is never a risky artifact in isolation — it
is the reachable route from something an attacker controls to something sensitive:
a malicious preinstall that runs in a CI job holding an AWS key on fork-triggered
pull requests; an over-broad agent grant sitting on a prompt-injectable surface.
Every existing scanner sees one layer and stops at its edge — dependency tools score the package, workflow tools audit the job, MCP scanners inspect the agent grant. None computes the connective path that turns three separate "maybe" signals into one demonstrable attack. That path is the gap. Blastgate is the gate that computes it.
Blastgate models your dependencies, your CI, and your agent/MCP configuration as one directed graph and asserts a single property on every change: no reachable path connects an attacker-controllable entry point to a sensitive sink. It reports a finding only when there is a demonstrable path — a new install script that runs in a fork-triggerable job that holds an AWS key — never merely a risky-looking pattern. A repository with no such path passes; a failure names the concrete path, the sink it reaches, why it is reachable, a fix, and the OWASP category.
It ships as a CI/PR gate, a local CLI, a GitHub Action, and a Claude Code plugin
(enforcement hooks + an MCP self-check tool + a /blastgate command) — all driven by
one engine, so every surface produces identical findings.
- Teams whose CI holds secrets and accepts external pull requests. Fork-triggered workflows with credential access are the highest-value target in most repos; Blastgate is the gate that fails the PR before an untrusted change can reach them.
- Teams adopting coding agents / MCP servers. Committed
.mcp.jsonand.claude/settings.jsongrants are a blast radius most teams never audit. Blastgate measures it and flags over-privilege against a least-privilege baseline. - npm / PyPI / RubyGems maintainers worried about supply-chain compromise — install-time execution and newly introduced dependencies, evaluated offline from the lockfile (plus opt-in npm provenance regressions).
- Security engineers who want a gate with a low false-positive rate: it fails only on a reachable secret/credential path, and warns (does not fail) on lower-severity capability paths.
Each row below is a live 2026 attack shape, and each is invisible to any single-layer tool because the danger is the connective path — the row's middle column — not any one artifact on it:
| Threat | The concrete path Blastgate catches | OWASP |
|---|---|---|
Install-script supply-chain worm (e.g. Shai-Hulud: a preinstall stealer that sweeps npm/GitHub/AWS/Vault creds and propagates) |
A newly added dependency's install script runs in a fork-triggerable job that holds AWS_SECRET_ACCESS_KEY |
ASI04 / MCP04 |
CI pwn-request |
A pull_request_target / fork pull_request job runs untrusted code while holding secrets or an over-broad GITHUB_TOKEN |
ASI03 |
| npm provenance regression (the CVE-2025-54313 shape) | A dependency that had npm attestations and silently lost them between versions — a strong compromise signal (opt-in, --provenance) |
ASI04 / MCP04 |
| Over-privileged coding agent | A committed MCP server rooted at / or an unrestricted Bash(*) / wrapper-bypass grant — a prompt-injectable path to out-of-repo filesystem/network/shell |
ASI01 / MCP02 |
| Slopsquatting & unsigned agent marketplaces | A newly introduced dependency or agent grant from an unaudited source, evaluated for reachability rather than trusted by name | ASI04 / MCP02 |
Each of these is named and modeled in docs/threat-model.md,
which also maps every OWASP category Blastgate emits.
✗ FAIL added dependency evil-pkg@1.0.0 → evil-pkg@1.0.0 → .github/workflows/ci.yml#test → AWS_SECRET_ACCESS_KEY
sink: credential AWS_SECRET_ACCESS_KEY [ASI04:2026, MCP04:2025]
why: New or changed dependency evil-pkg@1.0.0 declares an install script that executes in job
.github/workflows/ci.yml#test, which is triggered by untrusted input (fork PRs) and holds
credential AWS_SECRET_ACCESS_KEY, which the script can exfiltrate.
fix: Gate lifecycle scripts in that job (e.g. run `npm ci --ignore-scripts`) or remove
AWS_SECRET_ACCESS_KEY from .github/workflows/ci.yml#test.
Blastgate builds a single graph from the repository's manifests, lockfile, CI workflows, and agent/MCP configuration.
- Dependency / install layer — install-time execution and newly added or changed
dependencies across npm (lifecycle scripts, lockfile diff,
.npmrc, provenance regressions), Python (setup.py,requirements.txtdiff), and RubyGems (Gemfile.lockdiff, native-extension gems). A dependency an untrusted change introduces that runs code at install is the shared shape across all three. - CI layer — which jobs execute install/build steps, which secrets and token
permissions each job holds, and which jobs are triggerable by untrusted input —
across GitHub Actions (
pull_request_target, fork PRs), GitLab CI (merge-request pipelines, CI/CD variables), and CircleCI (advisory only: its forked-PR-secret exposure is a project setting outside the repo — Blastgate surfaces it rather than falsely passing; see the threat model). - Agent / MCP layer — the filesystem, network, shell, and tool capabilities granted by committed agent/MCP configuration, checked against a least-privilege baseline.
Edges encode reachability: an install script executes within a job, a job holds a secret, an agent grant reaches a capability. A finding is the shortest path per (entry, sink) from an attacker-controllable entry point — a new or modified dependency, an untrusted trigger, or a prompt-injectable agent surface — to a sensitive sink. Findings are ranked by sink sensitivity and entry-point exposure, and each is labeled with its OWASP Agentic Top 10 (2026) and MCP Top 10 (draft) category.
Precision is the primary design constraint. A check fails only on a genuinely reachable
path; the presence of a pattern alone does not produce a finding. A postinstall script
in a job that holds no secrets and is not triggerable by untrusted input is not
reported. This keeps findings actionable and avoids the false-positive rate of
pattern-matching tools. A human can accept a finding via a committed
.blastgate/acknowledged.json (by id) or the more expressive .blastgate/policy.json
(accept by finding id, sink identity, or archetype, with an optional review-by date) —
an auditable exception that downgrades a finding to a reported warning, never a silent
kill switch: blanket/wildcard rules are rejected at parse, and a rule introduced by the
same change is ignored (a PR cannot self-approve its own finding).
Inventory tools (StepSecurity Dev Machine Guard) enumerate what exists. Single-layer
tools assess one layer each — dependencies (Socket, Aikido, Endor), CI workflows
(Harden-Runner, OpenSSF Scorecard), or MCP configuration (Invariant mcp-scan, Cisco
mcp-scanner). None computes the connective, cross-layer reachable path. Blastgate is
that connective layer and does not replace the per-layer scanners — it composes their
signals into a path. The full tool-by-tool contrast is in
docs/threat-model.md.
- CI/PR gate — exits non-zero on a reachable path and surfaces the finding on the pull request.
- Local CLI — runs against the same repository and produces the same findings.
- Claude Code plugin (
plugin/) — enforces the same gate inside an agent session as deterministic hooks on commit and dependency installation, and exposes an advisory tool the agent can call to evaluate a proposed change before it acts. Seeplugin/README.md.
Blastgate is the same engine run at three moments, each catching a reachable path earlier than the last:
- Local, while you work — the Claude Code plugin gates a
git commit/git pushor annpm installthat would open a path, so it never leaves your machine. Runnpx blastgate .by hand anytime for the same check. - On the pull request — the GitHub Action runs on every
pull_request, fails the check on a reachable path, and posts the finding on the PR. - At pre-merge review — the same report (
--format md) is the job summary a reviewer reads before approving, so the merge decision sees the attack path.
One engine and one Finding shape back all three, so a change that passes locally
passes in CI for the same reason — the surfaces cannot disagree.
npx blastgate . # scan the current repo; exits non-zero on a fail verdict
npx blastgate . --base main # add diff signals (new deps, .npmrc changes) vs a ref
npx blastgate . --provenance # opt-in npm provenance-regression check (needs --base)
npx blastgate . --json # emit the findings array as JSON (has each finding `id`)
npx blastgate . --format md # human-readable markdown report (share it, or > report.md)
npx blastgate . --advisories # opt-in CVE/advisory enrichment of reachable deps (never gates)
npx blastgate . --record runs/ # append a run record for the trend dashboard
npx blastgate report runs/ --out trend.html # static HTML trend over recorded runsA fail verdict (a reachable path to a secret or credential) exits non-zero; a clean
repo or warn-only findings (lower-sensitivity capability paths) exit zero. The
--format md report leads with the verdict and a "where this runs" banner, gives one
section per finding (the attacker→sink path, why it is reachable, the fix, and the
OWASP label), and closes with what to do next — it is the same report the Action posts
as its PR job summary.
Runs the same engine as the CLI — findings are identical (asserted by test). It
defaults the diff base to the pull request's base ref, so change signals light up
automatically, and surfaces findings as PR annotations plus a job-summary report (the
same output as blastgate --format md).
# .github/workflows/blastgate.yml
name: Blastgate
on: [pull_request]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # so the base ref is available for diff signals
- uses: jwolberg/blastgate@v0Supported today. One engine, one finding shape across all surfaces:
- Ecosystems — npm (lifecycle scripts, lockfile diff,
.npmrc, provenance), Python (setup.py,requirements.txtdiff), RubyGems (Gemfile.lockdiff). - CI providers — GitHub Actions and GitLab CI (full gate), CircleCI (advisory only — the fork-secret toggle is out-of-repo).
- Agent / MCP — committed grants checked against a least-privilege baseline.
- Exceptions —
.blastgate/policy.json/acknowledged.json(auditable, specific, self-approval-guarded). - Opt-in enrichment — npm provenance regressions (
--provenance) and CVE/GHSA advisories on reachable deps (--advisories), deliberately enrichment, not gating: Blastgate fails only on a reachable path, never because a package is known-bad by name. - History — per-run records (
--record) and a static HTML trend (blastgate report).
On the roadmap. Maven / Gradle (build-script execution — the design spike recommends
GO, see docs/learnings/); more Python lockfiles (poetry / uv / Pipfile);
a hosted collaboration dashboard (the current dashboard is local/static only).
Out of scope. Analysis of deployed or running targets. See docs/plans/
for the product plan, docs/threat-model.md for the model, and
docs/implementation-notes.md for recorded decisions.
Functional across all surfaces, ecosystems, and CI providers. One engine backs the CLI
(blastgate), the GitHub Action, the Claude Code plugin hooks, and the
blastgate_check_change MCP self-check. Ecosystems (npm, Python, RubyGems), CI providers
(GitHub, GitLab, advisory CircleCI), the exception-policy engine, opt-in provenance and
CVE-advisory enrichment, and the run-record trend report are all wired, and the engine is
covered by unit tests plus a fixture-repo regression suite (a true-positive and
true-negative repo per shipped check). Remaining before a public release: publishing the
Action's built artifact for third-party consumers.
Apache-2.0.