Skip to content

GuardLink

GuardLink is a threat model you write beside the code, and a command that checks it. The whole of the idea is one line above a function:

src/routes.js
// @exposes #receipts to #idor [critical] cwe:CWE-639 -- "No ownership check"
app.get('/receipts/:id', async (req, res) => {
const receipt = await db.query('SELECT * FROM receipts WHERE id = $1', [req.params.id])
res.json(receipt)
})

A person wrote that line. Git dates it and attributes it, it appears in the pull request that changes the handler, and it moves with the function when the function moves. Until someone writes the @mitigates that closes it, every run says so:

Terminal window
npx guardlink ci . --strict
Unmitigated exposures: 1 (critical 1)
Anchor drift: 0 (no anchored @source blocks to check)
⚠ 1 unmitigated exposure(s):
#receipts → #idor [critical] (src/routes.js:1)

Exit code 1. That is the gate. Without --strict the same command reports the same findings and exits 0, so a repository does not fail its build on the day the annotations land.

  • MIT licensed. Free to use, including commercially. LICENSE.
  • Installs from npm. npm install --save-dev guardlink, Node.js 18 or newer.
  • No account, no API key, no server. guardlink reads and writes files on your disk. The one command that reaches further is annotate, and it does so by handing a prompt to a coding agent you already have.
  • An open specification, not only a tool. GuardLink v1.0.0 is published under CC-BY-4.0 with four conformance levels, and this CLI is Level 4 conformant. See the open specification.
  • The grammar is not new. @mitigates, @exposes, @transfers, and @accepts come from ThreatSpec (2015 to 2020).

Four commands, run in the repository you want annotated:

Terminal window
npm install --save-dev guardlink
npx guardlink init .
npx guardlink annotate "annotate the routes" . # hands the work to your coding agent
npx guardlink ci .

Install GuardLink runs the same loop against a project you build in about three minutes, and write your first annotation finishes with a threat model, a markdown report, and a CI check that passes.

guardlink annotate is the fastest route from an unannotated repository to a populated model, and it is the one command here that does not run on its own: it builds a prompt carrying the grammar, your definitions, and your project’s rules, then hands that prompt to a coding agent you already have. --stdout prints the prompt instead, so you can read exactly what would be sent before anything is. Annotate an existing codebase covers it in full.

GuardLink: AI-Powered Continuous Threat Modeling as a Code walks through the loop end to end on a real repository.

What it found on a repository nobody had annotated

Section titled “What it found on a repository nobody had annotated”

Bugb ran guardlink with a coding agent against vuln-node.js-express.js-app, a deliberately vulnerable Express application carrying 37 documented vulnerability types. In 6 minutes, with no human intervention:

Measure Result
Annotations written 143, across 6 route files
Distinct threats identified 29, with CWE mappings
Unmitigated exposures recorded 66, each with file:line precision
Known vulnerabilities detected 27 of 37, so 73% recall, or 81% counting partial matches
Architecture recovered 8 assets, 3 data flows, a Mermaid diagram with a risk heat map
Token cost About $0.50 in Haiku tokens

That is the project’s own measurement, published in guardlink’s README, not an independent benchmark, and recall is measured against that application’s own documented vulnerability list. It is quoted here because it is the clearest statement of what the annotate loop produces unattended, and a reader deciding whether to spend an afternoon on this should be able to see it.

A threat model written as a document is accurate on the day it is signed off and decays from then on: the code moves, the document does not, and nobody finds out until an incident. Annotations decay too, but they decay visibly. When an anchored block no longer sits on the symbol it names, guardlink ci reports the drift, guardlink reanchor repairs the ones whose symbol still exists, and what is left needs a human precisely because the function it described is gone. See why annotations live in code.

Under the hood guardlink reads a small annotation language called GAL. You declare assets, threats, controls, and actors once in .guardlink/definitions.js, each with an #id, and every claim references those ids rather than repeating their names. That split is what makes the model checkable: a claim referencing #idor either resolves to a declared threat or guardlink validate fails on it.

