Threat modeling
at the speed of code.

Security decisions live as annotations beside the code they describe. Your coding agent writes them, the CLI checks them, and CI fails when an exposure has nothing answering it.

Read the docsGitHub →
~/src/guardlink · src/mcp/server.ts
26 * Resources:
27 * guardlink://model — Full ThreatModel JSON
28 * guardlink://definitions — Assets, threats, controls
29 * guardlink://unmitigated — Unmitigated exposures list
30 *
31 * Transport: stdio (for Claude Code .mcp.json, Cursor, etc.)
32 *
33 * @comment -- "No guardlink_entitlement_accept tool exists on purpose: proposing is an agent's job, granting authority is not"
34 * @flows MCPClient -> #mcp via tool_call -- "Tool invocation input"
35 * @flows #mcp -> FileSystem via writeFile -- "Report/dashboard output"
36 * @flows #mcp -> #llm-client via generateThreatReport -- "LLM API call path"
37 * @flows #mcp -> MCPClient via resource -- "Threat model data output"
38 * @boundary #mcp and MCPClient (#mcp-tool-boundary) -- "Trust boundary at tool argument parsing"
39 * @handles internal on #mcp -- "Processes project annotations and threat model data"
40 * @feature "MCP Integration" -- "Model Context Protocol server for AI agent tooling"
41 * @entitles #mcp-agent to read-threat-model on #mcp against #data-exposure -- "By design: guardlink mcp exists to hand a connected coding agent the threat model — that disclosure is the product, not a leak…"
42 * @comment -- "Entitlement accepted by zippon on 2026-08-10 via guardlink entitle"
43 */
44
45import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
46import { z } from 'zod';
47import { parseProject, findDanglingRefs, findUnmitigatedExposures } from '../parser/index.js';
TerminalProblemsOutput zsh
~/src/guardlink main
mainguardlink 2.0.0429 annotations13 unmitigatedLn 33, Col 1Spaces: 2UTF-8LFTS TypeScript
See it in your theme
SYS/01 · PROBLEM

Security knowledge lives outside the code. That is the whole bug.

Every one of these is a real artifact that a real team paid for, and every one of them decays the moment someone opens an editor.

A page in Confluence
▤Security › Paymentsedited 14 months ago
Payments — Threat Model
3. Session handling
Sessions are server-side. The token never leaves the
database, so a stolen cookie cannot be replayed off-host.
4. Rate limiting
Covered by the edge proxy. See the platform runbook.
0commits since
0pipelines read this page
A scanner's output
$scan --all --format textexit 0
HIGH CWE-89 src/orders/query.ts:114
HIGH CWE-79 src/web/render.tsx:38
MEDIUM CWE-352 src/web/form.tsx:21
MEDIUM CWE-200 src/api/user.ts:73
LOW CWE-693 src/web/headers.ts:9
… 195 more
0findings
0say what already defends them
Last year's pen test
▣pentest-2024-final-v3.pdf1 / 48
WEB APPLICATION
PENETRATION TEST
Engagement 3–14 March 2024
Prepared for Payments Platform
Classification Confidential
0days old
0commits since
Move the claim into the repository and the problem changes shape.
src/tui/commands.ts@@ -9,5 +9,7 @@+2
9 * @exposes #tui to #arbitrary-write [high] cwe:CWE-73 -- "/report, /sarif, /dashboard write files"
10 * @mitigates #tui against #arbitrary-write using #path-validation -- "Output paths resolved relative to project root"
+11 * @exposes #tui to #cmd-injection [high] cwe:CWE-78 -- "/annotate and /threat-report spawn child processes"
+12 * @audit #tui -- "Child process spawning delegated to agents/launcher.ts"
13 * @exposes #tui to #api-key-exposure [high] cwe:CWE-798 -- "/model handles API key input and storage"

Two lines in a pull request. The reviewer reads them beside the code that caused them, a parser checks that #cmd-injection and #tui are declared things, and the exposure stays open in the model until a control answers it.

What that buys, and what it costs
SYS/02 · MECHANISM

Three commands, and the model stops rotting.

A threat model goes stale because it lives somewhere the code does not. GuardLink puts the claims in the repository and makes them checkable, so a stale claim is a failing build rather than a quiet lie.

SYS/03 · LANGUAGE

