Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
66 linesCodeBlameRaw

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.

Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address1/**
2 * The headers every answer from the site carries, and the policy its pages
3 * run under. No Workers imports, so it can be tested under Node.
4 *
5 * - Nothing is sniffed: a download or a data request is only the type it says.
6 * - A link to another site sends the origin, never the path.
7 * - No other site may put g1t's pages in a frame.
8 * - A page runs only the scripts the site served it: its own files, and the
9 * inline scripts React and React Router write, each carrying the page's
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today10 * nonce, plus Cloudflare's and HeyCatch's analytics scripts. Styles may
11 * be inline (highlighting and layout set them); images may come from any
12 * HTTPS address (pictures in a README); requests may go to any HTTPS
13 * address (the status page's summary, analytics events).
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address14 */
15
16/** A fresh nonce for one page: 128 random bits, base64. */
17export function makeNonce(): string {
18 const bytes = crypto.getRandomValues(new Uint8Array(16));
19 return btoa(String.fromCharCode(...bytes));
20}
21
22/**
23 * The Content-Security-Policy of a page rendered with `nonce`. `usercontent`
24 * is where repository files and avatars are served (lib/usercontent.ts),
25 * named for an installation that serves them over plain HTTP.
26 */
27export function pagePolicy(nonce: string, usercontent?: string): string {
28 const files = usercontent && /^http:/.test(usercontent) ? ` ${new URL(usercontent).origin}` : "";
29 return [
30 "default-src 'self'",
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.31 // Cloudflare's Web Analytics beacon, when the zone turns it on,
32 // HeyCatch's helper for product analytics (lib/analytics.client.ts), and
33 // Cloudflare Turnstile on sign-up when it is on (lib/turnstile.ts).
34 `script-src 'self' 'nonce-${nonce}' https://static.cloudflareinsights.com https://in.heycatch.ai https://challenges.cloudflare.com`,
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address35 "style-src 'self' 'unsafe-inline'",
36 `img-src 'self' https: data: blob:${files}`,
37 `media-src 'self' https:${files}`,
38 "font-src 'self' data:",
preparing for cloud work39 // `ipc:` and ipc.localhost are the desktop app's own channel to its
40 // shell, which the page calls through window.g1tDesktop
41 // (lib/notify-client.ts); no browser resolves either.
42 "connect-src 'self' https: ipc: http://ipc.localhost",
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.43 // Turnstile's widget draws in a frame of its own; nothing else is framed.
44 "frame-src https://challenges.cloudflare.com",
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address45 "object-src 'none'",
46 "base-uri 'self'",
47 "frame-ancestors 'none'",
48 ].join("; ");
49}
50
51/**
52 * The answer with the site's headers added. A header the answer already
53 * has is kept. An upgrade to a WebSocket is passed on as it is.
54 */
55export function withSiteHeaders(response: Response): Response {
56 if (response.status === 101 || (response as Response & { webSocket?: unknown }).webSocket) return response;
57 // A redirect's headers cannot be changed, so the answer is copied.
58 const answer = new Response(response.body, response);
59 const headers = answer.headers;
60 if (!headers.has("x-content-type-options")) headers.set("x-content-type-options", "nosniff");
61 if (!headers.has("referrer-policy")) headers.set("referrer-policy", "strict-origin-when-cross-origin");
62 if (/^text\/html\b/i.test(headers.get("content-type") ?? "") && !headers.has("x-frame-options")) {
63 headers.set("x-frame-options", "DENY");
64 }
65 return answer;
66}

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