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

93 lines4,245 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 { ChevronRight, ShieldCheck } from "lucide-react";
2import { Link, data } from "react-router";
3
4import { SEVERITIES, type SeverityCounts } from "@g1t/contracts";
5
6import type { Route } from "./+types/security";
7import { page } from "../../lib/meta";
8import { SeverityCountsGrid, SeverityCountsInline } from "../../components/security";
9import { TimeAgo } from "../../components/ui";
10import { Badge } from "../../components/ui/badge";
11import { security } from "../../lib/services.server";
12import { getViewer, roleIn, unwrap } from "../../lib/session.server";
13
14export function meta({ params, ...args }: Route.MetaArgs) {
15 return page(args, { title: `Security · ${params.owner} · g1t` });
16}
17
18export async function loader({ params, context }: Route.LoaderArgs) {
19 const viewer = getViewer(context);
20 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
21 const projects = unwrap(await security.workspace(params.owner, viewer));
22 const total = Object.fromEntries(SEVERITIES.map((severity) => [severity, 0])) as SeverityCounts;
23 for (const project of projects) {
24 for (const severity of SEVERITIES) total[severity] += project.counts[severity];
25 }
26 // Most to fix first.
27 projects.sort(
28 (a, b) =>
29 SEVERITIES.reduce((order, severity) => order || b.counts[severity] - a.counts[severity], 0) || a.name.localeCompare(b.name),
30 );
31 return { projects, total };
32}
33
34export default function WorkspaceSecurity({ loaderData, params }: Route.ComponentProps) {
35 const { projects, total } = loaderData;
36 return (
37 <div className="space-y-6">
38 <div>
39 <h2 className="flex items-center gap-2 text-xl font-semibold tracking-tight">
40 <ShieldCheck size={19} className="text-accent" />
41 Security across projects
42 </h2>
43 <p className="mt-1.5 max-w-2xl text-sm text-muted">
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily44 Open alerts in every project of {params.owner}: secrets found in pushes and history, and vulnerable
45 dependencies. Each project's Security page has the details and the security update g1t opened for each.
46 </p>
47 </div>
48 <div>
49 <SeverityCountsGrid counts={total} />
50 <p className="mt-2 text-xs text-faint">
51 Open alerts by severity. A secret in the history that looks real counts as critical; blocked pushes and likely test
52 values do not.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API53 </p>
54 </div>
55 {projects.length === 0 ? (
56 <p className="rounded-xl border border-dashed border-line px-4 py-6 text-sm text-muted">
57 No project has been scanned yet. Each one is scanned on its next push to its default branch, or when its Security
58 page is first opened.
59 </p>
60 ) : (
61 <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface">
62 {projects.map((project) => (
63 <li key={project.repoId}>
64 <Link
65 to={`/${params.owner}/${project.name}/security`}
66 className="group flex flex-col gap-2 px-4 py-3 transition-colors hover:bg-raised/50 sm:flex-row sm:items-center"
67 >
68 <span className="min-w-0 grow">
69 <span className="flex flex-wrap items-center gap-2">
70 <span className="font-mono text-sm font-medium">{project.name}</span>
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily71 {!project.upkeep && <Badge>security updates off</Badge>}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API72 </span>
73 <span className="mt-0.5 block text-xs text-faint">
74 {project.secrets} {project.secrets === 1 ? "secret" : "secrets"} · {project.vulnerabilities}{" "}
75 {project.vulnerabilities === 1 ? "vulnerability" : "vulnerabilities"}
76 {project.dependenciesScannedAt && (
77 <>
78 {" "}
79 · read <TimeAgo at={project.dependenciesScannedAt} />
80 </>
81 )}
82 </span>
83 </span>
84 <SeverityCountsInline counts={project.counts} />
85 <ChevronRight size={15} className="hidden shrink-0 text-faint group-hover:text-fg sm:block" />
86 </Link>
87 </li>
88 ))}
89 </ul>
90 )}
91 </div>
92 );
93}