Twenty verbs. Two of them close a finding.

GAL is a small grammar for security intent that works in any language, because it lives in comments. Its whole job is to make a claim about code checkable.

Small vocabulary. Explicit intent.

Browse the verbs, or get the full reference in your terminal.

Relationships

Connect threats to assets. Record the controls and decisions that resolve them.

@exposesopens finding
Marks an asset exposed to a threat at this line. Every one creates a finding.
@mitigatescloses finding
Names the control that answers a threat on an asset, and closes the finding.
@confirmed
Records a threat as verified exploitable, with the evidence that proved it.
@acceptscloses finding
Records a decision to carry the risk instead, which also closes the finding.
@entitles
States an actor is entitled to a capability by design. An agent proposes; a person accepts.
@transfers
Moves responsibility for a threat to another asset or team.
Only @mitigates and @accepts close a finding.
SYS/04 · OUTPUT

What comes out the other end.

One command turns the model into a self-contained HTML dashboard. Every screen below is real output, and the dashboard itself is open on this site, not a picture of one.

GuardLink dashboard executive summary: risk grade D, open threats, mitigation coverage, and a what-to-do-next list

Executive summary. A risk grade, the counts behind it, and what to do next — each item carrying the command that does it. This is the page a security lead opens.

Open this view
SYS/05 · AGENTS

Written by the thing that writes the code.

guardlink init detects your coding agent and wires two things: an MCP server it can query, and a rule in its instruction file telling it to annotate security-relevant code as it writes it.

agents guardlink init configures
  • Claude Code
    CLAUDE.md + .mcp.json
    MCP + rule
  • Cursor
    .cursorrules + .cursor/mcp.json
    MCP + rule
  • Windsurf
    .windsurfrules + .windsurf/mcp.json
    MCP + rule
  • Cline
    .clinerules + .cline/mcp.json
    MCP + rule
  • Codex
    AGENTS.md
    rule only
  • GitHub Copilot
    .github/copilot-instructions.md
    rule only

24 tools the agent can call

Read the model, validate it, suggest annotations for a snippet, query threats by keyword, generate a report or a dashboard, export SARIF, diff against a git ref. The agent asks “what threatens #api?” before it writes code that touches the API.

and two it deliberately cannot

An agent may find an exposure and propose an entitlement. It may not accept a risk, and it may not decide a caller was always meant to hold a power. Those two statements close a finding or excuse one, so the MCP server refuses to write @accepts or @entitles at all. A person makes those calls at a terminal, and their name goes on the record.

SYS/06 · GATE

A threat model that can fail a build.

A document nobody has to satisfy drifts. GuardLink runs as a check, so a new route with no annotations, or a control removed from under an exposure, shows up in the diff and stops the merge.

.github/workflows/guardlink.yml
- name: Install GuardLink
  run: npm install -g guardlink

- name: Validate annotations
  run: guardlink validate .

- name: Threat model diff
  run: guardlink diff --from origin/main --to HEAD

- name: Export SARIF
  run: guardlink sarif . -o guardlink.sarif

- uses: github/codeql-action/upload-sarif@v3
  with: { sarif_file: guardlink.sarif }

The full workflow, with PR comments and SARIF upload, ships as examples/github-action.yml. Multi-repo setups get two more in examples/ci/.

guardlink ci . · its own repository
Unmitigated exposures: 15 (high 3, medium 6, low 6)
Anchor drift: 0 (no anchored @source blocks to check)
 
⚠ 15 unmitigated exposure(s):
#mcp → #cmd-injection [high] (src/mcp/index.ts:6)
#tui → #cmd-injection [high] (src/tui/commands.ts:11)
#mcp → #prompt-injection [medium] (src/mcp/server.ts:36)
#mcp → #data-exposure [medium] (src/mcp/server.ts:40)
#suggest → #dos [low] (src/mcp/suggest.ts:16)
…

Advisory by default and exits 0; --strict turns it into a gate. Ten more lines are truncated here.

New route, no annotations
the diff shows the gap before review does
Control removed
an exposure reopens, and --fail-on-new blocks the PR
Exposure accepted
recorded under a person's name, not deleted
Every command and flag
SYS/07 · START

Model your first threat
in about ten minutes.

Node 18 or newer is the only requirement. init detects your agent and writes the config; everything after that is three commands.

Source