Skip to content

g1t/apps/web/app/components/security.tsx

855 lines31,141 bytesCodeBlame

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.

Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1/**
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2 * 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.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API6 */
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily7import {
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,
22 ShieldAlert,
23 ShieldCheck,
24} from "lucide-react";
25import { type ReactNode, useEffect, useRef, useState } from "react";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API26import { Link, useFetcher } from "react-router";
27
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily28import {
29 type AlertActivity,
30 type AlertState,
31 DEPENDENCY_DISMISS_REASONS,
32 type DismissReason,
33 SECRET_DISMISS_REASONS,
34 SEVERITIES,
35 type PullStatus,
36 type SecretFinding,
37 type SecurityUpdate,
38 type Severity,
39 type SeverityCounts,
40 type Vulnerability,
41 dismissLabel,
42} from "@g1t/contracts";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API43
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily44import {
45 type ActivityEntry,
46 type PackageGroup,
47 UPDATE_STATES,
48 alertActivity,
49 groupByPackage,
50 highestFix,
51 latestUpdate,
52 splitSecrets,
53 worstSeverity,
54} from "../lib/security-alerts";
55import { Avatar, TimeAgo } from "./ui";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API56import { Badge, type BadgeTone } from "./ui/badge";
Merge branch 'worktree-agent-ae1299e92e4462012'57import { Hint } from "./ui/hint";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API58import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily59import { RadioGroup, RadioOption } from "./ui/radio-group";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API60
61type Done = { ok: boolean; error?: string } | undefined;
62
63const SEVERITY: Record<Severity, { label: string; tone: BadgeTone }> = {
64 critical: { label: "Critical", tone: "danger" },
65 high: { label: "High", tone: "warn" },
66 medium: { label: "Medium", tone: "merged" },
67 low: { label: "Low", tone: "info" },
68 unknown: { label: "Unrated", tone: "neutral" },
69};
70
71
72export function SeverityBadge({ severity }: { severity: Severity }) {
73 return <Badge tone={SEVERITY[severity].tone}>{SEVERITY[severity].label}</Badge>;
74}
75
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily76/** Open alerts by severity, one tile each. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API77export function SeverityCountsGrid({ counts }: { counts: SeverityCounts }) {
78 return (
79 <div className="grid grid-cols-2 gap-3 sm:grid-cols-5">
80 {SEVERITIES.map((severity) => (
81 <div key={severity} className="rounded-xl border border-line bg-surface px-4 py-3">
82 <p className="text-xs text-muted">{SEVERITY[severity].label}</p>
83 <p className={`mt-1 text-2xl font-semibold tabular-nums ${counts[severity] > 0 && severity === "critical" ? "text-danger" : ""}`}>
84 {counts[severity]}
85 </p>
86 </div>
87 ))}
88 </div>
89 );
90}
91
92/** A compact row of severity counts, for a list of projects. */
93export function SeverityCountsInline({ counts }: { counts: SeverityCounts }) {
94 const shown = SEVERITIES.filter((severity) => counts[severity] > 0);
95 if (shown.length === 0) {
96 return (
Merge branch 'worktree-agent-ad7c6d88d93adc817'97 <span className="inline-flex items-center gap-1 text-xs text-success">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API98 <ShieldCheck size={13} />
99 Nothing open
100 </span>
101 );
102 }
103 return (
104 <span className="flex flex-wrap gap-1.5">
105 {shown.map((severity) => (
106 <Badge key={severity} tone={SEVERITY[severity].tone}>
107 {counts[severity]} {SEVERITY[severity].label.toLowerCase()}
108 </Badge>
109 ))}
110 </span>
111 );
112}
113
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily114const STATE_FILTERS: { state: AlertState; label: string; icon: ReactNode }[] = [
115 { state: "open", label: "Open", icon: <CircleDot size={14} /> },
116 { state: "dismissed", label: "Dismissed", icon: <CircleSlash size={14} /> },
117 { state: "fixed", label: "Fixed", icon: <CircleCheck size={14} /> },
118];
119
120/** Open, Dismissed and Fixed, with how many alerts each holds. */
121export function StateFilter({
122 counts,
123 value,
124 onChange,
125}: {
126 counts: Record<AlertState, number>;
127 value: AlertState;
128 onChange: (state: AlertState) => void;
129}) {
130 return (
131 <div role="group" aria-label="Filter alerts" className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
132 {STATE_FILTERS.map(({ state, label, icon }) => (
133 <button
134 key={state}
135 type="button"
136 aria-pressed={value === state}
137 onClick={() => onChange(state)}
138 className={`inline-flex items-center gap-1.5 transition-colors ${
139 value === state ? "font-medium text-fg" : "text-muted hover:text-fg"
140 }`}
141 >
142 {icon}
143 {counts[state]} {label}
144 </button>
145 ))}
146 </div>
147 );
148}
149
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API150const TEXTAREA =
151 "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";
152
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily153const SMALL_BUTTON =
154 "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";
155
156/** 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 bar157export function DismissDialog({
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily158 id,
159 title,
160 detail,
161 reasons,
162 note,
163 action,
164 defaultReason,
165 trigger = "Dismiss",
166}: {
167 id: string;
168 title: string;
169 /** What the alert is, in a line of code type. */
170 detail: string;
171 reasons: { reason: DismissReason; label: string; about: string }[];
172 note?: string;
173 action: string;
174 defaultReason?: DismissReason;
175 trigger?: string;
176}) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API177 const fetcher = useFetcher<Done>();
178 const [open, setOpen] = useState(false);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily179 const [reason, setReason] = useState<string>(defaultReason ?? "");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API180 useEffect(() => {
181 if (fetcher.state === "idle" && fetcher.data?.ok) setOpen(false);
182 }, [fetcher.state, fetcher.data]);
183 return (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily184 <Dialog
185 open={open}
186 onOpenChange={(next) => {
187 setOpen(next);
188 if (next) setReason(defaultReason ?? "");
189 }}
190 >
191 <DialogTrigger className={SMALL_BUTTON}>{trigger}</DialogTrigger>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API192 <DialogContent>
193 <DialogHeader>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily194 <DialogTitle>{title}</DialogTitle>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API195 <DialogDescription>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily196 Say why it can stay. The alert keeps your name, the reason and your comment, and anyone with access can reopen it.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API197 </DialogDescription>
198 </DialogHeader>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily199 <fetcher.Form method="post" action={action} className="space-y-4">
200 <input type="hidden" name="intent" value="dismiss" />
201 <input type="hidden" name="id" value={id} />
202 <p className="font-mono text-xs break-all text-muted">{detail}</p>
203 <fieldset>
204 <legend className="text-xs font-medium text-muted">Reason</legend>
205 <RadioGroup name="reason" value={reason} onValueChange={setReason} required className="mt-2 gap-3">
206 {reasons.map((option) => (
207 <RadioOption key={option.reason} value={option.reason} label={option.label} description={option.about} />
208 ))}
209 </RadioGroup>
210 </fieldset>
211 {note && <p className="rounded-md border border-warn/30 bg-warn/5 px-3 py-2 text-xs text-warn">{note}</p>}
212 <label className="block">
213 <span className="mb-1.5 block text-xs font-medium text-muted">Comment (optional)</span>
214 <textarea name="comment" rows={3} maxLength={500} placeholder="What someone reading this later should know." className={TEXTAREA} />
215 </label>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API216 <div className="flex justify-end">
217 <button
218 type="submit"
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily219 disabled={fetcher.state !== "idle" || !reason}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API220 className="rounded-md bg-fg px-3.5 py-2 text-sm font-medium text-bg hover:bg-white disabled:opacity-50"
221 >
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily222 {fetcher.state !== "idle" ? "Dismissing…" : "Dismiss alert"}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API223 </button>
224 </div>
225 {fetcher.data?.error && <p className="text-sm text-danger">{fetcher.data.error}</p>}
226 </fetcher.Form>
227 </DialogContent>
228 </Dialog>
229 );
230}
231
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar232export function ReopenButton({ id, action }: { id: string; action: string }) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily233 const fetcher = useFetcher<Done>();
234 return (
235 <span className="flex flex-col items-end gap-1">
236 <button
237 type="button"
238 disabled={fetcher.state !== "idle"}
239 onClick={() => fetcher.submit({ intent: "reopen", id }, { method: "post", action })}
240 className={SMALL_BUTTON}
241 >
242 {fetcher.state !== "idle" ? "Reopening…" : "Reopen"}
243 </button>
244 {fetcher.data?.error && <span className="text-xs text-danger">{fetcher.data.error}</span>}
245 </span>
246 );
247}
248
249/** What happened to an alert, oldest first, folded away until asked for. */
250function ActivityLog({ entries, base, open }: { entries: ActivityEntry[]; base: string; open?: boolean }) {
251 if (entries.length === 0) return null;
252 return (
253 <details className="group mt-2" open={open}>
254 <summary className="inline-flex cursor-pointer list-none items-center gap-1.5 text-xs text-muted hover:text-fg [&::-webkit-details-marker]:hidden">
255 <History size={12} />
256 <span className="group-open:hidden">Show activity ({entries.length})</span>
257 <span className="hidden group-open:inline">Hide activity</span>
258 </summary>
259 <ol className="mt-2 space-y-2 border-l border-line pl-3">
260 {entries.map((entry) => (
261 <li key={entry.key} className="text-xs">
262 <p className="flex flex-wrap items-center gap-x-1.5 gap-y-1 text-muted">
263 {entry.actor ? (
264 <>
265 <Avatar name={entry.actor} size={16} />
266 <span className="font-medium text-fg">{entry.actor}</span>
267 </>
268 ) : (
269 <CircleDot size={12} className="text-faint" />
270 )}
271 <span>{entry.text}</span>
272 {entry.ref && (
273 <Link
274 to={`${base}/${entry.ref.kind === "pull" ? "pull" : "issues"}/${entry.ref.number}`}
275 className="font-medium text-fg-soft hover:text-fg hover:underline"
276 >
277 #{entry.ref.number}
278 </Link>
279 )}
280 {entry.reason && <Badge>{dismissLabel(entry.reason)}</Badge>}
281 <span className="text-faint">
282 <TimeAgo at={entry.at} />
283 </span>
284 </p>
285 {entry.comment && <p className="mt-1 text-fg-soft wrap-anywhere">“{entry.comment}”</p>}
286 </li>
287 ))}
288 </ol>
289 </details>
290 );
291}
292
293function useFocus<T extends HTMLElement>(focused: boolean) {
294 const ref = useRef<T>(null);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API295 useEffect(() => {
296 if (focused) ref.current?.scrollIntoView({ block: "center" });
297 }, [focused]);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily298 return ref;
299}
300
301const FOCUSED = "bg-accent/5 ring-1 ring-accent/40 ring-inset";
302
303function secretBadge(finding: SecretFinding): { label: string; tone: BadgeTone; about: string } {
304 if (finding.state === "dismissed") {
305 return {
306 label: "Dismissed",
307 tone: "neutral",
308 about: finding.status === "allowed" ? "Pushes carrying it go through." : "Dismissed.",
309 };
310 }
Merge branch 'worktree-agent-ad7c6d88d93adc817'311 if (finding.state === "fixed") return { label: "Revoked", tone: "success", about: "Revoked or rotated." };
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily312 if (finding.status === "blocked") {
313 return { label: "Push blocked", tone: finding.testValue ? "neutral" : "warn", about: "A push carrying it was refused, so it never landed." };
314 }
315 return {
316 label: "In history",
317 tone: finding.testValue ? "neutral" : "danger",
318 about: "In the repository's history. Rotate it with whoever issued it, then dismiss it as revoked.",
319 };
320}
321
322function SecretItem({
323 finding,
324 activity,
325 base,
326 action,
327 focused,
328 canDismiss,
329}: {
330 finding: SecretFinding;
331 activity: AlertActivity[];
332 base: string;
333 action: string;
334 focused: boolean;
335 canDismiss: boolean;
336}) {
337 const ref = useFocus<HTMLLIElement>(focused);
338 const badge = secretBadge(finding);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API339 const landed = finding.source === "history" || finding.status === "open";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily340 const where = `${finding.path}:${finding.line}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API341 return (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily342 <li ref={ref} id={finding.id} className={`flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-start ${focused ? FOCUSED : ""}`}>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API343 <KeyRound size={15} className="mt-0.5 hidden shrink-0 text-muted sm:block" />
344 <div className="min-w-0 grow">
345 <div className="flex flex-wrap items-center gap-2">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar346 <Link to={`${base}/security/secret-scanning/${finding.id}`} className="text-sm font-medium first-letter:uppercase hover:underline">
347 {finding.label}
348 </Link>
Merge branch 'worktree-agent-ae1299e92e4462012'349 <Hint label={badge.about}>
350 <Badge tone={badge.tone}>{badge.label}</Badge>
351 </Hint>
352 {finding.testValue && (
353 <Hint label={finding.testValue}>
354 <Badge>Likely test value</Badge>
355 </Hint>
356 )}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar357 {finding.validity === "active" && (
Merge branch 'worktree-agent-ae1299e92e4462012'358 <Hint label="Its issuer says it still works">
359 <Badge tone="danger">Active</Badge>
360 </Hint>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar361 )}
Merge branch 'worktree-agent-ae1299e92e4462012'362 {finding.validity === "inactive" && (
363 <Hint label="Its issuer refused it: revoked or expired">
364 <Badge>Inactive</Badge>
365 </Hint>
366 )}
367 {finding.bypass && (
368 <Hint label={`Bypassed by ${finding.bypass.by}`}>
369 <Badge tone="warn">Bypassed</Badge>
370 </Hint>
371 )}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily372 {finding.state === "dismissed" && finding.dismissedReason && <Badge>{dismissLabel(finding.dismissedReason)}</Badge>}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API373 </div>
374 <p className="mt-1 truncate font-mono text-xs">
375 {landed ? (
376 <Link to={`${base}/blob/${finding.commit}/${finding.path}#L${finding.line}`} className="text-fg-soft hover:text-fg hover:underline">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily377 {where}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API378 </Link>
379 ) : (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily380 <span className="text-fg-soft">{where}</span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API381 )}
382 <span className="text-faint"> · {finding.preview}</span>
383 </p>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily384 {finding.testValue && <p className="mt-1 text-xs text-muted">{finding.testValue}</p>}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API385 <p className="mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-faint">
386 <span>
387 {finding.source === "push" ? "in a push" : "in history"}
388 {finding.foundBy && <> by {finding.foundBy}</>}, commit{" "}
389 {landed ? (
390 <Link to={`${base}/commit/${finding.commit}`} className="font-mono hover:text-fg">
391 {finding.commit.slice(0, 7)}
392 </Link>
393 ) : (
394 <span className="font-mono">{finding.commit.slice(0, 7)}</span>
395 )}
396 </span>
397 <span>
398 found <TimeAgo at={finding.foundAt} />
399 </span>
400 </p>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily401 <ActivityLog entries={alertActivity(finding, activity)} base={base} open={focused && finding.state !== "open"} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API402 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily403 {canDismiss && (
404 <div className="flex shrink-0 items-center gap-1.5">
405 {finding.state === "open" ? (
406 <DismissDialog
407 id={finding.id}
408 title={`Dismiss ${finding.label}`}
409 detail={`${where} · ${finding.preview}`}
410 reasons={SECRET_DISMISS_REASONS}
411 note={
412 finding.status === "blocked" && !finding.testValue
413 ? "Dismissing it lets the same push through, unless you dismiss it as revoked."
414 : undefined
415 }
416 action={action}
417 />
418 ) : (
419 <ReopenButton id={finding.id} action={action} />
420 )}
421 </div>
422 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API423 </li>
424 );
425}
426
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily427function Empty({ title, children }: { title: string; children: ReactNode }) {
428 return (
429 <div className="rounded-xl border border-dashed border-line px-6 py-10 text-center">
Merge branch 'worktree-agent-ad7c6d88d93adc817'430 <ShieldCheck size={22} className="mx-auto text-success" />
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily431 <p className="mt-2 font-medium">{title}</p>
432 <p className="mt-1 text-sm text-muted">{children}</p>
433 </div>
434 );
435}
436
437const LIST = "divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface";
438
439/** The secret alerts in one state: on Open, real ones first, then likely test values. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API440export function SecretsList({
441 secrets,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily442 state,
443 activity,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API444 base,
445 action,
446 focus,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily447 canDismiss,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API448}: {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily449 /** Already filtered to `state`. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API450 secrets: SecretFinding[];
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily451 state: AlertState;
452 activity: AlertActivity[];
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API453 base: string;
454 action: string;
455 focus: string | null;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily456 /** Whether the viewer may dismiss and reopen secret alerts (Admin). */
457 canDismiss: boolean;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API458}) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily459 const item = (finding: SecretFinding) => (
460 <SecretItem
461 key={finding.id}
462 finding={finding}
463 activity={activity}
464 base={base}
465 action={action}
466 focused={finding.id === focus}
467 canDismiss={canDismiss}
468 />
469 );
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API470 if (secrets.length === 0) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily471 return state === "open" ? (
472 <Empty title="No open secret alerts">
473 Pushes that add a key or a token are refused before they land, and the history is scanned once in the background.
474 </Empty>
475 ) : (
476 <Empty title={state === "dismissed" ? "No dismissed secret alerts" : "No revoked secrets"}>
477 {state === "dismissed"
478 ? "Alerts someone dismissed as a false positive, a test value or accepted are listed here."
479 : "Secrets dismissed as revoked are listed here."}
480 </Empty>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API481 );
482 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily483 if (state !== "open") return <ul className={LIST}>{secrets.map(item)}</ul>;
484 const { real, tests } = splitSecrets(secrets);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API485 return (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily486 <div className="space-y-5">
487 {real.length > 0 ? (
488 <ul className={LIST}>{real.map(item)}</ul>
489 ) : (
490 <Empty title="No secrets that look real">Only likely test values are open, and they never block a push.</Empty>
491 )}
492 {tests.length > 0 && (
493 <section>
494 <h4 className="text-sm font-medium">Likely test values</h4>
495 <p className="mt-0.5 mb-2 text-xs text-muted">
496 These look made for tests or documentation. They never block a push and are not counted as critical; dismiss them
497 to clear the list.
498 </p>
499 <ul className={LIST}>{tests.map(item)}</ul>
500 </section>
501 )}
502 </div>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API503 );
504}
505
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily506/** Where the legacy upgrade issue for a package stands, as the page loads it. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API507export type UpgradeFix = {
508 number: number;
509 state: "open" | "closed";
510 /** The newest pull request for the issue, if any. */
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily511 pull: { number: number; status: PullStatus; agent: string | null } | null;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API512 resolvedBy: number | null;
513};
514
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily515/** A security update's pull request as the page loads it. */
516export type PullInfo = { number: number; status: PullStatus; title: string };
517
518function FixLink({ issue, fix, base }: { issue: number; fix: UpgradeFix | undefined; base: string }) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API519 const pull = fix?.pull;
520 return (
521 <span className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily522 <span className="text-muted">Upgrade issue</span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API523 <Link to={`${base}/issues/${issue}`} className="inline-flex items-center gap-1 text-fg-soft hover:text-fg">
Merge branch 'worktree-agent-ad7c6d88d93adc817'524 {fix?.state === "closed" ? <CircleCheck size={12} className="text-merged" /> : <CircleDot size={12} className="text-success" />}#{issue}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API525 </Link>
526 {pull && (
527 <Link to={`${base}/pull/${pull.number}`} className="inline-flex items-center gap-1 text-muted hover:text-fg">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily528 {pull.agent ? <Bot size={12} /> : <GitPullRequest size={12} />}#{pull.number} {pull.status === "draft" ? "in progress" : pull.status}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API529 </Link>
530 )}
531 </span>
532 );
533}
534
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily535const PULL_ICON: Record<PullStatus, ReactNode> = {
536 draft: <GitPullRequest size={13} className="text-muted" />,
Merge branch 'worktree-agent-ad7c6d88d93adc817'537 open: <GitPullRequest size={13} className="text-success" />,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily538 merged: <GitMerge size={13} className="text-merged" />,
539 closed: <GitPullRequestClosed size={13} className="text-danger" />,
540};
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API541
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily542/** Where g1t's security update for a package stands. */
543function UpdateStatus({ update, name, pulls, base }: { update: SecurityUpdate; name: string; pulls: Record<number, PullInfo>; base: string }) {
544 const meta = UPDATE_STATES[update.state];
545 const pull = update.pull != null ? pulls[update.pull] : undefined;
546 const about: Record<SecurityUpdate["state"], ReactNode> = {
547 requested: <>A sandbox is raising {name} to {update.target}.</>,
548 open: <>It raises {name} to {update.target} and lands through your branch's required checks.</>,
549 merged: <>{name} was raised to {update.target}.</>,
550 closed: <>The pull request was closed without merging.</>,
551 superseded: <>A newer update replaced it, or the package is no longer vulnerable, so g1t closed it.</>,
552 needs_code: (
553 <>
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent554 Raising {name} to {update.target} needs code changes, so g1t opened an issue to make them
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily555 {update.issue != null && (
556 <>
557 :{" "}
558 <Link to={`${base}/issues/${update.issue}`} className="font-medium text-fg-soft hover:text-fg hover:underline">
559 #{update.issue}
560 </Link>
561 </>
562 )}
563 .
564 </>
565 ),
566 failed: <>{update.error ?? "g1t could not make the change."}</>,
567 };
568 return (
569 <div className="mt-2.5 rounded-lg border border-line bg-bg/40 px-3 py-2 text-xs sm:ml-7">
570 <div className="flex flex-wrap items-center gap-x-2.5 gap-y-1.5">
571 {update.state === "requested" ? (
572 <Loader size={13} className="animate-spin text-info motion-reduce:animate-none" />
573 ) : update.state === "failed" || update.state === "needs_code" ? (
574 <CircleAlert size={13} className={update.state === "failed" ? "text-danger" : "text-warn"} />
575 ) : (
576 PULL_ICON[pull?.status ?? (update.state === "merged" ? "merged" : update.state === "open" ? "open" : "closed")]
577 )}
578 <Badge tone={meta.tone}>{meta.label}</Badge>
579 {update.pull != null && (
580 <Link to={`${base}/pull/${update.pull}`} className="font-medium text-fg-soft hover:text-fg hover:underline">
581 #{update.pull}
582 {pull && <span className="font-normal text-muted"> {pull.status === "draft" ? "draft" : pull.status}</span>}
583 </Link>
584 )}
585 {update.branch && (
586 <span className="inline-flex min-w-0 items-center gap-1 font-mono text-muted">
587 <GitBranch size={12} className="shrink-0" />
588 <span className="truncate">{update.branch}</span>
589 </span>
590 )}
591 <span className="text-faint">
592 <TimeAgo at={update.updatedAt} />
593 </span>
594 </div>
595 <p className={`mt-1 ${update.state === "failed" ? "text-danger" : "text-muted"} wrap-anywhere`}>{about[update.state]}</p>
596 </div>
597 );
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API598}
599
600export function VulnerabilityList({
601 vulnerabilities,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily602 state,
603 activity,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API604 fixes,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily605 pulls,
606 upkeep,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API607 base,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily608 action,
609 focus,
610 canDismiss,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API611}: {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily612 /** Already filtered to `state`. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API613 vulnerabilities: Vulnerability[];
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily614 state: AlertState;
615 activity: AlertActivity[];
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API616 fixes: Record<number, UpgradeFix>;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily617 pulls: Record<number, PullInfo>;
618 /** Whether security updates are on. */
619 upkeep: boolean;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API620 base: string;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily621 action: string;
622 focus: string | null;
623 /** Whether the viewer may dismiss and reopen dependency alerts (Write). */
624 canDismiss: boolean;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API625}) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily626 const packages = groupByPackage(vulnerabilities);
627 if (packages.length === 0) {
628 return state === "open" ? (
629 <Empty title="No known vulnerabilities">
630 Every package the lockfiles resolve is checked against the OSV database on each push to the default branch, and daily.
631 </Empty>
632 ) : (
633 <Empty title={state === "dismissed" ? "No dismissed dependency alerts" : "Nothing fixed yet"}>
634 {state === "dismissed"
635 ? "Alerts someone dismissed, with their reason, are listed here."
636 : "Alerts whose package was upgraded, or is no longer vulnerable, are listed here."}
637 </Empty>
638 );
639 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API640 return (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily641 <ul className={LIST}>
642 {packages.map((group) => (
643 <PackageItem
644 key={group.key}
645 group={group}
646 activity={activity}
647 fixes={fixes}
648 pulls={pulls}
649 upkeep={upkeep}
650 base={base}
651 action={action}
652 focus={focus}
653 canDismiss={canDismiss}
654 />
655 ))}
656 </ul>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API657 );
658}
659
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily660function PackageItem({
661 group,
662 activity,
663 fixes,
664 pulls,
665 upkeep,
666 base,
667 action,
668 focus,
669 canDismiss,
670}: {
671 group: PackageGroup;
672 activity: AlertActivity[];
673 fixes: Record<number, UpgradeFix>;
674 pulls: Record<number, PullInfo>;
675 upkeep: boolean;
676 base: string;
677 action: string;
678 focus: string | null;
679 canDismiss: boolean;
680}) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API681 const versions = [...new Set(group.vulns.map((vuln) => vuln.version))];
682 const manifests = [...new Set(group.vulns.map((vuln) => vuln.manifest))];
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily683 const target = highestFix(group.vulns);
684 const update = latestUpdate(group.vulns);
685 const issue = update ? null : (group.vulns.find((vuln) => vuln.issue != null)?.issue ?? null);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API686 return (
687 <li className="px-4 py-3">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily688 <div className="flex items-start gap-3">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API689 <Package size={15} className="mt-0.5 hidden shrink-0 text-muted sm:block" />
690 <div className="min-w-0 grow">
691 <div className="flex flex-wrap items-center gap-2">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily692 <span className="font-mono text-sm font-medium break-all">{group.name}</span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API693 <span className="font-mono text-xs text-muted">{versions.join(", ")}</span>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily694 <Badge>{group.ecosystem}</Badge>
695 <SeverityBadge severity={worstSeverity(group.vulns)} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API696 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily697 <p className="mt-1 text-xs text-faint wrap-anywhere">
698 {target ? <>Fixed in {target}</> : "No patched version"} · locked in{" "}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API699 <span className="font-mono">{manifests.join(", ")}</span>
700 </p>
701 </div>
702 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily703 {update && <UpdateStatus update={update} name={group.name} pulls={pulls} base={base} />}
704 {issue != null && (
705 <div className="mt-2 sm:ml-7">
706 <FixLink issue={issue} fix={fixes[issue]} base={base} />
707 </div>
708 )}
709 <ul className="mt-2.5 space-y-px overflow-hidden rounded-lg border border-line sm:ml-7">
710 {group.vulns.map((vuln) => (
711 <AdvisoryItem
712 key={vuln.id}
713 vuln={vuln}
714 showManifest={manifests.length > 1}
715 activity={activity}
716 upkeep={upkeep}
717 base={base}
718 action={action}
719 focused={vuln.id === focus}
720 canDismiss={canDismiss}
721 />
722 ))}
723 </ul>
724 </li>
725 );
726}
727
728function AdvisoryItem({
729 vuln,
730 showManifest,
731 activity,
732 upkeep,
733 base,
734 action,
735 focused,
736 canDismiss,
737}: {
738 vuln: Vulnerability;
739 showManifest: boolean;
740 activity: AlertActivity[];
741 upkeep: boolean;
742 base: string;
743 action: string;
744 focused: boolean;
745 canDismiss: boolean;
746}) {
747 const ref = useFocus<HTMLLIElement>(focused);
748 const osv = `https://osv.dev/vulnerability/${vuln.osvId}`;
749 const dismiss = (reason?: DismissReason, trigger?: string) => (
750 <DismissDialog
751 id={vuln.id}
752 title={`Dismiss ${vuln.advisory}`}
753 detail={`${vuln.package} ${vuln.version} · ${vuln.manifest}`}
754 reasons={DEPENDENCY_DISMISS_REASONS}
755 action={action}
756 defaultReason={reason}
757 trigger={trigger}
758 />
759 );
760 return (
761 <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 : ""}`}>
762 <div className="flex flex-col gap-2 sm:flex-row sm:items-start">
763 <div className="min-w-0 grow text-xs">
764 <p className="flex flex-wrap items-center gap-x-2 gap-y-1">
765 <a href={osv} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 font-mono text-fg-soft hover:text-fg">
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API766 {vuln.advisory}
767 <ExternalLink size={10} />
768 </a>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily769 <SeverityBadge severity={vuln.severity} />
770 {vuln.fixedVersion && <span className="text-faint">fixed in {vuln.fixedVersion}</span>}
771 {showManifest && <span className="font-mono text-faint">{vuln.manifest}</span>}
772 {vuln.state === "dismissed" && vuln.dismissedReason && <Badge>{dismissLabel(vuln.dismissedReason)}</Badge>}
773 </p>
774 <p className="mt-1 text-muted wrap-anywhere">{vuln.summary}</p>
775 </div>
776 {canDismiss && vuln.state !== "fixed" && (
777 <div className="flex shrink-0 items-center gap-1.5">
778 {vuln.state === "open" ? dismiss() : <ReopenButton id={vuln.id} action={action} />}
779 </div>
780 )}
781 </div>
782 {vuln.state === "open" && !vuln.fixedVersion && (
783 <div className="mt-2 rounded-md border border-warn/30 bg-warn/5 px-3 py-2 text-xs">
784 <p className="flex items-center gap-1.5 font-medium text-warn">
785 <ShieldAlert size={13} />
786 No patched version available
787 </p>
788 <p className="mt-1 text-muted">
789 No release fixes this yet. Dependencies are checked again daily
790 {upkeep
791 ? ", and g1t opens a pull request when a fix is published."
792 : "; turn on security updates and g1t opens a pull request when a fix is published."}
793 </p>
794 <div className="mt-2 flex flex-wrap items-center gap-2">
795 <a href={osv} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-fg-soft hover:text-fg hover:underline">
796 Read the advisory
797 <ExternalLink size={10} />
798 </a>
799 {canDismiss && dismiss("tolerable_risk", "Dismiss as tolerable risk")}
800 </div>
801 </div>
802 )}
803 <ActivityLog entries={alertActivity(vuln, activity)} base={base} open={focused && vuln.state !== "open"} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API804 </li>
805 );
806}
807
808export function ScanSummary({
809 scan,
810}: {
811 scan: { history: string; commitsScanned: number; historyFinishedAt: string | null; dependenciesScannedAt: string | null; dependenciesError: string | null; lockfiles: string[] };
812}) {
813 const history =
814 scan.history === "done" ? (
815 <>
816 History scanned: {scan.commitsScanned.toLocaleString()} commits
817 {scan.historyFinishedAt && (
818 <>
819 , <TimeAgo at={scan.historyFinishedAt} />
820 </>
821 )}
822 </>
823 ) : scan.history === "stopped" ? (
824 "History scan paused: the workspace reached its spending limit"
825 ) : scan.history === "running" ? (
826 `Scanning history: ${scan.commitsScanned.toLocaleString()} commits so far`
827 ) : (
828 "History scan queued"
829 );
830 return (
831 <div className="flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted">
832 <span className="inline-flex items-center gap-1.5">
833 <KeyRound size={12} />
834 {history}
835 </span>
836 <span className="inline-flex items-center gap-1.5">
837 <Package size={12} />
838 {scan.dependenciesScannedAt ? (
839 <>
840 Dependencies read <TimeAgo at={scan.dependenciesScannedAt} />
841 {scan.lockfiles.length > 0 ? <> from {scan.lockfiles.join(", ")}</> : " (no lockfiles found)"}
842 </>
843 ) : (
844 "Dependencies not read yet"
845 )}
846 </span>
847 {scan.dependenciesError && (
848 <span className="inline-flex items-center gap-1.5 text-warn">
849 <ShieldAlert size={12} />
850 {scan.dependenciesError}
851 </span>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily852 )}
853 </div>
854 );
855}

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