Skip to content
203 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.1/**
2 * A repository's Security overview: one row per feature, each with a
3 * status word, a line of what it does, and the one thing to do about it.
4 * Pure, so the rows are tested apart from the page (routes/repo/security-overview.tsx).
5 */
6
7export type FeatureStatus = "enabled" | "disabled" | "needs_setup" | "coming";
8
9export const STATUS_LABEL: Record<FeatureStatus, string> = {
10 enabled: "Enabled",
11 disabled: "Disabled",
12 needs_setup: "Needs setup",
13 coming: "Coming",
14};
15
16/** The Badge tone each status word is shown in. */
17export const STATUS_TONE: Record<FeatureStatus, "success" | "neutral" | "warn" | "info"> = {
18 enabled: "success",
19 disabled: "neutral",
20 needs_setup: "warn",
21 coming: "info",
22};
23
24export type RowGroup = "policy" | "findings" | "reporting";
25
26/** The one thing to do about a feature: a page to open, or a form to post. */
27export type RowAction =
28 | { kind: "link"; label: string; to: string }
29 | { kind: "post"; label: string; action: string; intent?: string; pending: string }
30 | { kind: "switch"; label: string; intent: string; on: boolean };
31
32export type OverviewRow = {
33 key: string;
34 group: RowGroup;
35 title: string;
36 status: FeatureStatus;
37 about: string;
38 /** A short hint for the status, shown on hover, when the word alone is not enough. */
39 hint?: string;
40 action: RowAction | null;
41};
42
43export type OverviewFacts = {
44 /** Under the project: `/acme/rocket`. */
45 base: string;
46 /** Whether a SECURITY.md is on the default branch, and where. */
47 policyPath: string | null;
48 /** Private vulnerability reporting. */
49 reporting: boolean;
50 /** Published advisories. */
51 advisories: number;
52 /** Security updates. */
53 upkeep: boolean;
54 codeScanning: { configured: boolean; analyses: number; entitled: boolean };
55 /** Alerts for secrets found in the history. Push protection is always on. */
56 historyAlerts: boolean;
57 /** Whether the viewer may change settings (Admin), and push (for the policy's pull request). */
58 canManage: boolean;
59 canPush: boolean;
60};
61
62/** Where a security policy may be: the first found is the one. */
63export const POLICY_PATHS = ["SECURITY.md", ".github/SECURITY.md", "docs/SECURITY.md"];
64
65/** The rows, in the order the page shows them. */
66export function overviewRows(facts: OverviewFacts): OverviewRow[] {
67 const { base } = facts;
68 const code = facts.codeScanning;
69 return [
70 {
71 key: "policy",
72 group: "policy",
73 title: "Security policy",
74 status: facts.policyPath ? "enabled" : "needs_setup",
75 about: facts.policyPath
76 ? `${facts.policyPath} on the default branch tells people how to report a vulnerability and which versions are supported.`
77 : "A SECURITY.md on the default branch tells people how to report a vulnerability and which versions are supported.",
78 action: facts.policyPath
79 ? { kind: "link", label: "View policy", to: `${base}/blob/HEAD/${facts.policyPath}` }
80 : facts.canPush
81 ? { kind: "post", label: "Set up a security policy", action: `${base}/security/policy/setup`, pending: "Opening a pull request…" }
82 : null,
83 },
84 {
85 key: "advisories",
86 group: "reporting",
87 title: "Security advisories",
88 status: "enabled",
89 about:
90 facts.advisories === 0
91 ? "Publish what this repository knows about vulnerabilities in what it ships, so people who depend on it can act."
92 : `${facts.advisories} published ${facts.advisories === 1 ? "advisory tells" : "advisories tell"} people who depend on this repository what to do.`,
93 action: { kind: "link", label: facts.canManage ? "Manage advisories" : "View advisories", to: `${base}/security/advisories` },
94 },
95 {
96 key: "reporting",
97 group: "reporting",
98 title: "Private vulnerability reporting",
99 status: facts.reporting ? "enabled" : "disabled",
100 about: facts.reporting
101 ? "Anyone who can see this repository can report a vulnerability to its maintainers privately, from its Security tab."
102 : "Let anyone who can see this repository report a vulnerability to its maintainers privately, instead of in an issue.",
103 action: facts.canManage ? { kind: "switch", label: "Private vulnerability reporting", intent: "reporting", on: facts.reporting } : null,
104 },
105 {
106 key: "dependency_alerts",
107 group: "findings",
108 title: "Dependency alerts",
109 status: "enabled",
110 about: "Every package the lockfiles resolve is checked for known vulnerabilities, on every push to the default branch and daily.",
111 action: { kind: "link", label: "View alerts", to: `${base}/security/vulnerabilities` },
112 },
113 {
114 key: "security_updates",
115 group: "findings",
116 title: "Security updates",
117 status: facts.upkeep ? "enabled" : "disabled",
118 about: "A pull request upgrades each vulnerable dependency that has a fix; it lands through the branch's required checks.",
119 action: facts.canManage ? { kind: "switch", label: "Security updates", intent: "upkeep", on: facts.upkeep } : null,
120 },
121 {
122 key: "code_scanning",
123 group: "findings",
124 title: "Code scanning",
125 status: code.analyses > 0 ? "enabled" : "needs_setup",
126 about:
127 code.analyses > 0
128 ? "Your static analysis tools' results, uploaded as SARIF: alerts on the default branch and a check on pull requests."
129 : "Upload your static analysis tools' SARIF results from a workflow: alerts on the default branch and a check on pull requests.",
130 hint: !code.entitled ? "On a private repository, code scanning comes with the g1t plan." : undefined,
131 action:
132 code.analyses > 0 || code.configured
133 ? { kind: "link", label: "View alerts", to: `${base}/security/code-scanning` }
134 : facts.canManage && code.entitled
135 ? { kind: "post", label: "Set up code scanning", action: `${base}/security/code-scanning/setup`, pending: "Opening a pull request…" }
136 : { kind: "link", label: "Learn more", to: `${base}/security/code-scanning` },
137 },
138 {
139 key: "secret_scanning",
140 group: "findings",
141 title: "Secret scanning",
142 status: "enabled",
143 about: facts.historyAlerts
144 ? "Push protection refuses every push that adds a secret, and secrets already in the history open alerts."
145 : "Push protection refuses every push that adds a secret. Alerts for secrets already in the history are off.",
146 hint: "Push protection is always on.",
147 action: { kind: "link", label: "View alerts", to: `${base}/security/secret-scanning` },
148 },
149 {
150 key: "code_quality",
151 group: "findings",
152 title: "Code quality",
153 status: "coming",
154 about: "Maintainability and reliability findings beside the security ones. Not built yet.",
155 action: null,
156 },
157 ];
158}
159
160/** The rows of one group, in order. */
161export function rowsIn(rows: OverviewRow[], group: RowGroup): OverviewRow[] {
162 return rows.filter((row) => row.group === group);
163}
164
165/**
166 * The SECURITY.md "Set up a security policy" proposes: how to report, and
167 * which versions are supported, for the maintainers to finish.
168 */
169export function securityPolicyTemplate(repo: string, reporting: boolean, base: string): string {
170 const report = reporting
171 ? `Report it privately at https://g1t.sh${base}/security/report. You can report without an account; leave an email address so the maintainers can answer you.`
172 : `Email the maintainers (fill in the address here) rather than opening an issue, so the problem is not public before a fix is.`;
173 return `# Security policy
174
175## Reporting a vulnerability
176
177If you find a vulnerability in ${repo}, do not open an issue about it.
178
179${report}
180
181Say what is affected, how to reproduce it and, if you can, how bad you think it is. We aim to answer within a few days and will keep you informed as we work on a fix. If you want to be credited in the advisory, say so.
182
183## Supported versions
184
185| Version | Supported |
186| ------- | --------- |
187| latest | Yes |
188
189Fill in which releases get security fixes.
190`;
191}
192
193/** The pull request's body for the policy. */
194export function securityPolicyPullBody(path: string): string {
195 return `Adds \`${path}\`, which tells people how to report a vulnerability and which versions get security fixes. Edit the address and the versions before merging.`;
196}
197
198/** A branch name for the policy that is not taken. */
199export function securityPolicyBranch(taken: string[]): string {
200 let name = "add-security-policy";
201 for (let n = 2; taken.includes(name); n++) name = `add-security-policy-${n}`;
202 return name;
203}