Skip to content

g1t/apps/web/app/routes/repo/security.tsx

222 lines10,299 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 API1import { RefreshCw, ShieldCheck } from "lucide-react";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2import { useFetcher, useSearchParams } from "react-router";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API3
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily4import type { AlertState, DismissReason } from "@g1t/contracts";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API5
6import type { Route } from "./+types/security";
7import { page } from "../../lib/meta";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily8import {
9 type PullInfo,
10 ScanSummary,
11 SeverityCountsGrid,
12 StateFilter,
13 type UpgradeFix,
14 VulnerabilityList,
15} from "../../components/security";
Merge branch 'worktree-agent-ae1299e92e4462012'16import { Hint } from "../../components/ui/hint";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API17import { Switch } from "../../components/ui/switch";
18import { security, work } from "../../lib/services.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look19import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
20import { refusal, requireInsider } from "../../lib/access.server";
21import { whyNot } from "../../lib/access";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar22import { alertCapability, countByState, parseAlertState } from "../../lib/security-alerts";
23import { severityCounts } from "../../lib/security-suite";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API24
25export function meta({ params, ...args }: Route.MetaArgs) {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar26 return page(args, { title: `Vulnerabilities · ${params.owner}/${params.repo} · g1t` });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API27}
28
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily29/** Upgrade issues and security update pull requests looked up per page, at most. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API30const MAX_FIXES = 30;
31
32export async function loader({ params, context, request }: Route.LoaderArgs) {
33 // Git's refusal links here; someone signed out signs in first.
34 const viewer = getViewer(context) ?? requireUser(context, request);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look35 // Findings are for people who can push, public project or not: Write and up.
Merge main (membership, two-factor, GitHub repo roles) into tokens36 const { access } = await requireInsider(context, params, "security_alerts");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API37 const repo = { namespace: params.owner, name: params.repo };
38 const overview = unwrap(await security.overview(repo, viewer));
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily39 const unique = (numbers: (number | null | undefined)[]) =>
40 [...new Set(numbers.filter((n): n is number => n != null))].slice(0, MAX_FIXES);
41 // Security updates' pull requests, for their live status.
42 const pullNumbers = unique(overview.vulnerabilities.map((vuln) => vuln.update?.pull));
43 // Older upgrade issues, from before g1t opened pull requests itself.
44 const issueNumbers = unique(overview.vulnerabilities.filter((vuln) => !vuln.update).map((vuln) => vuln.issue));
45 const [pullDetails, issueDetails] = await Promise.all([
46 Promise.all(pullNumbers.map((number) => work.getPull(repo, number, viewer).catch(() => null))),
47 Promise.all(issueNumbers.map((number) => work.getIssue(repo, number, viewer).catch(() => null))),
48 ]);
49 const pulls: Record<number, PullInfo> = {};
50 for (const found of pullDetails) {
51 if (!found?.ok) continue;
52 const { pull } = found.value;
53 pulls[pull.number] = { number: pull.number, status: pull.status, title: pull.title };
54 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API55 const fixes: Record<number, UpgradeFix> = {};
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily56 for (const found of issueDetails) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API57 if (!found?.ok) continue;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily58 const { issue, pulls: issuePulls } = found.value;
59 const latest = issuePulls.at(-1) ?? null;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API60 fixes[issue.number] = {
61 number: issue.number,
62 state: issue.state,
63 resolvedBy: issue.resolvedBy,
64 pull: latest ? { number: latest.number, status: latest.status, agent: latest.runtime === "hosted" ? latest.agent : null } : null,
65 };
66 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily67 return { overview, fixes, pulls, can: access.can };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API68}
69
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily70const DISMISS_REASONS = new Set<string>([
71 "false_positive",
72 "used_in_tests",
73 "revoked",
74 "wont_fix",
75 "fix_started",
76 "no_bandwidth",
77 "tolerable_risk",
78 "inaccurate",
79 "not_used",
80]);
81
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API82export async function action({ params, context, request }: Route.ActionArgs) {
83 assertSameOrigin(request);
84 const user = requireUser(context, request);
85 const repo = { namespace: params.owner, name: params.repo };
86 const form = await request.formData();
87 const intent = String(form.get("intent") ?? "");
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily88 const id = String(form.get("id") ?? "");
89 // Scanning spends compute (Write); security updates are a setting
Merge main (membership, two-factor, GitHub repo roles) into tokens90 // (Maintain); an alert is dismissed or reopened with Write, or by a
91 // security manager of the workspace.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily92 const capability =
93 intent === "rescan" ? "run" : intent === "upkeep" ? "manage_settings" : intent === "dismiss" || intent === "reopen" ? alertCapability(id) : null;
94 if (!capability) return { ok: false, error: "Unknown action." };
95 const refused = await refusal(context, params, capability);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look96 if (refused) return { ok: false, error: refused };
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily97 if (intent === "dismiss") {
98 const reason = String(form.get("reason") ?? "");
99 if (!DISMISS_REASONS.has(reason)) return { ok: false, error: "Choose a reason." };
100 const comment = String(form.get("comment") ?? "").trim().slice(0, 500);
101 const dismissed = await security.dismiss(user, repo, id, reason as DismissReason, comment);
102 return dismissed.ok ? { ok: true } : { ok: false, error: dismissed.error.message };
103 }
104 if (intent === "reopen") {
105 const reopened = await security.reopen(user, repo, id);
106 return reopened.ok ? { ok: true } : { ok: false, error: reopened.error.message };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API107 }
108 if (intent === "rescan") {
109 const scanned = await security.rescan(user, repo);
110 return scanned.ok ? { ok: true } : { ok: false, error: scanned.error.message };
111 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily112 const set = await security.setUpkeep(user, repo, form.get("enabled") === "true");
113 return set.ok ? { ok: true } : { ok: false, error: set.error.message };
114}
115
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API116export default function ProjectSecurity({ loaderData, params }: Route.ComponentProps) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily117 const { overview, fixes, pulls, can } = loaderData;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API118 const base = `/${params.owner}/${params.repo}`;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar119 const action = `${base}/security/vulnerabilities`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API120 const [search, setSearch] = useSearchParams();
121 const rescan = useFetcher<{ ok: boolean; error?: string }>();
122 const upkeep = useFetcher<{ ok: boolean; error?: string }>();
123 const upkeepOn = upkeep.formData ? upkeep.formData.get("enabled") === "true" : overview.upkeep;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily124 const vulnCounts = countByState(overview.vulnerabilities);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar125 // A link to one alert shows it wherever it stands.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API126 const focus = search.get("finding");
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar127 const focused = focus ? overview.vulnerabilities.find((vuln) => vuln.id === focus) : undefined;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily128 const state: AlertState = search.get("state") ? parseAlertState(search.get("state")) : (focused?.state ?? "open");
129 const navigate = (change: (next: URLSearchParams) => void) => {
130 const next = new URLSearchParams(search);
131 change(next);
132 next.delete("finding");
133 setSearch(next, { replace: true, preventScrollReset: true });
134 };
135 const setState = (value: AlertState) =>
136 navigate((next) => (value === "open" ? next.delete("state") : next.set("state", value)));
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API137 return (
138 <div className="max-w-5xl">
139 <div className="flex flex-wrap items-start justify-between gap-4">
140 <div>
141 <h2 className="flex items-center gap-2 text-xl font-semibold tracking-tight">
142 <ShieldCheck size={19} className="text-accent" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar143 Vulnerabilities
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API144 </h2>
145 <p className="mt-1.5 max-w-2xl text-sm text-muted">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar146 Every package the lockfiles resolve is checked for known vulnerabilities, on every push to the default branch and
147 daily. With security updates on, g1t opens a pull request to upgrade each one that has a fix.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API148 </p>
149 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily150 {can.run && (
151 <rescan.Form method="post" action={action}>
152 <input type="hidden" name="intent" value="rescan" />
153 <button
154 type="submit"
155 disabled={rescan.state !== "idle"}
156 className="inline-flex items-center gap-2 rounded-md border border-line px-3 py-1.5 text-sm text-fg/80 transition-colors hover:border-line-strong hover:bg-surface hover:text-fg disabled:opacity-50"
157 >
158 <RefreshCw size={14} className={rescan.state !== "idle" ? "animate-spin" : ""} />
159 {rescan.state !== "idle" ? "Scanning…" : "Re-scan now"}
160 </button>
161 {rescan.data?.error && <p className="mt-1.5 text-xs text-danger">{rescan.data.error}</p>}
162 </rescan.Form>
163 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API164 </div>
165
166 <div className="mt-6">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar167 <SeverityCountsGrid counts={severityCounts(overview.vulnerabilities)} />
168 <p className="mt-2 text-xs text-faint">Open vulnerability alerts by severity.</p>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API169 </div>
170
171 <div className="mt-4">
172 <ScanSummary scan={overview.scan} />
173 </div>
174
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar175 <div className="mt-8">
176 <div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily177 <StateFilter counts={vulnCounts} value={state} onChange={setState} />
178 <div className="mt-3">
179 <VulnerabilityList
180 vulnerabilities={overview.vulnerabilities.filter((vuln) => vuln.state === state)}
181 state={state}
182 activity={overview.activity}
183 fixes={fixes}
184 pulls={pulls}
185 upkeep={overview.upkeep}
186 base={base}
187 action={action}
188 focus={focus}
189 canDismiss={can.push}
190 />
191 </div>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar192 </div>
193 </div>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily194
195 <section className="mt-10" aria-labelledby="security-settings">
196 <h3 id="security-settings" className="text-base font-semibold tracking-tight">
197 Settings
198 </h3>
199 <div className="mt-3 space-y-3">
200 <label className="flex cursor-pointer items-start justify-between gap-4 rounded-xl border border-line bg-surface p-4 transition-colors hover:border-line-strong">
201 <span className="min-w-0">
202 <span className="block text-sm font-medium">Security updates</span>
203 <span className="mt-1 block text-sm text-muted">
204 Open a pull request to upgrade each vulnerable dependency that has a fix. It lands through your branch's
205 required checks.
206 </span>
207 {upkeep.data?.error && <span className="mt-1 block text-xs text-danger">{upkeep.data.error}</span>}
208 </span>
Merge branch 'worktree-agent-ae1299e92e4462012'209 <Hint label={whyNot(can, "manage_settings")} disabled={!can.manage_settings}>
210 <Switch
211 className="mt-0.5"
212 checked={upkeepOn}
213 disabled={upkeep.state !== "idle" || !can.manage_settings}
214 onCheckedChange={(checked) => upkeep.submit({ intent: "upkeep", enabled: String(checked) }, { method: "post", action })}
215 />
216 </Hint>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily217 </label>
218 </div>
219 </section>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API220 </div>
221 );
222}

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