Skip to content
203 linesCodeBlameRaw
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}