Skip to content
319 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.

Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1import type { ServiceBinding } from "./clients";
2import type { User, Viewer } from "./identity";
3import type { RepoPath } from "./repos";
4import type { Result } from "./result";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar5import type { BumpPackage, BumpRegistry, VersionUpdatesState } from "./updates";
6
7export * from "./updates";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step8
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 */
22export type SecretStatus = "open" | "blocked" | "allowed" | "resolved";
23
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily24/**
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 */
29export type AlertState = "open" | "dismissed" | "fixed";
30
31/** Why a person dismissed an alert. */
32export 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. */
44export 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. */
52export 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. */
61export function dismissLabel(reason: DismissReason): string {
62 return [...SECRET_DISMISS_REASONS, ...DEPENDENCY_DISMISS_REASONS].find((r) => r.reason === reason)?.label ?? reason;
63}
64
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step65export 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;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily82 /** Who dismissed it (allowed or resolved it). */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step83 decidedBy: string | null;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily84 /** The comment given when it was dismissed. */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step85 reason: string | null;
86 decidedAt: string | null;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily87 /** 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 bar95 /** 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;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step111};
112
113export type Severity = "critical" | "high" | "medium" | "low" | "unknown";
114
115export const SEVERITIES: Severity[] = ["critical", "high", "medium", "low", "unknown"];
116
117export 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;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily132 status: "open" | "fixed" | "dismissed";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent133 /** The issue opened for g1t, when upgrading needs code changes. */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step134 issue: number | null;
135 foundAt: string;
136 fixedAt: string | null;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily137 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`.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily152 */
153export type UpdateState = "requested" | "open" | "merged" | "closed" | "superseded" | "needs_code" | "failed";
154
155/** The pull request g1t opens itself to upgrade one vulnerable package. */
156export 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. */
169export 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. */
181export 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;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step197};
198
199export type SeverityCounts = Record<Severity, number>;
200
201export 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
211export type SecurityOverview = {
212 repoId: string;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily213 /**
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 */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step218 counts: SeverityCounts;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily219 secretCounts: SecretCounts;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step220 secrets: SecretFinding[];
221 vulnerabilities: Vulnerability[];
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily222 /** What happened to the alerts, newest first. */
223 activity: AlertActivity[];
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step224 scan: ScanState;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily225 /** Security updates: whether g1t opens a pull request for each vulnerable dependency with a fix. */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step226 upkeep: boolean;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily227 versionUpdates: VersionUpdatesState;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step228};
229
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily230/** The alert `dismiss` or `reopen` changed, as it is now. */
231export type AlertChange = {
232 secret: SecretFinding | null;
233 vulnerability: Vulnerability | null;
234};
235
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step236export 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
246export interface SecurityApi {
247 overview(repo: RepoPath, viewer: Viewer): Promise<Result<SecurityOverview>>;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily248 /**
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>>;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step255 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 bar258 /** 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>>;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step260}
261
262export 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 }),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily274 dismiss: (actor, repo, id, reason, comment) => call("dismiss", { actor, repo, id, reason, comment }),
275 reopen: (actor, repo, id) => call("reopen", { actor, repo, id }),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step276 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 bar279 checkUpdates: (actor, repo, entry) => call("check_updates", { actor, repo, entry }),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step280 };
281}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily282
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 */
290export 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 bar301 /** `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;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily316};
317
318/** The prefix every security update's branch starts with. */
319export const UPDATE_BRANCH_PREFIX = "g1t/security/";

This file's history is long; its oldest lines are credited to the oldest commit read.