Track risk with tickets
Record the ticket beside the risk it tracks. GuardLink stores the reference in the model; it does not connect to your tracker. Removing the annotation removes the ticket record. A ticket neither mitigates nor accepts an exposure.
Before you start
Section titled “Before you start”- GuardLink 2.5.0 installed. See Install GuardLink.
- A repository you can edit. The example below uses a local fixture and sends no traffic.
Record the ticket
Section titled “Record the ticket”Create src/users.js with these declarations and the code they describe:
// @asset App.Users (#users) -- "User records"// @threat SQL_Injection (#sqli) [critical] -- "Input reaches SQL"// @control Prepared_Statements (#prepared) -- "Bind query parameters"// @exposes #users to #sqli [critical] -- "findUser(email) interpolates attacker email into SELECT without parameter binding"// @ticket #sqli on #users SEC-42 sla "P1" by security until 2026-11-30 -- "Bind query parameters"export function findUser(email) { return `SELECT * FROM users WHERE email = '${email}'`; }Replace SEC-42 with your tracker reference. It may be one bare token or a
quoted string, including an HTTP URL. sla preserves your tracker’s label; it
does not translate P1 into a GuardLink severity. by records the assignee and
until records a calendar date.
guardlink lookup "ticket SEC-42"{ "query": "ticket SEC-42", "type": "tickets", "count": 1, "results": [ { "ref": "SEC-42", "threat": "#sqli", "asset": "#users", "sla": "P1", "assignee": "security", "due": "2026-11-30", "state": "open", "confirmed": false, "description": "Bind query parameters", "file": "src/users.js", "line": 5 } ], "matched_via": "exact", "matched_against": "sec-42"}state: "open" describes the exposure’s current coverage, not the tracker
status. The other states are mitigated, accepted and unexposed.
Keep the record after a fix
Section titled “Keep the record after a fix”After you fix the query, add the mitigation beside the exposure:
// @mitigates #users against #sqli using #prepared -- "findUser now binds email"Then check the annotations:
guardlink lint . --jsonThe fixture returned this summary, with exit 0:
"summary": { "errors": 0, "warnings": 0, "infos": 1, "violations": 1 },Truncated to the summary. The violation is ticket-on-mitigated, at
level: "info". Info never fails the gate. Keep the annotation as a record or
remove it after you close the tracker ticket. GuardLink does not close it for you.
Read tickets in other outputs
Section titled “Read tickets in other outputs”The model carries tickets. SARIF exposure and confirmed results carry
properties.tickets for their asset/threat pair. In the fixture’s SARIF, that
property contains:
[ { "ref": "SEC-42", "sla": "P1", "assignee": "security", "due": "2026-11-30" }]Changing the fixture’s sla from P1 to P2 appears in guardlink diff as a
modified ticket, with riskDelta: "unchanged" and no new unmitigated exposure.
Ticket metadata routes the work; it does not decide the security verdict.
The dashboard shows a Tickets page when the model has tickets. In the
fixture it lists SEC-42, its state, SLA, assignee and due date. An HTTP(S)
reference becomes a link; other references remain text.
For every command and option, see the CLI reference and the GAL reference.

