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.
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 1 | import type { ServiceBinding } from "./clients"; |
| 2 | import type { User, Viewer } from "./identity"; | |
| 3 | import type { RepoPath } from "./repos"; | |
| 4 | import type { Result } from "./result"; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 5 | import type { BumpPackage, BumpRegistry, VersionUpdatesState } from "./updates"; |
| 6 | ||
| 7 | export * from "./updates"; | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 8 | |
| 9 | /** | |
| 10 | * The security service: secrets found in pushes and in history, vulnerable | |
| 11 | * dependencies, and the upgrade issues g1t opens for them. Members of a | |
| 12 | * workspace see its findings; nobody else does. Mirrors | |
| 13 | * `g1t_contracts::security`. | |
| 14 | */ | |
| 15 | ||
| 16 | /** | |
| 17 | * Where a secret stands. `open`: in history, to be rotated. `blocked`: a | |
| 18 | * push carrying it was refused, so it never landed. `allowed`: someone said | |
| 19 | * it is not a real secret, and pushes carrying it go through. `resolved`: | |
| 20 | * rotated or removed. | |
| 21 | */ | |
| 22 | export type SecretStatus = "open" | "blocked" | "allowed" | "resolved"; | |
| 23 | ||
| 24 | /** | |
| 25 | * Where an alert stands, as the Security page's filters and the API put it. | |
| 26 | * `open`: needs someone. `dismissed`: someone said why it can stay. | |
| 27 | * `fixed`: a secret revoked, or a dependency no longer vulnerable. | |
| 28 | */ | |
| 29 | export type AlertState = "open" | "dismissed" | "fixed"; | |
| 30 | ||
| 31 | /** Why a person dismissed an alert. */ | |
| 32 | export type DismissReason = | |
| 33 | | "false_positive" | |
| 34 | | "used_in_tests" | |
| 35 | | "revoked" | |
| 36 | | "wont_fix" | |
| 37 | | "fix_started" | |
| 38 | | "no_bandwidth" | |
| 39 | | "tolerable_risk" | |
| 40 | | "inaccurate" | |
| 41 | | "not_used"; | |
| 42 | ||
| 43 | /** The reasons a secret alert can be dismissed with, as people read them. */ | |
| 44 | export const SECRET_DISMISS_REASONS: { reason: DismissReason; label: string; about: string }[] = [ | |
| 45 | { reason: "false_positive", label: "False positive", about: "It is not a secret." }, | |
| 46 | { reason: "used_in_tests", label: "Used in tests", about: "A value made for tests or examples." }, | |
| 47 | { reason: "revoked", label: "Revoked", about: "It was real and has been revoked or rotated." }, | |
| 48 | { reason: "wont_fix", label: "Won't fix", about: "It is real, and accepted as it is." }, | |
| 49 | ]; | |
| 50 | ||
| 51 | /** The reasons a dependency alert can be dismissed with, as people read them. */ | |
| 52 | export const DEPENDENCY_DISMISS_REASONS: { reason: DismissReason; label: string; about: string }[] = [ | |
| 53 | { reason: "fix_started", label: "A fix has already been started", about: "Someone is upgrading it." }, | |
| 54 | { reason: "no_bandwidth", label: "No bandwidth to fix this", about: "Nobody can get to it now." }, | |
| 55 | { reason: "tolerable_risk", label: "Risk is tolerable to this project", about: "It does not matter for how this project uses it." }, | |
| 56 | { reason: "inaccurate", label: "This alert is inaccurate or incorrect", about: "The advisory is wrong about this package or version." }, | |
| 57 | { reason: "not_used", label: "Vulnerable code is not actually used", about: "The vulnerable code is never called." }, | |
| 58 | ]; | |
| 59 | ||
| 60 | /** A reason's label. */ | |
| 61 | export function dismissLabel(reason: DismissReason): string { | |
| 62 | return [...SECRET_DISMISS_REASONS, ...DEPENDENCY_DISMISS_REASONS].find((r) => r.reason === reason)?.label ?? reason; | |
| 63 | } | |
| 64 | ||
| 65 | export type SecretFinding = { | |
| 66 | id: string; | |
| 67 | repoId: string; | |
| 68 | /** `aws_access_key`, `github_token`, … */ | |
| 69 | kind: string; | |
| 70 | /** "an AWS access key". */ | |
| 71 | label: string; | |
| 72 | path: string; | |
| 73 | line: number; | |
| 74 | commit: string; | |
| 75 | /** Enough of the secret to recognise it; the secret itself is never kept. */ | |
| 76 | preview: string; | |
| 77 | status: SecretStatus; | |
| 78 | source: "push" | "history"; | |
| 79 | foundBy: string | null; | |
| 80 | /** RFC 3339. */ | |
| 81 | foundAt: string; | |
| 82 | /** Who dismissed it (allowed or resolved it). */ | |
| 83 | decidedBy: string | null; | |
| 84 | /** The comment given when it was dismissed. */ | |
| 85 | reason: string | null; | |
| 86 | decidedAt: string | null; | |
| 87 | /** Why it was dismissed; absent on open alerts and older decisions. */ | |
| 88 | dismissedReason?: DismissReason | null; | |
| 89 | /** | |
| 90 | * Why the value looks made for tests or documentation, when it does. Such | |
| 91 | * an alert never stops a push and is never counted as critical. | |
| 92 | */ | |
| 93 | testValue?: string | null; | |
| 94 | state: AlertState; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 95 | /** What its issuer said when last asked: active, inactive, unknown or unsupported. */ |
| 96 | validity?: "active" | "inactive" | "unknown" | "unsupported" | null; | |
| 97 | validityCheckedAt?: string | null; | |
| 98 | /** How it got past push protection, when someone bypassed it. */ | |
| 99 | bypass?: { | |
| 100 | reason: "false_positive" | "used_in_tests" | "will_fix_later"; | |
| 101 | comment: string | null; | |
| 102 | by: string; | |
| 103 | at: string; | |
| 104 | approvedBy: string | null; | |
| 105 | } | null; | |
| 106 | /** The custom pattern that found it, for a `custom_pattern` finding. */ | |
| 107 | patternId?: string | null; | |
| 108 | patternName?: string | null; | |
| 109 | /** How many places it was found in. */ | |
| 110 | locations?: number; | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 111 | }; |
| 112 | ||
| 113 | export type Severity = "critical" | "high" | "medium" | "low" | "unknown"; | |
| 114 | ||
| 115 | export const SEVERITIES: Severity[] = ["critical", "high", "medium", "low", "unknown"]; | |
| 116 | ||
| 117 | export type Vulnerability = { | |
| 118 | id: string; | |
| 119 | repoId: string; | |
| 120 | /** `npm`, `crates.io`, `Go`, `PyPI`. */ | |
| 121 | ecosystem: string; | |
| 122 | package: string; | |
| 123 | version: string; | |
| 124 | /** The lockfile that resolves it. */ | |
| 125 | manifest: string; | |
| 126 | /** Its GHSA id when it has one. */ | |
| 127 | advisory: string; | |
| 128 | osvId: string; | |
| 129 | summary: string; | |
| 130 | severity: Severity; | |
| 131 | fixedVersion: string | null; | |
| 132 | status: "open" | "fixed" | "dismissed"; | |
| 133 | /** The issue opened for g1t, when upgrading needs code changes. */ | |
| 134 | issue: number | null; | |
| 135 | foundAt: string; | |
| 136 | fixedAt: string | null; | |
| 137 | state: AlertState; | |
| 138 | dismissedBy?: string | null; | |
| 139 | dismissedReason?: DismissReason | null; | |
| 140 | dismissedComment?: string | null; | |
| 141 | dismissedAt?: string | null; | |
| 142 | /** The security update for its package, if g1t has started one. */ | |
| 143 | update?: SecurityUpdate | null; | |
| 144 | }; | |
| 145 | ||
| 146 | /** | |
| 147 | * Where a security update stands. `requested`: a sandbox is making the | |
| 148 | * change. `open`: its pull request is going through the required checks. | |
| 149 | * `superseded`: a newer update replaced it, or the package is no longer | |
| Security and version updates open no issues on their own: an update whose version cannot be raised fails with why on its alert and is tried again a day later, and one whose required checks fail stays open for a person to decide on; an agent takes one only when a person asks with Fix with g1t. That issue now names the lockfiles the package is locked in and the commands that show it is done, which were left empty ("Locked in: .") because the lockfiles were never passed in. | 150 | * vulnerable. `needs_code`: an earlier update g1t opened an issue for; new |
| 151 | * updates no longer reach it. `failed`: see `error`. | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 152 | */ |
| 153 | export type UpdateState = "requested" | "open" | "merged" | "closed" | "superseded" | "needs_code" | "failed"; | |
| 154 | ||
| 155 | /** The pull request g1t opens itself to upgrade one vulnerable package. */ | |
| 156 | export type SecurityUpdate = { | |
| 157 | state: UpdateState; | |
| 158 | /** The version it upgrades to. */ | |
| 159 | target: string; | |
| 160 | /** `g1t/security/<package>-<version>`. */ | |
| 161 | branch: string | null; | |
| 162 | pull: number | null; | |
| 163 | issue: number | null; | |
| 164 | error: string | null; | |
| 165 | updatedAt: string; | |
| 166 | }; | |
| 167 | ||
| 168 | /** Secret alerts by where they stand. */ | |
| 169 | export type SecretCounts = { | |
| 170 | /** In the history and looking real: rotate these. */ | |
| 171 | open: number; | |
| 172 | /** Stopped at a push, so never landed, and looking real. */ | |
| 173 | blocked: number; | |
| 174 | /** Open or blocked, but likely test values. */ | |
| 175 | testValues: number; | |
| 176 | dismissed: number; | |
| 177 | fixed: number; | |
| 178 | }; | |
| 179 | ||
| 180 | /** One thing that happened to an alert. */ | |
| 181 | export type AlertActivity = { | |
| 182 | id: string; | |
| 183 | alertId: string; | |
| 184 | /** | |
| 185 | * `dismissed`, `reopened`, `update_requested`, `update_opened`, | |
| 186 | * `update_merged`, `update_closed`, `update_superseded`, | |
| 187 | * `update_needs_code` or `update_failed`. | |
| 188 | */ | |
| 189 | action: string; | |
| 190 | /** A person's username, or `g1t`. */ | |
| 191 | actor: string | null; | |
| 192 | reason: DismissReason | null; | |
| 193 | comment: string | null; | |
| 194 | /** The pull request or issue it concerns. */ | |
| 195 | number: number | null; | |
| 196 | at: string; | |
| 197 | }; | |
| 198 | ||
| 199 | export type SeverityCounts = Record<Severity, number>; | |
| 200 | ||
| 201 | export type ScanState = { | |
| 202 | /** `pending`, `running`, `done`, or `stopped` at the workspace's limit. */ | |
| 203 | history: "pending" | "running" | "done" | "stopped"; | |
| 204 | commitsScanned: number; | |
| 205 | historyFinishedAt: string | null; | |
| 206 | dependenciesScannedAt: string | null; | |
| 207 | dependenciesError: string | null; | |
| 208 | lockfiles: string[]; | |
| 209 | }; | |
| 210 | ||
| 211 | export type SecurityOverview = { | |
| 212 | repoId: string; | |
| 213 | /** | |
| 214 | * Open alerts by severity: vulnerabilities open and not dismissed, and | |
| 215 | * secrets in the history that look real, as critical. Blocked secrets | |
| 216 | * and likely test values are not counted. | |
| 217 | */ | |
| 218 | counts: SeverityCounts; | |
| 219 | secretCounts: SecretCounts; | |
| 220 | secrets: SecretFinding[]; | |
| 221 | vulnerabilities: Vulnerability[]; | |
| 222 | /** What happened to the alerts, newest first. */ | |
| 223 | activity: AlertActivity[]; | |
| 224 | scan: ScanState; | |
| 225 | /** Security updates: whether g1t opens a pull request for each vulnerable dependency with a fix. */ | |
| 226 | upkeep: boolean; | |
| 227 | versionUpdates: VersionUpdatesState; | |
| 228 | }; | |
| 229 | ||
| 230 | /** The alert `dismiss` or `reopen` changed, as it is now. */ | |
| 231 | export type AlertChange = { | |
| 232 | secret: SecretFinding | null; | |
| 233 | vulnerability: Vulnerability | null; | |
| 234 | }; | |
| 235 | ||
| 236 | export type RepoSecurity = { | |
| 237 | repoId: string; | |
| 238 | name: string; | |
| 239 | counts: SeverityCounts; | |
| 240 | secrets: number; | |
| 241 | vulnerabilities: number; | |
| 242 | upkeep: boolean; | |
| 243 | dependenciesScannedAt: string | null; | |
| 244 | }; | |
| 245 | ||
| 246 | export interface SecurityApi { | |
| 247 | overview(repo: RepoPath, viewer: Viewer): Promise<Result<SecurityOverview>>; | |
| 248 | /** | |
| 249 | * Dismisses an alert (a secret takes Admin, a dependency Write) with a | |
| 250 | * reason and an optional comment. | |
| 251 | */ | |
| 252 | dismiss(actor: User, repo: RepoPath, id: string, reason: DismissReason, comment: string): Promise<Result<AlertChange>>; | |
| 253 | /** Opens a dismissed alert again. */ | |
| 254 | reopen(actor: User, repo: RepoPath, id: string): Promise<Result<AlertChange>>; | |
| 255 | rescan(actor: User, repo: RepoPath): Promise<Result<ScanState>>; | |
| 256 | setUpkeep(actor: User, repo: RepoPath, enabled: boolean): Promise<Result<boolean>>; | |
| 257 | workspace(workspace: string, viewer: Viewer): Promise<Result<RepoSecurity[]>>; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 258 | /** Checks one `updates` entry of the dependency update file for new versions now. Write and up. */ |
| 259 | checkUpdates(actor: User, repo: RepoPath, entry: string): Promise<Result<VersionUpdatesState>>; | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 260 | } |
| 261 | ||
| 262 | export function securityClient(service: ServiceBinding): SecurityApi { | |
| 263 | const call = async <T>(method: string, args: object): Promise<T> => { | |
| 264 | const response = await service.fetch(`https://service/rpc/${method}`, { | |
| 265 | method: "POST", | |
| 266 | headers: { "content-type": "application/json" }, | |
| 267 | body: JSON.stringify(args), | |
| 268 | }); | |
| 269 | if (!response.ok) throw new Error(`${method} failed with status ${response.status}`); | |
| 270 | return (await response.json()) as T; | |
| 271 | }; | |
| 272 | return { | |
| 273 | overview: (repo, viewer) => call("overview", { repo, viewer }), | |
| 274 | dismiss: (actor, repo, id, reason, comment) => call("dismiss", { actor, repo, id, reason, comment }), | |
| 275 | reopen: (actor, repo, id) => call("reopen", { actor, repo, id }), | |
| 276 | rescan: (actor, repo) => call("rescan", { actor, repo }), | |
| 277 | setUpkeep: (actor, repo, enabled) => call("set_upkeep", { actor, repo, enabled }), | |
| 278 | workspace: (workspace, viewer) => call("workspace", { workspace, viewer }), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 279 | checkUpdates: (actor, repo, entry) => call("check_updates", { actor, repo, entry }), |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 280 | }; |
| 281 | } | |
| 282 | ||
| 283 | /** | |
| 284 | * The runner's `bump`: makes a security update in a sandbox. It clones the | |
| 285 | * default branch, raises `package` to `version` in each lockfile with the | |
| 286 | * ecosystem's own tool, commits that as g1t and pushes it to `branch` | |
| 287 | * (`g1t/security/…`). The push tells the security service to open the pull | |
| 288 | * request. Mirrors `g1t_contracts::security::BumpArgs`. | |
| 289 | */ | |
| 290 | export type BumpArgs = { | |
| 291 | repo: RepoPath; | |
| 292 | /** OSV's name for the ecosystem: `npm`, `crates.io`, `Go` or `PyPI`. */ | |
| 293 | ecosystem: string; | |
| 294 | package: string; | |
| 295 | version: string; | |
| 296 | /** The lockfiles that resolve a vulnerable version, from the root. */ | |
| 297 | lockfiles: string[]; | |
| 298 | branch: string; | |
| 299 | /** The commit's message. */ | |
| 300 | message: string; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 301 | /** `version` for a version update, whose branch is any but the default one. */ |
| 302 | kind?: "version"; | |
| 303 | /** Several packages raised in one commit, for a grouped update. */ | |
| 304 | packages?: BumpPackage[]; | |
| 305 | /** `increase` (default), `increase-if-necessary`, `widen` or `lockfile-only`. */ | |
| 306 | strategy?: string; | |
| 307 | /** Replace the branch if it is there already: a rebase or a recreate. */ | |
| 308 | force?: boolean; | |
| 309 | /** Private registries the tools may read, with their credentials. */ | |
| 310 | registries?: BumpRegistry[]; | |
| 311 | /** | |
| 312 | * The branch to start from, which its pull request merges into | |
| 313 | * (`target-branch`): the default branch when absent. | |
| 314 | */ | |
| 315 | base?: string; | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 316 | }; |
| 317 | ||
| 318 | /** The prefix every security update's branch starts with. */ | |
| 319 | export const UPDATE_BRANCH_PREFIX = "g1t/security/"; |
This file's history is long; its oldest lines are credited to the oldest commit read.