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 →
guardlink status .
GuardLink Status: guardlink
Files scanned:87
Files annotated:66
Files unannotated:21
Annotations:441
Unmitigated:15
3 high · 6 medium · 6 low
Assets:16
Threats:15
Controls:12
Actors:3
Mitigations:68
Exposures:80
Flows:106
Boundaries:9

Truncated. The real block also reports acceptances, entitlements, transfers, validations, audits, ownership, data handling, assumptions, features, comments, and shields. GuardLink runs on its own codebase, and it reports 15 open exposures rather than a clean sheet.

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

Written once at kickoff. Nothing in the pipeline reads it, so nothing notices when it stops being true.

A scanner's output

Two hundred findings with no sense of which asset matters, what already defends it, or what someone decided to live with.

Last year's pen test

A PDF in a shared drive, accurate on the day it was written and about a codebase that has since moved.

Move the claims into the repository and the problem changes shape. A claim beside the code is one a reviewer sees in the diff, a parser can check, and CI can refuse.

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.

Declare a thing
@asset@threat@control@actor

Named once, each with an #id the rest of the model references.

Open a finding
@exposes

The only verb that creates work.

Close it
@mitigates@accepts

A control handles it, or a person decides to carry it. Both are recorded; neither is silence.

Record context
@flows@boundary@handles@confirmed@entitles@audit@assumes@owns@validates@transfers@feature@comment@shield

Facts a reader, a report, or a downstream tool needs. None of them opens or closes anything.

src/reports.js
// @asset   App.Reports  (#reports)
// @threat  Scraping     (#scrape) [medium]

// @exposes #reports to #scrape [medium]
//   -- "No rate limit on export"
export function exportAll(userId) {
  return db.query(
    'SELECT * FROM reports WHERE user = $1',
    [userId]
  );
}
That @exposes is one of the 15 findings the status block above counts. It stays open until a @mitigates names a control or a person signs an @accepts.
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, counts, coverage, severity breakdown

Executive summary. A risk grade, the counts behind it, mitigation coverage, and the severity split. 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