■ Zwei Achsen, nicht eine Zahl

Blast Radius

Version 0.1August 2026Nie Teil des Trust-Scores

Der Trust-Score beantwortet, wie wahrscheinlich es ist, dass etwas schiefgeht. Der Blast Radius beantwortet, wie schlimm es dann wäre — welche Tools ein Eintrag bereitstellt, welche Zugangsdaten er hält, wo er läuft und wie viel er installiert. Ein großer Blast Radius ist kein Mangel: Ein Dateisystem-Server soll Dateien schreiben. Er verändert die Trust-Note nie, in keine Richtung.

01Contained
02Moderate
03Extensive
04Critical
??Nicht gemessen · als Spanne ausgewiesen

Dieses Dokument wird nur auf Englisch gepflegt. Maßgeblich ist die englische Fassung.

Version 0.1 · August 2026 · Forge Registry

Forge's trust score answers one question: how likely is this to be bad? Who published it, did they prove it, did a scan run, and what did the scan find.

Blast radius answers the other one: how bad if it is?

They are different questions, they take different evidence, and Forge reports them as two axes. This note says what raises a blast radius, what it is computed from, and — at least as importantly — what it deliberately refuses to claim.


Why this is not part of the trust score

The obvious product is one number: likelihood × impact, a single "adoption risk" figure, one sort key. Forge does not ship that, for four structural reasons.

1. The merged number means neither thing. A trust score of 82 decomposes into rows a publisher can act on — verify your domain, publish with provenance, fix the high-severity CVE. Fold impact in and 82 means "some mixture of how trustworthy you are and how powerful you are". Nobody can act on that, and nobody can explain it. Forge's commitment is that scores are earned and the data behind them is public; a number whose components cancel out honours neither half of that.

2. A high blast radius is not a defect. A filesystem MCP server is supposed to write files. A shell server is supposed to run commands. Charging them for it produces a registry where the most useful tools carry the worst grades, and where a publisher's only route to an A is to expose less capability — which is not a security improvement, it is a worse product.

3. The score would move for nobody's act. Every weight in the trust score is something a publisher did or failed to do. Blast radius is a property of what the software is. A server that adds a legitimate write_file tool in a minor release would watch its trust grade drop with no change in its security posture.

4. The two are read together, and better apart. The decision a person actually makes is two-dimensional:

High trust Low / unrated trust
High blast radius Fine — install it, and scope the token narrowly Stop
Low blast radius Fine Probably fine; little to lose

Multiplying those into one scalar destroys the only information that made the read useful.

This is the same argument Forge already applies to eval results and to endpoint health, and it is enforced the same way: the trust scorer takes no blast-radius input, the blast-radius scorer reads no trust input, and src/__tests__/blast-radius.test.ts pins both directions.


The bands

Blast radius is reported as a band, not as a precise figure. The underlying 0–100 number exists and is published, but the inputs behind it are inferences, and rendering an inference to two significant figures claims a precision it does not have.

Band Score What it means
Contained 0–17 Nothing measured reaches past the entry itself — no execution, no credential worth stealing.
Moderate 18–33 The ordinary local-process reach: runs as you, holds a service credential, sees what you point it at.
Extensive 34–59 Reaches outside its own process in ways that are hard to undo — mutation, deletion, a data store, or an account-wide grant.
Critical 60–100 Arbitrary execution, or execution together with a live credential. A compromise is a compromise of the host or the account.
Unmeasured The floor and the ceiling land in different bands. Read the range.

The bands are calibrated against real cases rather than round numbers, and the weights below are calibrated against the bands rather than the other way round. Two cases pin the scale:


What raises it

Six dimensions, each capped independently so the published breakdown adds up to the score with nothing quietly absorbed. The caps sum to exactly 100, and every factor row is clamped to its dimension's remaining headroom as it is added — so the rows a reader can see always sum to the score they are shown, with no after-the-fact Math.min absorbing the difference.

Dimension Cap
Execution 40
Credentials 18
Locus 12
Bundle 14
Escalation 10
Supply 6

Execution — up to 40

What the entry can make happen, read from the tool surface Forge extracted (statically from the published artifact, or from a live tools/list handshake against a hosted endpoint).

Capability class Weight Example tool names
Runs commands 34 exec_command, run_shell, spawn_process, eval_code
Deletes data 26 delete_file, drop_table, truncate_log, kill_job
Mutates data 20 write_file, edit_document, rename_key, upload_blob, install_package
Reaches a data store 15 run_sql
Privileged, unclassified 12 flagged by the scanner, matching no class above
Read-only surface 3 a surface we read that contains none of the above

