Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail | 1 | import { ChevronRight } from "lucide-react"; |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 2 | import { Link, data } from "react-router"; |
| 3 | ||
| 4 | import { SEVERITIES, type SeverityCounts } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import type { Route } from "./+types/security"; | |
| 7 | import { page } from "../../lib/meta"; | |
| 8 | import { SeverityCountsGrid, SeverityCountsInline } from "../../components/security"; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 9 | import { ActivationPrompt, CARD, CoverageTable, TrendChart, countsLine } from "../../components/security-suite"; |
| 10 | import { WorkspaceSecurityTabs } from "../../components/workspace-security-tabs"; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 11 | import { TimeAgo } from "../../components/ui"; |
| 12 | import { Badge } from "../../components/ui/badge"; | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 13 | import { Card } from "../../components/ui/card"; |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 14 | import { repos, security, securitySuite } from "../../lib/services.server"; |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 15 | import { getViewer, managesSecurity, roleIn, unwrap } from "../../lib/session.server"; |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 16 | import { planPrice } from "../../lib/security-suite.server"; |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 17 | |
| 18 | export function meta({ params, ...args }: Route.MetaArgs) { | |
| 19 | return page(args, { title: `Security · ${params.owner} · g1t` }); | |
| 20 | } | |
| 21 | ||
| 22 | export async function loader({ params, context }: Route.LoaderArgs) { | |
| 23 | const viewer = getViewer(context); | |
| 24 | if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 }); | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 25 | const [scanned, current, overview, requests, price] = await Promise.all([ |
| Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail | 26 | security.workspace(params.owner, viewer), |
| 27 | repos.list(viewer, { namespace: params.owner.toLowerCase() }), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 28 | securitySuite.overview(params.owner, viewer, 30), |
| 29 | securitySuite.bypassRequests(params.owner, viewer, "pending"), | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 30 | planPrice(params.owner, viewer), |
| Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail | 31 | ]); |
| 32 | // Only repositories that are still there: a deleted one's alerts stay | |
| 33 | // with it for its 30 days, but not on this page. | |
| 34 | const live = new Set(current.map((repo) => repo.id)); | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 35 | // Whether each is private, from the repository itself: the security |
| 36 | // service's own record of it can lag behind. | |
| 37 | const privacy = new Map(current.map((repo) => [repo.id, repo.isPrivate])); | |
| Security matches the workspace's other pages and leaves out deleted repositories; every section has a name in the trail | 38 | const projects = unwrap(scanned).filter((project) => live.has(project.repoId)); |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 39 | const total = Object.fromEntries(SEVERITIES.map((severity) => [severity, 0])) as SeverityCounts; |
| 40 | for (const project of projects) { | |
| 41 | for (const severity of SEVERITIES) total[severity] += project.counts[severity]; | |
| 42 | } | |
| 43 | // Most to fix first. | |
| 44 | projects.sort( | |
| 45 | (a, b) => | |
| 46 | SEVERITIES.reduce((order, severity) => order || b.counts[severity] - a.counts[severity], 0) || a.name.localeCompare(b.name), | |
| 47 | ); | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 48 | const full = overview.ok |
| 49 | ? { | |
| 50 | ...overview.value, | |
| 51 | repos: overview.value.repos | |
| 52 | .filter((repo) => live.has(repo.repoId)) | |
| 53 | .map((repo) => ({ ...repo, private: privacy.get(repo.repoId) ?? repo.private })), | |
| 54 | } | |
| 55 | : null; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 56 | return { |
| 57 | projects, | |
| 58 | total, | |
| 59 | overview: full, | |
| 60 | pending: requests.ok ? requests.value.length : 0, | |
| 61 | price, | |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 62 | owner: managesSecurity(viewer, params.owner), |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 63 | }; |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 64 | } |
| 65 | ||
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 66 | const TYPE_LABEL: Record<string, string> = { |
| 67 | secret_scanning: "Secrets", | |
| 68 | code_scanning: "Code scanning", | |
| 69 | vulnerability: "Vulnerabilities", | |
| 70 | }; | |
| 71 | ||
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 72 | export default function WorkspaceSecurity({ loaderData, params }: Route.ComponentProps) { |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 73 | const { projects, total, overview, pending, price, owner } = loaderData; |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 74 | // The full overview counts private repositories with the plan; without it, a workspace |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 75 | // with private ones sees the free list of every repository's open alerts. |
| 76 | const full = overview && (overview.activated || overview.privateHidden === 0); | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 77 | return ( |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 78 | <div className="space-y-8"> |
| 79 | <WorkspaceSecurityTabs owner={params.owner} pending={pending} /> | |
| 80 | {full && overview ? ( | |
| 81 | <> | |
| 82 | <div className="grid gap-3 sm:grid-cols-3"> | |
| 83 | {overview.totals.map((totals) => ( | |
| 84 | <div key={totals.alertType} className={`${CARD} p-4`}> | |
| 85 | <p className="text-xs text-muted">{TYPE_LABEL[totals.alertType]}</p> | |
| 86 | <p className="mt-1 text-2xl font-semibold tabular-nums"> | |
| 87 | {totals.open.critical + totals.open.high + totals.open.medium + totals.open.low + totals.open.unknown} | |
| 88 | <span className="ml-1.5 text-sm font-normal text-muted">open</span> | |
| 89 | </p> | |
| 90 | <p className="mt-1 text-xs text-muted">{countsLine(totals.open)}</p> | |
| 91 | <p className="mt-2 text-xs text-faint"> | |
| 92 | {totals.opened} opened · {totals.closed} closed in 30 days | |
| 93 | </p> | |
| 94 | </div> | |
| 95 | ))} | |
| 96 | </div> | |
| 97 | <TrendChart points={overview.trend} /> | |
| 98 | <section className="space-y-3"> | |
| 99 | <h2 className="text-base font-semibold tracking-tight">Repositories, most in need first</h2> | |
| 100 | <CoverageTable repos={overview.repos} owner={params.owner} /> | |
| 101 | </section> | |
| 102 | </> | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 103 | ) : ( |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 104 | <> |
| 105 | <ActivationPrompt workspace={params.owner} feature="The security overview" monthlyCents={price} isOwner={owner} /> | |
| 106 | <div> | |
| 107 | <SeverityCountsGrid counts={total} /> | |
| 108 | <p className="mt-2 text-xs text-faint"> | |
| 109 | Open alerts by severity. A secret in the history that looks real counts as critical; blocked pushes and likely test | |
| 110 | values do not. | |
| 111 | </p> | |
| 112 | </div> | |
| 113 | {projects.length === 0 ? ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 114 | <Card asChild tone="plain" className="border-dashed px-4 py-6 text-sm text-muted"> |
| 115 | <p> | |
| 116 | No project has been scanned yet. Each one is scanned on its next push to its default branch, or when its Security | |
| 117 | page is first opened. | |
| 118 | </p> | |
| 119 | </Card> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 120 | ) : ( |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 121 | <Card asChild divided className="overflow-hidden"> |
| 122 | <ul> | |
| 123 | {projects.map((project) => ( | |
| 124 | <li key={project.repoId}> | |
| 125 | <Link | |
| 126 | to={`/${params.owner}/${project.name}/security`} | |
| 127 | className="group flex flex-col gap-2 px-4 py-3 transition-colors hover:bg-raised/50 sm:flex-row sm:items-center" | |
| 128 | > | |
| 129 | <span className="min-w-0 grow"> | |
| 130 | <span className="flex flex-wrap items-center gap-2"> | |
| 131 | <span className="font-mono text-sm font-medium">{project.name}</span> | |
| 132 | {!project.upkeep && <Badge>security updates off</Badge>} | |
| 133 | </span> | |
| 134 | <span className="mt-0.5 block text-xs text-faint"> | |
| 135 | {project.secrets} {project.secrets === 1 ? "secret" : "secrets"} · {project.vulnerabilities}{" "} | |
| 136 | {project.vulnerabilities === 1 ? "vulnerability" : "vulnerabilities"} | |
| 137 | {project.dependenciesScannedAt && ( | |
| 138 | <> | |
| 139 | {" "} | |
| 140 | · read <TimeAgo at={project.dependenciesScannedAt} /> | |
| 141 | </> | |
| 142 | )} | |
| 143 | </span> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 144 | </span> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 145 | <SeverityCountsInline counts={project.counts} /> |
| 146 | <ChevronRight size={15} className="hidden shrink-0 text-faint group-hover:text-fg sm:block" /> | |
| 147 | </Link> | |
| 148 | </li> | |
| 149 | ))} | |
| 150 | </ul> | |
| 151 | </Card> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 152 | )} |
| 153 | </> | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 154 | )} |
| 155 | </div> | |
| 156 | ); | |
| 157 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.