Install GuardLink
Install guardlink and initialize a project to annotate. Everything here was run against GuardLink 2.2.0.
Before you start
Section titled “Before you start”- 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.
Install
Section titled “Install”-
Create the project and install guardlink into it.
Terminal window mkdir guardlink-quickstartcd guardlink-quickstartnpm init -ynpm install --save-dev guardlinkInstalling as a dev dependency records the version in
package.json, so CI gets the same one you did.npm install -g guardlinkalso works and gives you a bareguardlinkcommand. The rest of these pages usenpx guardlink, which resolves the project-local copy. -
Confirm it runs.
Terminal window npx guardlink --version2.2.0A version number rather than an error means both binaries are on the path:
guardlink, andguardlink-mcpfor the MCP server. -
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: [] }}findUserByEmailinterpolates 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.
Initialize
Section titled “Initialize”npx guardlink init . --agent claudeDetected: javascript project "guardlink-quickstart"
Created: .guardlink/Created: .guardlink/config.jsonCreated: .guardlink/definitions.jsCreated: .guardlink/README.mdCreated: .guardlink/prompt.mdCreated: docs/GUARDLINK_REFERENCE.mdCreated: .gitignoreCreated: .gitattributesCreated: .claude/skills/guardlink-annotate-map/SKILL.mdCreated: .claude/skills/guardlink-annotate-exploitable/SKILL.mdCreated: .claude/skills/guardlink-annotate-chains/SKILL.mdCreated: .claude/skills/guardlink-annotate-diff/SKILL.mdCreated: .claude/skills/guardlink-annotate-coverage/SKILL.mdCreated: .claude/skills/guardlink-annotate-verify/SKILL.mdCreated: .claude/skills/guardlink-report-executive/SKILL.mdCreated: .claude/skills/guardlink-report-pr/SKILL.mdCreated: .claude/skills/guardlink-report-audit/SKILL.mdCreated: CLAUDE.mdCreated: .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,@threatand@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
--agentnamed- …
- CLAUDE.md live threat-model context for a coding agent
- .mcp.json MCP server registration
Directorysrc/
- …
Where annotations go
Section titled “Where annotations go”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.

