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 API | 1 | /** |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 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. | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 6 | */ |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 7 | import { |
| 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"; | |
| 25 | import { 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 API | 26 | import { Link, useFetcher } from "react-router"; |
| 27 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 28 | import { |
| 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 API | 43 | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 44 | import { |
| 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"; | |
| 55 | import { Avatar, TimeAgo } from "./ui"; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 56 | import { Badge, type BadgeTone } from "./ui/badge"; |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 57 | import { Hint } from "./ui/hint"; |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 58 | import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog"; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 59 | import { 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 API | 60 | |
| 61 | type Done = { ok: boolean; error?: string } | undefined; | |
| 62 | ||
| 63 | const 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 | ||
| 72 | export 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 daily | 76 | /** 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 API | 77 | export 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. */ | |
| 93 | export 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 API | 98 | <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 daily | 114 | const 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. */ | |
| 121 | export 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 API | 150 | const 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 daily | 153 | const 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 bar | 157 | export function DismissDialog({ |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 158 | 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 API | 177 | const fetcher = useFetcher<Done>(); |
| 178 | const [open, setOpen] = useState(false); | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 179 | 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 API | 180 | 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 daily | 184 | <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 API | 192 | <DialogContent> |
| 193 | <DialogHeader> | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 194 | <DialogTitle>{title}</DialogTitle> |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 195 | <DialogDescription> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 196 | 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 API | 197 | </DialogDescription> |
| 198 | </DialogHeader> | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 199 | <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 API | 216 | <div className="flex justify-end"> |
| 217 | <button | |
| 218 | type="submit" | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 219 | disabled={fetcher.state !== "idle" || !reason} |
| You choose how g1t looks: Auto follows your system, Light and Dark stay put, set from the account menu, the phone's You and help sheet, Settings → Account or the command palette; the page is drawn in your choice from the first byte, every colour has a light value that reads at 4.5:1 or better, code and diffs are highlighted for both, diagrams and the document editor follow along, the product tour and the g1t mark's avatar stay dark, and Dark is what you see until you choose; the workspaces guide says how. | 220 | className="rounded-md bg-fg px-3.5 py-2 text-sm font-medium text-bg hover:bg-fg-hover disabled:opacity-50" |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 221 | > |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 222 | {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 API | 223 | </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 bar | 232 | export function ReopenButton({ id, action }: { id: string; action: string }) { |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 233 | 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. */ | |
| 250 | function 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 | ||
| 293 | function 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 API | 295 | 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 daily | 298 | return ref; |
| 299 | } | |
| 300 | ||
| 301 | const FOCUSED = "bg-accent/5 ring-1 ring-accent/40 ring-inset"; | |
| 302 | ||
| 303 | function 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 daily | 312 | 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 | ||
| 322 | function 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 API | 339 | const landed = finding.source === "history" || finding.status === "open"; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 340 | 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 API | 341 | return ( |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 342 | <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 API | 343 | <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 bar | 346 | <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 bar | 357 | {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 bar | 361 | )} |
| 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 daily | 372 | {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 API | 373 | </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 daily | 377 | {where} |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 378 | </Link> |
| 379 | ) : ( | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 380 | <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 API | 381 | )} |
| 382 | <span className="text-faint"> · {finding.preview}</span> | |
| 383 | </p> | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 384 | {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 API | 385 | <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 daily | 401 | <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 API | 402 | </div> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 403 | {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 API | 423 | </li> |
| 424 | ); | |
| 425 | } | |
| 426 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 427 | function 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 daily | 431 | <p className="mt-2 font-medium">{title}</p> |
| 432 | <p className="mt-1 text-sm text-muted">{children}</p> | |
| 433 | </div> | |
| 434 | ); | |
| 435 | } | |
| 436 | ||
| 437 | const 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 API | 440 | export function SecretsList({ |
| 441 | secrets, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 442 | state, |
| 443 | activity, | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 444 | base, |
| 445 | action, | |
| 446 | focus, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 447 | canDismiss, |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 448 | }: { |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 449 | /** Already filtered to `state`. */ |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 450 | secrets: SecretFinding[]; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 451 | state: AlertState; |
| 452 | activity: AlertActivity[]; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 453 | base: string; |
| 454 | action: string; | |
| 455 | focus: string | null; | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 456 | /** 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 API | 458 | }) { |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 459 | 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 API | 470 | if (secrets.length === 0) { |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 471 | 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 API | 481 | ); |
| 482 | } | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 483 | 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 API | 485 | return ( |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 486 | <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 API | 503 | ); |
| 504 | } | |
| 505 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 506 | /** 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 API | 507 | export 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 daily | 511 | 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 API | 512 | resolvedBy: number | null; |
| 513 | }; | |
| 514 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 515 | /** A security update's pull request as the page loads it. */ |
| 516 | export type PullInfo = { number: number; status: PullStatus; title: string }; | |
| 517 | ||
| 518 | function 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 API | 519 | 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 daily | 522 | <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 API | 523 | <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 API | 525 | </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 daily | 528 | {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 API | 529 | </Link> |
| 530 | )} | |
| 531 | </span> | |
| 532 | ); | |
| 533 | } | |
| 534 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 535 | const 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 daily | 538 | 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 API | 541 | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 542 | /** Where g1t's security update for a package stands. */ |
| 543 | function 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-agent | 554 | 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 daily | 555 | {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 API | 598 | } |
| 599 | ||
| 600 | export function VulnerabilityList({ | |
| 601 | vulnerabilities, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 602 | state, |
| 603 | activity, | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 604 | fixes, |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 605 | pulls, |
| 606 | upkeep, | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 607 | base, |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 608 | action, |
| 609 | focus, | |
| 610 | canDismiss, | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 611 | }: { |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 612 | /** Already filtered to `state`. */ |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 613 | vulnerabilities: Vulnerability[]; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 614 | state: AlertState; |
| 615 | activity: AlertActivity[]; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 616 | fixes: Record<number, UpgradeFix>; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 617 | 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 API | 620 | base: string; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 621 | 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 API | 625 | }) { |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 626 | 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 API | 640 | return ( |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 641 | <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 API | 657 | ); |
| 658 | } | |
| 659 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 660 | function 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 API | 681 | 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 daily | 683 | 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 API | 686 | return ( |
| 687 | <li className="px-4 py-3"> | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 688 | <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 API | 689 | <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 daily | 692 | <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 API | 693 | <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 daily | 694 | <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 API | 696 | </div> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 697 | <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 API | 699 | <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 daily | 703 | {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 | ||
| 728 | function 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 API | 766 | {vuln.advisory} |
| 767 | <ExternalLink size={10} /> | |
| 768 | </a> | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 769 | <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 API | 804 | </li> |
| 805 | ); | |
| 806 | } | |
| 807 | ||
| 808 | export 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 daily | 852 | )} |
| 853 | </div> | |
| 854 | ); | |
| 855 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.