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 | ||
| 7 | export type FeatureStatus = "enabled" | "disabled" | "needs_setup" | "coming"; | |
| 8 | ||
| 9 | export 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. */ | |
| 17 | export const STATUS_TONE: Record<FeatureStatus, "success" | "neutral" | "warn" | "info"> = { | |
| 18 | enabled: "success", | |
| 19 | disabled: "neutral", | |
| 20 | needs_setup: "warn", | |
| 21 | coming: "info", | |
| 22 | }; | |
| 23 | ||
| 24 | export type RowGroup = "policy" | "findings" | "reporting"; | |
| 25 | ||
| 26 | /** The one thing to do about a feature: a page to open, or a form to post. */ | |
| 27 | export 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 | ||
| 32 | export 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 | ||
| 43 | export 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. */ | |
| 63 | export const POLICY_PATHS = ["SECURITY.md", ".github/SECURITY.md", "docs/SECURITY.md"]; | |
| 64 | ||
| 65 | /** The rows, in the order the page shows them. */ | |
| 66 | export 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. */ | |
| 161 | export 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 | */ | |
| 169 | export 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 | ||
| 177 | If you find a vulnerability in ${repo}, do not open an issue about it. | |
| 178 | ||
| 179 | ${report} | |
| 180 | ||
| 181 | Say 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 | ||
| 189 | Fill in which releases get security fixes. | |
| 190 | `; | |
| 191 | } | |
| 192 | ||
| 193 | /** The pull request's body for the policy. */ | |
| 194 | export 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. */ | |
| 199 | export 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 | } |