.guardlink/definitions.js
// @asset App.Receipts (#receipts) -- "Customer payment receipts"
// @threat Insecure_Direct_Object_Reference (#idor) [critical] cwe:CWE-639 -- "A user reads another user's record by changing an id"

npx guardlink gal prints the whole language with an example per verb, which is the fastest reference while you are writing annotations. The annotation language explains how the verbs relate.

Once the annotations are in place, one model feeds several consumers.

flowchart TB
    SRC["annotations in your source<br/>@exposes, @mitigates, @confirmed"] --> P["guardlink parse"]
    P --> M["the threat model<br/>.guardlink/model.json"]
    M --> CI["guardlink ci<br/>open exposures, drifted anchors"]
    M --> EX["sarif, report, dashboard, diff"]
    M --> AG["MCP server<br/>an agent reads and extends it"]

    class M emphasis
Output Command For
The model as JSON guardlink parse Anything that wants to read the model directly
A CI gate guardlink ci Failing a build on unmitigated exposures and drifted anchors
SARIF 2.1.0 guardlink sarif GitHub Advanced Security, VS Code
A report with a Mermaid diagram guardlink report A human reading the model
An interactive HTML dashboard guardlink dashboard Browsing the model and its diagrams
What changed since a git ref guardlink diff Reviewing a pull request
What you are doing Reach for Why
Recording what a piece of code puts at risk @exposes and @mitigates in source The statement is dated by git and attributable, and it moves with the code
Gating a build on the model guardlink ci Advisory by default, and --strict makes it fail
Finding annotations that drifted off their symbol guardlink reanchor Reports @source blocks whose file:line no longer holds the symbol they name
Feeding a code-scanning pipeline guardlink sarif Standard SARIF, so existing tooling reads it without adapters
Handing the model to a coding agent guardlink mcp An agent reads and extends the model instead of inferring one
Testing whether an exposure is real cxg pentest guardlink states the hypothesis, cxg tries to confirm it at runtime
Reading the model from your own code The library API Seven subpath exports, from the published TypeScript declarations

Three things surprise people. Each one is covered in full elsewhere.

Install and annotate one function

Install GuardLink, annotate a single function, and finish with a threat model that validates clean in CI.

Install GuardLink

Give an agent the model

Wire the MCP server into a coding agent: 24 tools and 3 resources over stdio.

Wire up the MCP server

Read the specification

Four conformance levels, a CC-BY-4.0 grammar, and a ten-year lineage.

The open specification

These pages describe GuardLink 2.0.0, the current release on npm. Every command shown was run against that version and its real output pasted in.

Each of the three references is produced from the installed package: its --help output, its shipped .d.ts files, and its running MCP server. None of them can claim a behaviour the release does not have.

Reference Covers
CLI reference Both executables, guardlink and guardlink-mcp
API reference The seven subpath exports, from the published TypeScript declarations
MCP tool reference Every tool and resource the MCP server registers
What Where Licence
The CLI and library Bugb-Technologies/guardlink MIT
The published package guardlink on npm MIT
The specification text docs/SPEC.md CC-BY-4.0

The major version is scoped to the TypeScript type surface and the threat-model JSON schema. No CLI command, flag, or output behaviour was removed, so CLI and MCP users upgrade with no migration.

What 2.0.0 changed
Change Affects
coverage reshaped to {annotation_count, coverage_percent} Anything reading total_symbols, annotated_symbols, or unannotated_critical. See the artifact page
Model schema 1.1.0 to 1.2.0, report schema 1.0.0 to 1.1.0 guardlink merge warns on a mixed-version set and normalises the old shape
UnannotatedSymbol removed from the type surface TypeScript consumers
New unknown-verb diagnostic A misspelled verb now warns instead of vanishing
Every diagnostic carries a machine-readable code Consumers classifying diagnostics. Twelve codes are defined
Version reporting corrected SARIF tool.driver.version reported 1.4.3 regardless of the installed version. It now reports the real one
guardlink-mcp --help and --version Both previously started the server and blocked
parse --no-pretty Compact single-line JSON, which had no flag before
report --format <bogus> now exits 1 Previously wrote nothing and exited 0