Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
896 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.

Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail1/**
2 * Security, as a project's page shows it: open alerts by severity, the
3 * secrets found in pushes and history, vulnerable dependencies with the
4 * security update g1t opened for each, and what happened to every alert.
5 * The page posts the intents in `routes/repo/security.tsx`'s action.
6 */
7import {
8 Bot,
9 CircleAlert,
10 CircleCheck,
11 CircleDot,
12 CircleSlash,
13 ExternalLink,
14 GitBranch,
15 GitMerge,
16 GitPullRequest,
17 GitPullRequestClosed,
18 History,
19 KeyRound,
20 Loader,
21 Package,
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.22 PauseCircle,
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail23 ShieldAlert,
24 ShieldCheck,
25} from "lucide-react";
26import { type ReactNode, useEffect, useRef, useState } from "react";
27import { Link, useFetcher } from "react-router";
28
29import {
30 type AlertActivity,
31 type AlertState,
32 DEPENDENCY_DISMISS_REASONS,
33 type DismissReason,
34 SECRET_DISMISS_REASONS,
35 SEVERITIES,
36 type PullStatus,
37 type SecretFinding,
38 type SecurityUpdate,
39 type Severity,
40 type SeverityCounts,
41 type Vulnerability,
42 dismissLabel,
43} from "@g1t/contracts";
44
45import {
46 type ActivityEntry,
47 type PackageGroup,
48 UPDATE_STATES,
49 alertActivity,
50 groupByPackage,
51 highestFix,
52 latestUpdate,
53 splitSecrets,
54 worstSeverity,
55} from "../lib/security-alerts";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.56import { TimeAgo } from "./ui";
57import { Avatar } from "./ui/avatar";
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail58import { Badge, type BadgeTone } from "./ui/badge";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.59import { Button } from "./ui/button";
60import { Card } from "./ui/card";
Merge branch 'worktree-agent-ae1299e92e4462012'61import { Hint } from "./ui/hint";
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail62import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
63import { RadioGroup, RadioOption } from "./ui/radio-group";
64
65type Done = { ok: boolean; error?: string } | undefined;
66
67const SEVERITY: Record<Severity, { label: string; tone: BadgeTone }> = {
68 critical: { label: "Critical", tone: "danger" },
69 high: { label: "High", tone: "warn" },
70 medium: { label: "Medium", tone: "merged" },
71 low: { label: "Low", tone: "info" },
72 unknown: { label: "Unrated", tone: "neutral" },
73};
74
75
76export function SeverityBadge({ severity }: { severity: Severity }) {
77 return <Badge tone={SEVERITY[severity].tone}>{SEVERITY[severity].label}</Badge>;
78}
79
80/** Open alerts by severity, one tile each. */
81export function SeverityCountsGrid({ counts }: { counts: SeverityCounts }) {
82 return (
83 <div className="grid grid-cols-2 gap-3 sm:grid-cols-5">
84 {SEVERITIES.map((severity) => (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.85 <Card key={severity} className="px-4 py-3">
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail86 <p className="text-xs text-muted">{SEVERITY[severity].label}</p>
87 <p className={`mt-1 text-2xl font-semibold tabular-nums ${counts[severity] > 0 && severity === "critical" ? "text-danger" : ""}`}>
88 {counts[severity]}
89 </p>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.90 </Card>
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail91 ))}
92 </div>
93 );
94}
95
96/** A compact row of severity counts, for a list of projects. */
97export function SeverityCountsInline({ counts }: { counts: SeverityCounts }) {
98 const shown = SEVERITIES.filter((severity) => counts[severity] > 0);
99 if (shown.length === 0) {
100 return (
Merge branch 'worktree-agent-ad7c6d88d93adc817'101 <span className="inline-flex items-center gap-1 text-xs text-success">
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail102 <ShieldCheck size={13} />
103 Nothing open
104 </span>
105 );
106 }
107 return (
108 <span className="flex flex-wrap gap-1.5">
109 {shown.map((severity) => (
110 <Badge key={severity} tone={SEVERITY[severity].tone}>
111 {counts[severity]} {SEVERITY[severity].label.toLowerCase()}
112 </Badge>
113 ))}
114 </span>
115 );
116}
117
118const STATE_FILTERS: { state: AlertState; label: string; icon: ReactNode }[] = [
119 { state: "open", label: "Open", icon: <CircleDot size={14} /> },
120 { state: "dismissed", label: "Dismissed", icon: <CircleSlash size={14} /> },
121 { state: "fixed", label: "Fixed", icon: <CircleCheck size={14} /> },
122];
123
124/** Open, Dismissed and Fixed, with how many alerts each holds. */
125export function StateFilter({
126 counts,
127 value,
128 onChange,
129}: {
130 counts: Record<AlertState, number>;
131 value: AlertState;
132 onChange: (state: AlertState) => void;
133}) {
134 return (
135 <div role="group" aria-label="Filter alerts" className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
136 {STATE_FILTERS.map(({ state, label, icon }) => (
137 <button
138 key={state}
139 type="button"
140 aria-pressed={value === state}
141 onClick={() => onChange(state)}
142 className={`inline-flex items-center gap-1.5 transition-colors ${
143 value === state ? "font-medium text-fg" : "text-muted hover:text-fg"
144 }`}
145 >
146 {icon}
147 {counts[state]} {label}
148 </button>
149 ))}
150 </div>
151 );
152}
153
154const TEXTAREA =
155 "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim";
156
157const SMALL_BUTTON =
158 "rounded-md border border-line px-2.5 py-1 text-xs font-medium text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-50";
159
160/** Dismiss an alert with one of `reasons` and an optional comment. */
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar161export function DismissDialog({
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail162 id,
163 title,
164 detail,
165 reasons,
166 note,
167 action,
168 defaultReason,
169 trigger = "Dismiss",
170}: {
171 id: string;
172 title: string;
173 /** What the alert is, in a line of code type. */
174 detail: string;
175 reasons: { reason: DismissReason; label: string; about: string }[];
176 note?: string;
177 action: string;
178 defaultReason?: DismissReason;
179 trigger?: string;
180}) {
181 const fetcher = useFetcher<Done>();
182 const [open, setOpen] = useState(false);
183 const [reason, setReason] = useState<string>(defaultReason ?? "");
184 useEffect(() => {
185 if (fetcher.state === "idle" && fetcher.data?.ok) setOpen(false);
186 }, [fetcher.state, fetcher.data]);
187 return (
188 <Dialog
189 open={open}
190 onOpenChange={(next) => {
191 setOpen(next);
192 if (next) setReason(defaultReason ?? "");
193 }}
194 >
195 <DialogTrigger className={SMALL_BUTTON}>{trigger}</DialogTrigger>
196 <DialogContent>
197 <DialogHeader>
198 <DialogTitle>{title}</DialogTitle>
199 <DialogDescription>
200 Say why it can stay. The alert keeps your name, the reason and your comment, and anyone with access can reopen it.
201 </DialogDescription>
202 </DialogHeader>
203 <fetcher.Form method="post" action={action} className="space-y-4">
204 <input type="hidden" name="intent" value="dismiss" />
205 <input type="hidden" name="id" value={id} />
206 <p className="font-mono text-xs break-all text-muted">{detail}</p>
207 <fieldset>
208 <legend className="text-xs font-medium text-muted">Reason</legend>
209 <RadioGroup name="reason" value={reason} onValueChange={setReason} required className="mt-2 gap-3">
210 {reasons.map((option) => (
211 <RadioOption key={option.reason} value={option.reason} label={option.label} description={option.about} />
212 ))}
213 </RadioGroup>
214 </fieldset>
215 {note && <p className="rounded-md border border-warn/30 bg-warn/5 px-3 py-2 text-xs text-warn">{note}</p>}
216 <label className="block">
217 <span className="mb-1.5 block text-xs font-medium text-muted">Comment (optional)</span>
218 <textarea name="comment" rows={3} maxLength={500} placeholder="What someone reading this later should know." className={TEXTAREA} />
219 </label>
220 <div className="flex justify-end">
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.221 <Button
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail222 type="submit"
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.223 disabled={fetcher.state !== "idle" || !reason}>
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail224 {fetcher.state !== "idle" ? "Dismissing…" : "Dismiss alert"}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.225 </Button>
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail226 </div>
227 {fetcher.data?.error && <p className="text-sm text-danger">{fetcher.data.error}</p>}
228 </fetcher.Form>
229 </DialogContent>
230 </Dialog>
231 );
232}
233
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar234export function ReopenButton({ id, action }: { id: string; action: string }) {
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail235 const fetcher = useFetcher<Done>();
236 return (
237 <span className="flex flex-col items-end gap-1">
238 <button
239 type="button"
240 disabled={fetcher.state !== "idle"}
241 onClick={() => fetcher.submit({ intent: "reopen", id }, { method: "post", action })}
242 className={SMALL_BUTTON}
243 >
244 {fetcher.state !== "idle" ? "Reopening…" : "Reopen"}
245 </button>
246 {fetcher.data?.error && <span className="text-xs text-danger">{fetcher.data.error}</span>}
247 </span>
248 );
249}
250
251/** What happened to an alert, oldest first, folded away until asked for. */
252function ActivityLog({ entries, base, open }: { entries: ActivityEntry[]; base: string; open?: boolean }) {
253 if (entries.length === 0) return null;
254 return (
255 <details className="group mt-2" open={open}>
256 <summary className="inline-flex cursor-pointer list-none items-center gap-1.5 text-xs text-muted hover:text-fg [&::-webkit-details-marker]:hidden">
257 <History size={12} />
258 <span className="group-open:hidden">Show activity ({entries.length})</span>
259 <span className="hidden group-open:inline">Hide activity</span>
260 </summary>
261 <ol className="mt-2 space-y-2 border-l border-line pl-3">
262 {entries.map((entry) => (
263 <li key={entry.key} className="text-xs">
264 <p className="flex flex-wrap items-center gap-x-1.5 gap-y-1 text-muted">
265 {entry.actor ? (
266 <>
267 <Avatar name={entry.actor} size={16} />
268 <span className="font-medium text-fg">{entry.actor}</span>
269 </>
270 ) : (
271 <CircleDot size={12} className="text-faint" />
272 )}
273 <span>{entry.text}</span>
274 {entry.ref && (
275 <Link
276 to={`${base}/${entry.ref.kind === "pull" ? "pull" : "issues"}/${entry.ref.number}`}
277 className="font-medium text-fg-soft hover:text-fg hover:underline"
278 >
279 #{entry.ref.number}
280 </Link>
281 )}
282 {entry.reason && <Badge>{dismissLabel(entry.reason)}</Badge>}
283 <span className="text-faint">
284 <TimeAgo at={entry.at} />
285 </span>
286 </p>
287 {entry.comment && <p className="mt-1 text-fg-soft wrap-anywhere">“{entry.comment}”</p>}
288 </li>
289 ))}
290 </ol>
291 </details>
292 );
293}
294
295function useFocus<T extends HTMLElement>(focused: boolean) {
296 const ref = useRef<T>(null);
297 useEffect(() => {
298 if (focused) ref.current?.scrollIntoView({ block: "center" });
299 }, [focused]);
300 return ref;
301}
302
303const FOCUSED = "bg-accent/5 ring-1 ring-accent/40 ring-inset";
304
305function secretBadge(finding: SecretFinding): { label: string; tone: BadgeTone; about: string } {
306 if (finding.state === "dismissed") {
307 return {
308 label: "Dismissed",
309 tone: "neutral",
310 about: finding.status === "allowed" ? "Pushes carrying it go through." : "Dismissed.",
311 };
312 }
Merge branch 'worktree-agent-ad7c6d88d93adc817'313 if (finding.state === "fixed") return { label: "Revoked", tone: "success", about: "Revoked or rotated." };
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail314 if (finding.status === "blocked") {
315 return { label: "Push blocked", tone: finding.testValue ? "neutral" : "warn", about: "A push carrying it was refused, so it never landed." };
316 }
317 return {
318 label: "In history",
319 tone: finding.testValue ? "neutral" : "danger",
320 about: "In the repository's history. Rotate it with whoever issued it, then dismiss it as revoked.",
321 };
322}
323
324function SecretItem({
325 finding,
326 activity,
327 base,
328 action,
329 focused,
330 canDismiss,
331}: {
332 finding: SecretFinding;
333 activity: AlertActivity[];
334 base: string;
335 action: string;
336 focused: boolean;
337 canDismiss: boolean;
338}) {
339 const ref = useFocus<HTMLLIElement>(focused);
340 const badge = secretBadge(finding);
341 const landed = finding.source === "history" || finding.status === "open";
342 const where = `${finding.path}:${finding.line}`;
343 return (
344 <li ref={ref} id={finding.id} className={`flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-start ${focused ? FOCUSED : ""}`}>
345 <KeyRound size={15} className="mt-0.5 hidden shrink-0 text-muted sm:block" />
346 <div className="min-w-0 grow">
347 <div className="flex flex-wrap items-center gap-2">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar348 <Link to={`${base}/security/secret-scanning/${finding.id}`} className="text-sm font-medium first-letter:uppercase hover:underline">
349 {finding.label}
350 </Link>
Merge branch 'worktree-agent-ae1299e92e4462012'351 <Hint label={badge.about}>
352 <Badge tone={badge.tone}>{badge.label}</Badge>
353 </Hint>
354 {finding.testValue && (
355 <Hint label={finding.testValue}>
356 <Badge>Likely test value</Badge>
357 </Hint>
358 )}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar359 {finding.validity === "active" && (
Merge branch 'worktree-agent-ae1299e92e4462012'360 <Hint label="Its issuer says it still works">
361 <Badge tone="danger">Active</Badge>
362 </Hint>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar363 )}
Merge branch 'worktree-agent-ae1299e92e4462012'364 {finding.validity === "inactive" && (
365 <Hint label="Its issuer refused it: revoked or expired">
366 <Badge>Inactive</Badge>
367 </Hint>
368 )}
369 {finding.bypass && (
370 <Hint label={`Bypassed by ${finding.bypass.by}`}>
371 <Badge tone="warn">Bypassed</Badge>
372 </Hint>
373 )}
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail374 {finding.state === "dismissed" && finding.dismissedReason && <Badge>{dismissLabel(finding.dismissedReason)}</Badge>}
375 </div>
376 <p className="mt-1 truncate font-mono text-xs">
377 {landed ? (
378 <Link to={`${base}/blob/${finding.commit}/${finding.path}#L${finding.line}`} className="text-fg-soft hover:text-fg hover:underline">
379 {where}
380 </Link>
381 ) : (
382 <span className="text-fg-soft">{where}</span>
383 )}
384 <span className="text-faint"> · {finding.preview}</span>
385 </p>
386 {finding.testValue && <p className="mt-1 text-xs text-muted">{finding.testValue}</p>}
387 <p className="mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-faint">
388 <span>
389 {finding.source === "push" ? "in a push" : "in history"}
390 {finding.foundBy && <> by {finding.foundBy}</>}, commit{" "}
391 {landed ? (
392 <Link to={`${base}/commit/${finding.commit}`} className="font-mono hover:text-fg">
393 {finding.commit.slice(0, 7)}
394 </Link>
395 ) : (
396 <span className="font-mono">{finding.commit.slice(0, 7)}</span>
397 )}
398 </span>
399 <span>
400 found <TimeAgo at={finding.foundAt} />
401 </span>
402 </p>
403 <ActivityLog entries={alertActivity(finding, activity)} base={base} open={focused && finding.state !== "open"} />
404 </div>
405 {canDismiss && (
406 <div className="flex shrink-0 items-center gap-1.5">
407 {finding.state === "open" ? (
408 <DismissDialog
409 id={finding.id}
410 title={`Dismiss ${finding.label}`}
411 detail={`${where} · ${finding.preview}`}
412 reasons={SECRET_DISMISS_REASONS}
413 note={
414 finding.status === "blocked" && !finding.testValue
415 ? "Dismissing it lets the same push through, unless you dismiss it as revoked."
416 : undefined
417 }
418 action={action}
419 />
420 ) : (
421 <ReopenButton id={finding.id} action={action} />
422 )}
423 </div>
424 )}
425 </li>
426 );
427}
428
429function Empty({ title, children }: { title: string; children: ReactNode }) {
430 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.431 <Card tone="plain" className="border-dashed px-6 py-10 text-center">
Merge branch 'worktree-agent-ad7c6d88d93adc817'432 <ShieldCheck size={22} className="mx-auto text-success" />
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail433 <p className="mt-2 font-medium">{title}</p>
434 <p className="mt-1 text-sm text-muted">{children}</p>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.435 </Card>
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail436 );
437}
438
439const LIST = "divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface";
440
441/** The secret alerts in one state: on Open, real ones first, then likely test values. */
442export function SecretsList({
443 secrets,
444 state,
445 activity,
446 base,
447 action,
448 focus,
449 canDismiss,
450}: {
451 /** Already filtered to `state`. */
452 secrets: SecretFinding[];
453 state: AlertState;
454 activity: AlertActivity[];
455 base: string;
456 action: string;
457 focus: string | null;
458 /** Whether the viewer may dismiss and reopen secret alerts (Admin). */
459 canDismiss: boolean;
460}) {
461 const item = (finding: SecretFinding) => (
462 <SecretItem
463 key={finding.id}
464 finding={finding}
465 activity={activity}
466 base={base}
467 action={action}
468 focused={finding.id === focus}
469 canDismiss={canDismiss}
470 />
471 );
472 if (secrets.length === 0) {
473 return state === "open" ? (
474 <Empty title="No open secret alerts">
475 Pushes that add a key or a token are refused before they land, and the history is scanned once in the background.
476 </Empty>
477 ) : (
478 <Empty title={state === "dismissed" ? "No dismissed secret alerts" : "No revoked secrets"}>
479 {state === "dismissed"
480 ? "Alerts someone dismissed as a false positive, a test value or accepted are listed here."
481 : "Secrets dismissed as revoked are listed here."}
482 </Empty>
483 );
484 }
485 if (state !== "open") return <ul className={LIST}>{secrets.map(item)}</ul>;
486 const { real, tests } = splitSecrets(secrets);
487 return (
488 <div className="space-y-5">
489 {real.length > 0 ? (
490 <ul className={LIST}>{real.map(item)}</ul>
491 ) : (
492 <Empty title="No secrets that look real">Only likely test values are open, and they never block a push.</Empty>
493 )}
494 {tests.length > 0 && (
495 <section>
496 <h4 className="text-sm font-medium">Likely test values</h4>
497 <p className="mt-0.5 mb-2 text-xs text-muted">
498 These look made for tests or documentation. They never block a push and are not counted as critical; dismiss them
499 to clear the list.
500 </p>
501 <ul className={LIST}>{tests.map(item)}</ul>
502 </section>
503 )}
504 </div>
505 );
506}
507
508/** Where the legacy upgrade issue for a package stands, as the page loads it. */
509export type UpgradeFix = {
510 number: number;
511 state: "open" | "closed";
512 /** The newest pull request for the issue, if any. */
513 pull: { number: number; status: PullStatus; agent: string | null } | null;
514 resolvedBy: number | null;
515};
516
517/** A security update's pull request as the page loads it. */
518export type PullInfo = { number: number; status: PullStatus; title: string };
519
520function FixLink({ issue, fix, base }: { issue: number; fix: UpgradeFix | undefined; base: string }) {
521 const pull = fix?.pull;
522 return (
523 <span className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs">
524 <span className="text-muted">Upgrade issue</span>
525 <Link to={`${base}/issues/${issue}`} className="inline-flex items-center gap-1 text-fg-soft hover:text-fg">
Merge branch 'worktree-agent-ad7c6d88d93adc817'526 {fix?.state === "closed" ? <CircleCheck size={12} className="text-merged" /> : <CircleDot size={12} className="text-success" />}#{issue}
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail527 </Link>
528 {pull && (
529 <Link to={`${base}/pull/${pull.number}`} className="inline-flex items-center gap-1 text-muted hover:text-fg">
530 {pull.agent ? <Bot size={12} /> : <GitPullRequest size={12} />}#{pull.number} {pull.status === "draft" ? "in progress" : pull.status}
531 </Link>
532 )}
533 </span>
534 );
535}
536
537const PULL_ICON: Record<PullStatus, ReactNode> = {
538 draft: <GitPullRequest size={13} className="text-muted" />,
Merge branch 'worktree-agent-ad7c6d88d93adc817'539 open: <GitPullRequest size={13} className="text-success" />,
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail540 merged: <GitMerge size={13} className="text-merged" />,
541 closed: <GitPullRequestClosed size={13} className="text-danger" />,
542};
543
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.544/**
545 * Where g1t's security update for a package stands. On a mirror g1t does
546 * not lead (`updatesWait`, the reason), an update that has not opened a
547 * pull request waits: nothing is made or pushed there until a takeover.
548 */
549function UpdateStatus({ update, name, pulls, base, updatesWait }: {
550 update: SecurityUpdate;
551 name: string;
552 pulls: Record<number, PullInfo>;
553 base: string;
554 updatesWait?: string | null;
555}) {
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail556 const meta = UPDATE_STATES[update.state];
557 const pull = update.pull != null ? pulls[update.pull] : undefined;
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.558 if (updatesWait && (update.state === "requested" || update.state === "failed" || update.state === "needs_code")) {
559 return (
560 <Card tone="plain" radius="lg" className="mt-2.5 bg-bg/40 px-3 py-2 text-xs sm:ml-7">
561 <div className="flex flex-wrap items-center gap-x-2.5 gap-y-1.5">
562 <PauseCircle size={13} className="text-muted" />
563 <Badge>Security update waiting</Badge>
564 <span className="text-faint">
565 <TimeAgo at={update.updatedAt} />
566 </span>
567 </div>
568 <p className="mt-1 text-muted wrap-anywhere">
569 Raising {name} to {update.target} is on hold until {base.replace(/^\//, "")} is taken over or moved to g1t.
570 </p>
571 </Card>
572 );
573 }
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail574 const about: Record<SecurityUpdate["state"], ReactNode> = {
575 requested: <>A sandbox is raising {name} to {update.target}.</>,
576 open: <>It raises {name} to {update.target} and lands through your branch's required checks.</>,
577 merged: <>{name} was raised to {update.target}.</>,
578 closed: <>The pull request was closed without merging.</>,
579 superseded: <>A newer update replaced it, or the package is no longer vulnerable, so g1t closed it.</>,
580 needs_code: (
581 <>
582 Raising {name} to {update.target} needs code changes, so g1t opened an issue to make them
583 {update.issue != null && (
584 <>
585 :{" "}
586 <Link to={`${base}/issues/${update.issue}`} className="font-medium text-fg-soft hover:text-fg hover:underline">
587 #{update.issue}
588 </Link>
589 </>
590 )}
591 .
592 </>
593 ),
594 failed: <>{update.error ?? "g1t could not make the change."}</>,
595 };
596 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.597 <Card tone="plain" radius="lg" className="mt-2.5 bg-bg/40 px-3 py-2 text-xs sm:ml-7">
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail598 <div className="flex flex-wrap items-center gap-x-2.5 gap-y-1.5">
599 {update.state === "requested" ? (
600 <Loader size={13} className="animate-spin text-info motion-reduce:animate-none" />
601 ) : update.state === "failed" || update.state === "needs_code" ? (
602 <CircleAlert size={13} className={update.state === "failed" ? "text-danger" : "text-warn"} />
603 ) : (
604 PULL_ICON[pull?.status ?? (update.state === "merged" ? "merged" : update.state === "open" ? "open" : "closed")]
605 )}
606 <Badge tone={meta.tone}>{meta.label}</Badge>
607 {update.pull != null && (
608 <Link to={`${base}/pull/${update.pull}`} className="font-medium text-fg-soft hover:text-fg hover:underline">
609 #{update.pull}
610 {pull && <span className="font-normal text-muted"> {pull.status === "draft" ? "draft" : pull.status}</span>}
611 </Link>
612 )}
613 {update.branch && (
614 <span className="inline-flex min-w-0 items-center gap-1 font-mono text-muted">
615 <GitBranch size={12} className="shrink-0" />
616 <span className="truncate">{update.branch}</span>
617 </span>
618 )}
619 <span className="text-faint">
620 <TimeAgo at={update.updatedAt} />
621 </span>
622 </div>
623 <p className={`mt-1 ${update.state === "failed" ? "text-danger" : "text-muted"} wrap-anywhere`}>{about[update.state]}</p>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.624 </Card>
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail625 );
626}
627
628export function VulnerabilityList({
629 vulnerabilities,
630 state,
631 activity,
632 fixes,
633 pulls,
634 upkeep,
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.635 updatesWait,
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail636 base,
637 action,
638 focus,
639 canDismiss,
640}: {
641 /** Already filtered to `state`. */
642 vulnerabilities: Vulnerability[];
643 state: AlertState;
644 activity: AlertActivity[];
645 fixes: Record<number, UpgradeFix>;
646 pulls: Record<number, PullInfo>;
647 /** Whether security updates are on. */
648 upkeep: boolean;
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.649 /** Why no update opens now: the repository is a mirror g1t does not lead (lib/mirror.ts). */
650 updatesWait?: string | null;
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail651 base: string;
652 action: string;
653 focus: string | null;
654 /** Whether the viewer may dismiss and reopen dependency alerts (Write). */
655 canDismiss: boolean;
656}) {
657 const packages = groupByPackage(vulnerabilities);
658 if (packages.length === 0) {
659 return state === "open" ? (
660 <Empty title="No known vulnerabilities">
661 Every package the lockfiles resolve is checked against the OSV database on each push to the default branch, and daily.
662 </Empty>
663 ) : (
664 <Empty title={state === "dismissed" ? "No dismissed dependency alerts" : "Nothing fixed yet"}>
665 {state === "dismissed"
666 ? "Alerts someone dismissed, with their reason, are listed here."
667 : "Alerts whose package was upgraded, or is no longer vulnerable, are listed here."}
668 </Empty>
669 );
670 }
671 return (
672 <ul className={LIST}>
673 {packages.map((group) => (
674 <PackageItem
675 key={group.key}
676 group={group}
677 activity={activity}
678 fixes={fixes}
679 pulls={pulls}
680 upkeep={upkeep}
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.681 updatesWait={updatesWait}
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail682 base={base}
683 action={action}
684 focus={focus}
685 canDismiss={canDismiss}
686 />
687 ))}
688 </ul>
689 );
690}
691
692function PackageItem({
693 group,
694 activity,
695 fixes,
696 pulls,
697 upkeep,
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.698 updatesWait,
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail699 base,
700 action,
701 focus,
702 canDismiss,
703}: {
704 group: PackageGroup;
705 activity: AlertActivity[];
706 fixes: Record<number, UpgradeFix>;
707 pulls: Record<number, PullInfo>;
708 upkeep: boolean;
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.709 updatesWait?: string | null;
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail710 base: string;
711 action: string;
712 focus: string | null;
713 canDismiss: boolean;
714}) {
715 const versions = [...new Set(group.vulns.map((vuln) => vuln.version))];
716 const manifests = [...new Set(group.vulns.map((vuln) => vuln.manifest))];
717 const target = highestFix(group.vulns);
718 const update = latestUpdate(group.vulns);
719 const issue = update ? null : (group.vulns.find((vuln) => vuln.issue != null)?.issue ?? null);
720 return (
721 <li className="px-4 py-3">
722 <div className="flex items-start gap-3">
723 <Package size={15} className="mt-0.5 hidden shrink-0 text-muted sm:block" />
724 <div className="min-w-0 grow">
725 <div className="flex flex-wrap items-center gap-2">
726 <span className="font-mono text-sm font-medium break-all">{group.name}</span>
727 <span className="font-mono text-xs text-muted">{versions.join(", ")}</span>
728 <Badge>{group.ecosystem}</Badge>
729 <SeverityBadge severity={worstSeverity(group.vulns)} />
730 </div>
731 <p className="mt-1 text-xs text-faint wrap-anywhere">
732 {target ? <>Fixed in {target}</> : "No patched version"} · locked in{" "}
733 <span className="font-mono">{manifests.join(", ")}</span>
734 </p>
735 </div>
736 </div>
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.737 {update && <UpdateStatus update={update} name={group.name} pulls={pulls} base={base} updatesWait={updatesWait} />}
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail738 {issue != null && (
739 <div className="mt-2 sm:ml-7">
740 <FixLink issue={issue} fix={fixes[issue]} base={base} />
741 </div>
742 )}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.743 <Card asChild tone="plain" radius="lg" className="mt-2.5 space-y-px overflow-hidden sm:ml-7">
744 <ul>
745 {group.vulns.map((vuln) => (
746 <AdvisoryItem
747 key={vuln.id}
748 vuln={vuln}
749 showManifest={manifests.length > 1}
750 activity={activity}
751 upkeep={upkeep}
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.752 updatesWait={updatesWait}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.753 base={base}
754 action={action}
755 focused={vuln.id === focus}
756 canDismiss={canDismiss}
757 />
758 ))}
759 </ul>
760 </Card>
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail761 </li>
762 );
763}
764
765function AdvisoryItem({
766 vuln,
767 showManifest,
768 activity,
769 upkeep,
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.770 updatesWait,
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail771 base,
772 action,
773 focused,
774 canDismiss,
775}: {
776 vuln: Vulnerability;
777 showManifest: boolean;
778 activity: AlertActivity[];
779 upkeep: boolean;
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.780 updatesWait?: string | null;
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail781 base: string;
782 action: string;
783 focused: boolean;
784 canDismiss: boolean;
785}) {
786 const ref = useFocus<HTMLLIElement>(focused);
787 const osv = `https://osv.dev/vulnerability/${vuln.osvId}`;
788 const dismiss = (reason?: DismissReason, trigger?: string) => (
789 <DismissDialog
790 id={vuln.id}
791 title={`Dismiss ${vuln.advisory}`}
792 detail={`${vuln.package} ${vuln.version} · ${vuln.manifest}`}
793 reasons={DEPENDENCY_DISMISS_REASONS}
794 action={action}
795 defaultReason={reason}
796 trigger={trigger}
797 />
798 );
799 return (
800 <li ref={ref} id={vuln.id} className={`bg-bg/30 px-3 py-2.5 not-first:border-t not-first:border-line ${focused ? FOCUSED : ""}`}>
801 <div className="flex flex-col gap-2 sm:flex-row sm:items-start">
802 <div className="min-w-0 grow text-xs">
803 <p className="flex flex-wrap items-center gap-x-2 gap-y-1">
804 <a href={osv} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 font-mono text-fg-soft hover:text-fg">
805 {vuln.advisory}
806 <ExternalLink size={10} />
807 </a>
808 <SeverityBadge severity={vuln.severity} />
809 {vuln.fixedVersion && <span className="text-faint">fixed in {vuln.fixedVersion}</span>}
810 {showManifest && <span className="font-mono text-faint">{vuln.manifest}</span>}
811 {vuln.state === "dismissed" && vuln.dismissedReason && <Badge>{dismissLabel(vuln.dismissedReason)}</Badge>}
812 </p>
813 <p className="mt-1 text-muted wrap-anywhere">{vuln.summary}</p>
814 </div>
815 {canDismiss && vuln.state !== "fixed" && (
816 <div className="flex shrink-0 items-center gap-1.5">
817 {vuln.state === "open" ? dismiss() : <ReopenButton id={vuln.id} action={action} />}
818 </div>
819 )}
820 </div>
821 {vuln.state === "open" && !vuln.fixedVersion && (
822 <div className="mt-2 rounded-md border border-warn/30 bg-warn/5 px-3 py-2 text-xs">
823 <p className="flex items-center gap-1.5 font-medium text-warn">
824 <ShieldAlert size={13} />
825 No patched version available
826 </p>
827 <p className="mt-1 text-muted">
828 No release fixes this yet. Dependencies are checked again daily
A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.829 {updatesWait
830 ? ". A fix is applied once the repository is taken over or moved to g1t."
831 : upkeep
832 ? ", and g1t opens a pull request when a fix is published."
833 : "; turn on security updates and g1t opens a pull request when a fix is published."}
Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail834 </p>
835 <div className="mt-2 flex flex-wrap items-center gap-2">
836 <a href={osv} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-fg-soft hover:text-fg hover:underline">
837 Read the advisory
838 <ExternalLink size={10} />
839 </a>
840 {canDismiss && dismiss("tolerable_risk", "Dismiss as tolerable risk")}
841 </div>
842 </div>
843 )}
844 <ActivityLog entries={alertActivity(vuln, activity)} base={base} open={focused && vuln.state !== "open"} />
845 </li>
846 );
847}
848
849export function ScanSummary({
850 scan,
851}: {
852 scan: { history: string; commitsScanned: number; historyFinishedAt: string | null; dependenciesScannedAt: string | null; dependenciesError: string | null; lockfiles: string[] };
853}) {
854 const history =
855 scan.history === "done" ? (
856 <>
857 History scanned: {scan.commitsScanned.toLocaleString()} commits
858 {scan.historyFinishedAt && (
859 <>
860 , <TimeAgo at={scan.historyFinishedAt} />
861 </>
862 )}
863 </>
864 ) : scan.history === "stopped" ? (
865 "History scan paused: the workspace reached its spending limit"
866 ) : scan.history === "running" ? (
867 `Scanning history: ${scan.commitsScanned.toLocaleString()} commits so far`
868 ) : (
869 "History scan queued"
870 );
871 return (
872 <div className="flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted">
873 <span className="inline-flex items-center gap-1.5">
874 <KeyRound size={12} />
875 {history}
876 </span>
877 <span className="inline-flex items-center gap-1.5">
878 <Package size={12} />
879 {scan.dependenciesScannedAt ? (
880 <>
881 Dependencies read <TimeAgo at={scan.dependenciesScannedAt} />
882 {scan.lockfiles.length > 0 ? <> from {scan.lockfiles.join(", ")}</> : " (no lockfiles found)"}
883 </>
884 ) : (
885 "Dependencies not read yet"
886 )}
887 </span>
888 {scan.dependenciesError && (
889 <span className="inline-flex items-center gap-1.5 text-warn">
890 <ShieldAlert size={12} />
891 {scan.dependenciesError}
892 </span>
893 )}
894 </div>
895 );
896}

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