Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

175 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { AlertActivity, SecretFinding, Vulnerability } from "@g1t/contracts";
5
6import {
7 alertActivity,
8 alertCapability,
9 compareVersions,
10 countByState,
11 groupByPackage,
12 highestFix,
13 latestUpdate,
14 parseAlertState,
15 splitSecrets,
16 tabOf,
17 worstSeverity,
18} from "./security-alerts.ts";
19
20const secret = (over: Partial<SecretFinding> = {}): SecretFinding => ({
21 id: "sec_1",
22 repoId: "r",
23 kind: "aws_access_key",
24 label: "an AWS access key",
25 path: "src/config.ts",
26 line: 4,
27 commit: "abcdef1234",
28 preview: "AKIA…WXYZ",
29 status: "open",
30 source: "history",
31 foundBy: null,
32 foundAt: "2026-10-01T10:00:00Z",
33 decidedBy: null,
34 reason: null,
35 decidedAt: null,
36 state: "open",
37 ...over,
38});
39
40const vuln = (over: Partial<Vulnerability> = {}): Vulnerability => ({
41 id: "vul_1",
42 repoId: "r",
43 ecosystem: "npm",
44 package: "lodash",
45 version: "4.17.20",
46 manifest: "package-lock.json",
47 advisory: "GHSA-1",
48 osvId: "GHSA-1",
49 summary: "Prototype pollution",
50 severity: "high",
51 fixedVersion: "4.17.21",
52 status: "open",
53 item: null,
54 foundAt: "2026-10-01T10:00:00Z",
55 fixedAt: null,
56 state: "open",
57 ...over,
58});
59
60test("the state parameter falls back to open", () => {
61 assert.equal(parseAlertState(null), "open");
62 assert.equal(parseAlertState("dismissed"), "dismissed");
63 assert.equal(parseAlertState("fixed"), "fixed");
64 assert.equal(parseAlertState("nonsense"), "open");
65});
66
67test("ids say which tab and which role an alert takes", () => {
68 assert.equal(tabOf("sec_9"), "secrets");
69 assert.equal(tabOf("vul_9"), "dependencies");
70 assert.equal(alertCapability("sec_9"), "security_alerts");
71 assert.equal(alertCapability("vul_9"), "security_alerts");
72});
73
74test("alerts are counted by state", () => {
75 assert.deepEqual(countByState([secret(), secret({ state: "dismissed" }), secret({ state: "fixed" }), secret()]), {
76 open: 2,
77 dismissed: 1,
78 fixed: 1,
79 });
80});
81
82test("likely test values are listed apart from real secrets", () => {
83 const real = secret({ id: "sec_real" });
84 const fake = secret({ id: "sec_fake", testValue: "AWS's documented example key" });
85 const { real: shown, tests } = splitSecrets([fake, real]);
86 assert.deepEqual(
87 shown.map((s) => s.id),
88 ["sec_real"],
89 );
90 assert.deepEqual(
91 tests.map((s) => s.id),
92 ["sec_fake"],
93 );
94});
95
96test("packages group their alerts and take the worst severity", () => {
97 const groups = groupByPackage([
98 vuln({ id: "vul_1", severity: "medium" }),
99 vuln({ id: "vul_2", package: "express" }),
100 vuln({ id: "vul_3", severity: "critical", fixedVersion: "4.17.3" }),
101 ]);
102 assert.deepEqual(
103 groups.map((g) => g.name),
104 ["lodash", "express"],
105 );
106 assert.equal(worstSeverity(groups[0].vulns), "critical");
107 assert.equal(highestFix(groups[0].vulns), "4.17.21");
108});
109
110test("versions compare number by number", () => {
111 assert.ok(compareVersions("4.17.10", "4.17.9") > 0);
112 assert.ok(compareVersions("1.2.0", "1.10.0") < 0);
113 assert.equal(highestFix([vuln({ fixedVersion: null })]), null);
114});
115
116test("the newest security update wins", () => {
117 const older = { state: "superseded" as const, target: "4.17.20", branch: null, pull: 3, item: null, error: null, updatedAt: "2026-10-01T00:00:00Z" };
118 const newer = { ...older, state: "open" as const, target: "4.17.21", pull: 14, updatedAt: "2026-10-02T00:00:00Z" };
119 assert.equal(latestUpdate([vuln({ update: older }), vuln({ update: newer }), vuln()])?.pull, 14);
120 assert.equal(latestUpdate([vuln()]), null);
121});
122
123const row = (over: Partial<AlertActivity>): AlertActivity => ({
124 id: "act_1",
125 alertId: "sec_1",
126 action: "dismissed",
127 actor: "syntaqx",
128 reason: "used_in_tests",
129 comment: "A fixture.",
130 number: null,
131 at: "2026-10-03T00:00:00Z",
132 ...over,
133});
134
135test("a secret's activity starts with when it was found", () => {
136 const entries = alertActivity(secret({ source: "push", status: "blocked", foundBy: "ana" }), [row({}), row({ id: "act_2", alertId: "sec_other" })]);
137 assert.deepEqual(
138 entries.map((e) => [e.actor, e.text]),
139 [
140 ["ana", "pushed it, and the push was refused"],
141 ["syntaqx", "dismissed it"],
142 ],
143 );
144 assert.equal(entries[1].reason, "used_in_tests");
145});
146
147test("an older decision without a row is shown from the alert itself", () => {
148 const entries = alertActivity(
149 secret({ status: "allowed", state: "dismissed", decidedBy: "ana", decidedAt: "2026-10-02T00:00:00Z", reason: "Docs example." }),
150 [],
151 );
152 assert.equal(entries.length, 2);
153 assert.equal(entries[0].text, "Found in the history");
154 assert.equal(entries[0].actor, null);
155 assert.deepEqual([entries[1].actor, entries[1].comment, entries[1].reason], ["ana", "Docs example.", "false_positive"]);
156 // A row for the dismissal replaces it.
157 const covered = alertActivity(secret({ status: "allowed", state: "dismissed", decidedBy: "ana", decidedAt: "2026-10-02T00:00:00Z" }), [
158 row({ actor: "ana" }),
159 ]);
160 assert.equal(covered.filter((e) => e.text === "dismissed it").length, 1);
161});
162
163test("a dependency's activity links its pull request and work item", () => {
164 const entries = alertActivity(vuln({ state: "fixed", status: "fixed", fixedAt: "2026-10-05T00:00:00Z" }), [
165 row({ id: "a", alertId: "vul_1", action: "update_opened", actor: "g1t", reason: null, comment: null, number: 14, at: "2026-10-02T00:00:00Z" }),
166 row({ id: "b", alertId: "vul_1", action: "update_needs_code", actor: "g1t", reason: null, comment: null, number: 15, at: "2026-10-03T00:00:00Z" }),
167 row({ id: "c", alertId: "vul_1", action: "update_merged", actor: "g1t", reason: null, comment: null, number: 14, at: "2026-10-04T00:00:00Z" }),
168 ]);
169 assert.deepEqual(
170 entries.map((e) => e.ref),
171 [null, { kind: "pull", number: 14 }, { kind: "item", number: 15 }, { kind: "pull", number: 14 }],
172 );
173 // A merged update already says it was fixed.
174 assert.ok(!entries.some((e) => e.text === "found it no longer vulnerable"));
175});