Ordered by how hard the damage is to undo rather than by how alarming the verb sounds. Arbitrary execution tops it because it subsumes every other row — a shell is a delete tool, a write tool and a network client at once. Deletion outranks mutation because a mutation is usually recoverable from the thing that was mutated. A data-store verb sits below both because its reach is one store rather than the host.

A second privileged tool adds 1 point, capped at 6 in total: more tools widen the surface, they do not multiply it.

A "read-only surface" scores 3 rather than 0 on purpose. Reading is reach — whatever the entry can see is inside whatever its credentials open — and the score has to stay distinct from the 0 an entry we never measured receives.

Prompt entries (Agent Skills, subagents) confer no tools of their own. What they can do is name MCP servers they require, which is borrowed reach: 6 points, +3 per additional server, capped at 14.

Credentials — up to 18

Weighted by what the credential opens, not by how secret it is.

Kind Weight Why
OAuth grant 15 Account-scoped: reaches everything that account can reach on that service, usually until someone revokes it. The credential a stolen copy is most useful to somebody else.
API key 10 One service, whatever the key was scoped to when it was minted — frequently more than this entry needs.
Environment variable 4 Configuration that gates the install without conferring access.

Each additional distinct credential adds 2, capped at 3.

Locus — up to 12

Where the code runs, and therefore whose authority a compromise inherits.

Weight
Runs on your machine 8 Started as a local process by your MCP client: your shell, your environment, your filesystem, your network.
Runs hosted 6 Your machine is not executing its code — but every prompt and every tool argument you send it leaves your machine, and what happens next is not observable from here.
Both 12 Ships an installable package and a hosted endpoint; whichever you pick, the other is available.
Runs as a prompt 4 Text loaded into a client that already held its own authority.

Bundle — up to 14

A collection installs many separately-authored units on one command: 7 points, plus 1 per two bundled units. A collection's trust score covers its installer package, not its contents — none of the bundled units were individually verified by installing it, and each carries its own reach.

Supply — up to 6

Install-time code execution (5 points when lifecycle scripts are declared) and the size of the transitive tree behind it (1–3 points by count). Installing a package installs everything under it, each from a separate maintainer with the same install-time reach.

Escalation — up to 10

The one non-additive term in the model, and the one that needs a justification rather than a weight.

Execution and a secret are each other's exit. A tool that runs commands can read the credential out of the process environment and post it anywhere; a credential worth stealing gives the command somewhere worth going. Scored as two independent rows, the pair lands mid-table beside a server that merely holds a token — which understates the only combination in this model with a named, mechanical escalation path.

Plain environment variables never trigger it. A non-secret configuration value is not worth exfiltrating, and letting one fire this term would make it near universal and therefore meaningless.


What we do not know, and how it is reported

This is the part of the model that matters most, because the failure mode is one-sided. Reporting "contained" for something Forge simply never measured is the dangerous error. "We do not know what this can do" and "it cannot do much" are opposite claims.

So every result is a range:

An unscanned npm entry does not score 0 and read "contained". It scores 8 — what its package coordinates alone imply — and reads "contained to critical — not scanned yet, tool surface unknown", which is the true state of Forge's knowledge about it.

Four dimensions can go unmeasured:

Gap Recorded when Headroom
Execution No tool surface, and the reason is not-scanned or not-extractable 40
Credentials No credential found from the publisher, the upstream registry, or the README 18
Locus No package coordinates and no declared endpoint 12
Supply Never scanned 6

Execution gaps are reported with Forge's existing ToolSurfaceGap vocabulary — not-scanned, not-extractable, none-detected, not-applicable — rather than a second, parallel set of reasons. Two of those four are answers, not gaps, and are scored as such:

The ceiling is kept honest in the other direction too. An entry with an observed surface containing nothing privileged cannot grow an escalation term by having its credentials filled in, so its ceiling does not include one. Overstating the ceiling is as dishonest as understating the floor: it would turn every unscanned entry critical and teach readers to ignore the field.


Known weaknesses

Stated rather than hidden.


Where it appears

There is deliberately no sort key. An ordinal built from inferred inputs, over a corpus where a large share of entries are honestly unmeasured, makes a bad ranking: sorting ascending would put the least-known listings at the top of a list that reads as "safest". The filter answers the same question without implying an order the data cannot support.


Reference implementation

src/lib/blast-radius.ts — pure, no I/O, no clock. computeBlastRadius() is the whole model; the weight tables above are exported constants in that file and this document is the prose view of them. The two must not drift.