Skip to content

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.

  • GuardLink 2.5.0 installed. See Install GuardLink.
  • A repository you can edit. The example below uses a local fixture and sends no traffic.

Create src/users.js with these declarations and the code they describe:

src/users.js
// @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.

Terminal window
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.

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:

Terminal window
guardlink lint . --json

The 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.

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.