Skip to content

Install GuardLink

Install guardlink and initialize a project to annotate. Everything here was run against GuardLink 2.2.0.

  • Node.js 18 or newer. The transcripts are from Node 24.14.0 with npm 11.9.0.
  • A directory you can delete afterwards. The worked example builds its own project, so nothing here touches code you care about.
  1. Create the project and install guardlink into it.

    Terminal window
    mkdir guardlink-quickstart
    cd guardlink-quickstart
    npm init -y
    npm install --save-dev guardlink

    Installing as a dev dependency records the version in package.json, so CI gets the same one you did. npm install -g guardlink also works and gives you a bare guardlink command. The rest of these pages use npx guardlink, which resolves the project-local copy.

  2. Confirm it runs.

    Terminal window
    npx guardlink --version
    2.2.0

    A version number rather than an error means both binaries are on the path: guardlink, and guardlink-mcp for the MCP server.

  3. Write the code you are about to describe. Two files: one that reads user records out of a database, and the query helper it calls.

    src/users.js
    import { query } from './db.js'
    export async function findUserByEmail(email) {
    return query(`SELECT id, email, phone FROM users WHERE email = '${email}'`)
    }
    src/db.js
    export async function query(sql) {
    return { sql, rows: [] }
    }

    findUserByEmail interpolates a caller-supplied string into SQL and returns two pieces of personal data. Both of those are facts about the code that no type signature records, and both are what you are about to write down.

Terminal window
npx guardlink init . --agent claude
Detected: javascript project "guardlink-quickstart"
Created: .guardlink/
Created: .guardlink/config.json
Created: .guardlink/definitions.js
Created: .guardlink/README.md
Created: .guardlink/prompt.md
Created: docs/GUARDLINK_REFERENCE.md
Created: .gitignore
Created: .gitattributes
Created: .claude/skills/guardlink-annotate-map/SKILL.md
Created: .claude/skills/guardlink-annotate-exploitable/SKILL.md
Created: .claude/skills/guardlink-annotate-chains/SKILL.md
Created: .claude/skills/guardlink-annotate-diff/SKILL.md
Created: .claude/skills/guardlink-annotate-coverage/SKILL.md
Created: .claude/skills/guardlink-annotate-verify/SKILL.md
Created: .claude/skills/guardlink-report-executive/SKILL.md
Created: .claude/skills/guardlink-report-pr/SKILL.md
Created: .claude/skills/guardlink-report-audit/SKILL.md
Created: CLAUDE.md
Created: .mcp.json
✓ GuardLink initialized. Next steps:
1. Review .guardlink/definitions.js — remove threats/controls not relevant to your project
2. Add annotations in .guardlink/annotations/<source path>.gal sidecars — NOT in source files (or ask your coding agent to do it)
3. Run: guardlink validate .
4. Run: guardlink parse . -o .guardlink/report.json
Sidecars are read by GuardLink and by nothing else. That export is how
the code graph, dashboards and CI see them — without it they read zero.
guardlink validate . and guardlink status . both say when it is missing or stale.

--agent decides which coding-agent instruction files get written and kept in sync. It takes a comma-separated list of claude, cursor, codex, copilot, windsurf, cline, or none. Substitute whichever you use. Without the flag, an interactive terminal shows a picker.

The project name comes from the directory unless package.json carries a non-placeholder name.

  • Directoryguardlink-quickstart/
    • Directory.guardlink/
      • config.json project name, language, annotation mode
      • definitions.js every @asset, @threat and @control
      • README.md what this directory is, regenerated by guardlink sync
      • prompt.md project description, feeds the report
    • Directorydocs/
      • GUARDLINK_REFERENCE.md
    • Directory.claude/
      • Directoryskills/ nine task skills, written for whichever agents --agent named
        • …
    • CLAUDE.md live threat-model context for a coding agent
    • .mcp.json MCP server registration
    • Directorysrc/
      • …

init defaults to external mode: annotations live in .gal sidecar files under .guardlink/annotations/, mirroring the source path, not in the source files themselves. config.json records that choice as "annotation_mode": "external".

flowchart TB
    SRC["src/users.js<br/>line 3, findUserByEmail"]
    GAL[".guardlink/annotations/src/users.js.gal<br/>the mirrored path"]
    ANC["@source file:src/users.js line:3 symbol:findUserByEmail<br/>the anchor, first line of the sidecar"]
    MOD["the parsed model"]
    OUT["every consumer reports src/users.js:3"]

    SRC -.->|"mirror the path, append .gal"| GAL
    GAL --> ANC
    ANC -->|"resolves back to the code position"| MOD
    MOD --> OUT

    class ANC emphasis

The mirrored path is what guardlink looks for, and the @source header is what turns a line in a sidecar into a position in your code. Get either wrong and the annotations parse into nothing, which is the most common first-run problem on the next page.

Source file Its annotations
src/users.js .guardlink/annotations/src/users.js.gal
internal/db/query.go .guardlink/annotations/internal/db/query.go.gal

guardlink init . --mode inline puts them in source comments instead, with no sidecar and no @source header. Both modes parse to the same model, and they differ in what survives a refactor. Why annotations live in code covers the trade.

Write your first annotation takes this project to a validated threat model, a markdown report, and a CI check that exits 0.