Install and annotate one function
Install GuardLink, annotate a single function, and finish with a threat model that validates clean in CI.
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:
// @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:
npx guardlink ci . --strictUnmitigated 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.
npm install --save-dev guardlink, Node.js 18 or newer.annotate, and it does so by
handing a prompt to a coding agent you already have.@mitigates, @exposes, @transfers, and
@accepts come from
ThreatSpec (2015 to 2020).Four commands, run in the repository you want annotated:
npm install --save-dev guardlinknpx guardlink init .npx guardlink annotate "annotate the routes" . # hands the work to your coding agentnpx 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.
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.
// @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.
Annotate a real repository
Vocabulary first, then the seams, then a gate that catches drift.
Give an agent the model
Wire the MCP server into a coding agent: 24 tools and 3 resources over stdio.
Read the specification
Four conformance levels, a CC-BY-4.0 grammar, and a ten-year lineage.
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.
| 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 |