Skip to content
89 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

A repository's Security overview is one row per feature in three groups, each with a status word (Enabled, Disabled, Needs setup, Coming), a line on what it does and the one thing to do about it: the security policy (View policy, or Set up a security policy, which commits a starting SECURITY.md on a branch and opens it as a pull request), dependency alerts, security updates (the switch, kept on Vulnerabilities and now in Settings too), code scanning (Enabled once an analysis was uploaded), secret scanning (push protection always on) and code quality as Coming; and under Reporting, advisories and the private vulnerability reporting switch. Anyone who can see a public repository but has no role on it sees the public face at the same address: the policy, the published advisories and, when it is on, Report a vulnerability. Advisories get their pages (the list with its state filter, one advisory with Publish, Withdraw and Edit, a form for a new draft or changes) and reports theirs (new ones first, each accepted into a draft advisory or declined), with a Reports page beside Advisories and an Advisories tab; the public report form takes an email when signed out and counts against the front door's heavy limit. Security settings gain Alerts for secrets in the history and Private vulnerability reporting. Settings → Notifications gains a Security section: critical and high alerts at once (always, for the workspaces one owns or manages security for) and the rest in a daily or weekly digest, at once, or not at all, with security alerts now among the email reasons. The notifications guide says how security alerts arrive and what the digest is, the security guide the overview's rows and the public face, secret protection the history alerts switch, webhooks the summary and advisory events, and a new guide covers advisories and private reporting; the report_settings method lets the public page know whether a repository takes reports.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 type OverviewFacts,
6 POLICY_PATHS,
7 overviewRows,
8 rowsIn,
9 securityPolicyBranch,
10 securityPolicyPullBody,
11 securityPolicyTemplate,
12} from "./security-overview.ts";
13
14const facts = (over: Partial<OverviewFacts> = {}): OverviewFacts => ({
15 base: "/acme/rocket",
16 policyPath: null,
17 reporting: false,
18 advisories: 0,
19 upkeep: true,
20 codeScanning: { configured: false, analyses: 0, entitled: true },
21 historyAlerts: true,
22 canManage: true,
23 canPush: true,
24 ...over,
25});
26
27test("every feature is one row with a status word and one thing to do", () => {
28 const rows = overviewRows(facts());
29 assert.deepEqual(
30 rows.map((row) => [row.key, row.status]),
31 [
32 ["policy", "needs_setup"],
33 ["advisories", "enabled"],
34 ["reporting", "disabled"],
35 ["dependency_alerts", "enabled"],
36 ["security_updates", "enabled"],
37 ["code_scanning", "needs_setup"],
38 ["secret_scanning", "enabled"],
39 ["code_quality", "coming"],
40 ],
41 );
42 // No policy: a pull request adds one, for someone who can push.
43 assert.deepEqual(rows[0].action, { kind: "post", label: "Set up a security policy", action: "/acme/rocket/security/policy/setup", pending: "Opening a pull request…" });
44 // Code scanning not set up: the starter workflow, for an admin on an entitled repository.
45 assert.equal(rows[5].action?.kind, "post");
46 // Switches are for admins; the words say what they switch.
47 assert.deepEqual(rows[2].action, { kind: "switch", label: "Private vulnerability reporting", intent: "reporting", on: false });
48 assert.deepEqual(rows[4].action, { kind: "switch", label: "Security updates", intent: "upkeep", on: true });
49 // Code quality is honest about not being there.
50 assert.equal(rows[7].action, null);
51 assert.equal(rowsIn(rows, "findings").length, 5);
52 assert.equal(rowsIn(rows, "reporting").length, 2);
53});
54
55test("what is on reads as enabled, with a link to it", () => {
56 const rows = overviewRows(facts({ policyPath: "SECURITY.md", reporting: true, advisories: 2, codeScanning: { configured: true, analyses: 3, entitled: true } }));
57 assert.equal(rows[0].status, "enabled");
58 assert.deepEqual(rows[0].action, { kind: "link", label: "View policy", to: "/acme/rocket/blob/HEAD/SECURITY.md" });
59 assert.equal(rows[1].about, "2 published advisories tell people who depend on this repository what to do.");
60 assert.equal(rows[2].status, "enabled");
61 assert.equal(rows[5].status, "enabled");
62 assert.deepEqual(rows[5].action, { kind: "link", label: "View alerts", to: "/acme/rocket/security/code-scanning" });
63});
64
65test("someone who cannot change settings sees the rows without the switches", () => {
66 const rows = overviewRows(facts({ canManage: false, canPush: false, codeScanning: { configured: false, analyses: 0, entitled: false } }));
67 assert.equal(rows[0].action, null);
68 assert.equal(rows[2].action, null);
69 assert.equal(rows[4].action, null);
70 // Code scanning on a private repository without the plan: a page that says so, not a form.
71 assert.deepEqual(rows[5].action, { kind: "link", label: "Learn more", to: "/acme/rocket/security/code-scanning" });
72 assert.match(rows[5].hint ?? "", /g1t plan/);
73 assert.equal(rows[6].hint, "Push protection is always on.");
74 assert.match(overviewRows(facts({ historyAlerts: false }))[6].about, /history are off/);
75});
76
77test("the policy template says how to report and names the private form when it is on", () => {
78 const withForm = securityPolicyTemplate("acme/rocket", true, "/acme/rocket");
79 assert.match(withForm, /^# Security policy/);
80 assert.match(withForm, /https:\/\/g1t\.sh\/acme\/rocket\/security\/report/);
81 assert.match(withForm, /## Supported versions/);
82 const without = securityPolicyTemplate("acme/rocket", false, "/acme/rocket");
83 assert.doesNotMatch(without, /security\/report/);
84 assert.match(without, /Email the maintainers/);
85 assert.match(securityPolicyPullBody("SECURITY.md"), /`SECURITY\.md`/);
86 assert.equal(securityPolicyBranch([]), "add-security-policy");
87 assert.equal(securityPolicyBranch(["add-security-policy", "add-security-policy-2"]), "add-security-policy-3");
88 assert.equal(POLICY_PATHS[0], "SECURITY.md");
89});