Skip to content
127 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 branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails1/**
2 * The front door's rate limits (workers/app.ts), per request before it is
3 * answered. The bindings and their limits are in `RATE_LIMITS`
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.4 * (packages/contracts/src/rate-limits.ts); CONTRIBUTING.md, "Rate limits",
5 * says how they are kept.
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails6 *
7 * - Git over HTTPS: without credentials, by address; with them, by a hash
8 * of the credential, much higher. A clone is about three requests.
9 * - Pages: every request that reaches the Worker counts against a ceiling
10 * per address. Signed out, by address, with a tighter limit on what is
11 * costly to answer (archives, run pages, logs, search). Signed in, by a
12 * hash of the session cookie, higher: the session is not checked here,
13 * which would cost a call to identity, and the ceiling per address keeps
Merge main into Artifacts Phase 214 * made-up cookies from getting round the signed-out limit. With an
15 * access token (`Authorization: Bearer`, app/lib/website-token.ts), by a
16 * hash of the token, at the API's limit for a token; the address ceiling
17 * applies as for cookies.
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails18 *
19 * Static assets never reach the Worker (the assets binding answers them),
20 * and the few files it serves itself are left out here too. Every limit
21 * fails open.
22 */
23import {
24 type RateLimitBinding,
25 checkLimit,
26 clientAddress,
27 secretKey,
28 tooManyRequests,
29} from "@g1t/contracts/rate-limits";
30
31export type FrontDoorLimits = {
32 WEB_ANONYMOUS_LIMIT?: RateLimitBinding;
33 WEB_HEAVY_LIMIT?: RateLimitBinding;
34 WEB_SESSION_LIMIT?: RateLimitBinding;
35 WEB_ADDRESS_LIMIT?: RateLimitBinding;
Merge main into Artifacts Phase 236 WEB_TOKEN_LIMIT?: RateLimitBinding;
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails37 GIT_ANONYMOUS_LIMIT?: RateLimitBinding;
38 GIT_SIGNED_LIMIT?: RateLimitBinding;
39};
40
41/** Files the Worker serves that are never limited: build output, fonts, and top-level files such as robots.txt. */
42const UNLIMITED = /^\/(?:assets\/|fonts\/|favicon|[^/]+\.(?:ico|png|svg|txt|xml|webmanifest)$)/;
43
A repository's Security overview is one row per feature in three groups, each with a status word (Enabled, Disabled, Needs setup, Coming), a line on what it does and the one thing to do about it: the security policy (View policy, or Set up a security policy, which commits a starting SECURITY.md on a branch and opens it as a pull request), dependency alerts, security updates (the switch, kept on Vulnerabilities and now in Settings too), code scanning (Enabled once an analysis was uploaded), secret scanning (push protection always on) and code quality as Coming; and under Reporting, advisories and the private vulnerability reporting switch. Anyone who can see a public repository but has no role on it sees the public face at the same address: the policy, the published advisories and, when it is on, Report a vulnerability. Advisories get their pages (the list with its state filter, one advisory with Publish, Withdraw and Edit, a form for a new draft or changes) and reports theirs (new ones first, each accepted into a draft advisory or declined), with a Reports page beside Advisories and an Advisories tab; the public report form takes an email when signed out and counts against the front door's heavy limit. Security settings gain Alerts for secrets in the history and Private vulnerability reporting. Settings → Notifications gains a Security section: critical and high alerts at once (always, for the workspaces one owns or manages security for) and the rest in a daily or weekly digest, at once, or not at all, with security alerts now among the email reasons. The notifications guide says how security alerts arrive and what the digest is, the security guide the overview's rows and the public face, secret protection the history alerts switch, webhooks the summary and advisory events, and a new guide covers advisories and private reporting; the report_settings method lets the public page know whether a repository takes reports.44/**
45 * What is costly to answer for a signed-out visitor, or must not be flooded:
46 * a repository's archives, a run's page, logs and artifacts, search, and
47 * the form that reports a vulnerability to a repository's maintainers
48 * privately, which takes no account.
49 */
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails50const HEAVY =
A repository's Security overview is one row per feature in three groups, each with a status word (Enabled, Disabled, Needs setup, Coming), a line on what it does and the one thing to do about it: the security policy (View policy, or Set up a security policy, which commits a starting SECURITY.md on a branch and opens it as a pull request), dependency alerts, security updates (the switch, kept on Vulnerabilities and now in Settings too), code scanning (Enabled once an analysis was uploaded), secret scanning (push protection always on) and code quality as Coming; and under Reporting, advisories and the private vulnerability reporting switch. Anyone who can see a public repository but has no role on it sees the public face at the same address: the policy, the published advisories and, when it is on, Report a vulnerability. Advisories get their pages (the list with its state filter, one advisory with Publish, Withdraw and Edit, a form for a new draft or changes) and reports theirs (new ones first, each accepted into a draft advisory or declined), with a Reports page beside Advisories and an Advisories tab; the public report form takes an email when signed out and counts against the front door's heavy limit. Security settings gain Alerts for secrets in the history and Private vulnerability reporting. Settings → Notifications gains a Security section: critical and high alerts at once (always, for the workspaces one owns or manages security for) and the rest in a daily or weekly digest, at once, or not at all, with security alerts now among the email reasons. The notifications guide says how security alerts arrive and what the digest is, the security guide the overview's rows and the public face, secret protection the history alerts switch, webhooks the summary and advisory events, and a new guide covers advisories and private reporting; the report_settings method lets the public page know whether a repository takes reports.51 /^\/(?:search(?:\.data)?$|[^/]+\/[^/]+\/(?:archive\/|actions\/runs\/[^/]+(?:\.data|\/logs\.zip|\/artifacts\/[^/]+)?$|actions\/jobs\/[^/]+\/log|security\/report(?:\.data)?$))/;
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails52
53export function unlimited(pathname: string): boolean {
54 return UNLIMITED.test(pathname);
55}
56
57export function heavy(pathname: string): boolean {
58 return HEAVY.test(pathname);
59}
60
61/** The session cookie's value, or null when signed out. */
62export function sessionCookie(cookie: string | null): string | null {
63 const match = /(?:^|;\s*)g1t_session=([^;]+)/.exec(cookie ?? "");
64 return match?.[1] ?? null;
65}
66
67const GIT_MESSAGE_ANONYMOUS =
68 "Too many git requests from your network. Wait a minute and try again, or use credentials for a higher limit: https://docs.g1t.sh/reference/rate-limits/\n";
69const GIT_MESSAGE_SIGNED = "Too many git requests with these credentials. Wait a minute and try again: https://docs.g1t.sh/reference/rate-limits/\n";
70const PAGE_MESSAGE = "Too many requests from your network. Wait a minute and try again.\n";
Merge main into Artifacts Phase 271const TOKEN_PAGE_MESSAGE = "Too many requests with this access token. Wait a minute and try again: https://docs.g1t.sh/reference/rate-limits/\n";
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails72
73/**
74 * The 429 for a git request past its limit, or null to go on. Git shows a
75 * plain-text answer's body to the person running it.
76 */
77export async function gitLimited(env: FrontDoorLimits, request: Request): Promise<Response | null> {
78 const credentials = request.headers.get("authorization");
79 if (credentials) {
80 const verdict = await checkLimit(env.GIT_SIGNED_LIMIT, await secretKey("git", credentials));
81 return verdict === "limited" ? tooManyRequests(GIT_MESSAGE_SIGNED) : null;
82 }
83 const verdict = await checkLimit(env.GIT_ANONYMOUS_LIMIT, `ip:${clientAddress(request)}`);
84 return verdict === "limited" ? tooManyRequests(GIT_MESSAGE_ANONYMOUS) : null;
85}
86
Merge main into Artifacts Phase 287/**
88 * The token in a page request's `Authorization: Bearer` header, or null
89 * (app/lib/website-token.ts). Not checked here either.
90 */
91export function websiteToken(authorization: string | null): string | null {
92 return /^\s*bearer\s+(\S+)\s*$/i.exec(authorization ?? "")?.[1] ?? null;
93}
94
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails95/** The 429 for a page or data request past its limit, or null to go on. */
96export async function pageLimited(env: FrontDoorLimits, request: Request, pathname: string): Promise<Response | null> {
97 if (unlimited(pathname)) return null;
98 const address = `ip:${clientAddress(request)}`;
Merge main into Artifacts Phase 299 const token = websiteToken(request.headers.get("authorization"));
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails100 const session = sessionCookie(request.headers.get("cookie"));
101 const checks: Promise<string>[] = [checkLimit(env.WEB_ADDRESS_LIMIT, address)];
Merge main into Artifacts Phase 2102 if (token) {
103 // A token on the website: per token, as the API counts it.
104 checks.push(secretKey("token", token).then((key) => checkLimit(env.WEB_TOKEN_LIMIT, key)));
105 } else if (session) {
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails106 checks.push(secretKey("session", session).then((key) => checkLimit(env.WEB_SESSION_LIMIT, key)));
107 } else {
108 checks.push(checkLimit(env.WEB_ANONYMOUS_LIMIT, address));
109 if (heavy(pathname)) checks.push(checkLimit(env.WEB_HEAVY_LIMIT, address));
110 }
111 const verdicts = await Promise.all(checks);
112 if (!verdicts.includes("limited")) return null;
Merge main into Artifacts Phase 2113 if (token && verdicts[1] === "limited") return tooManyRequests(TOKEN_PAGE_MESSAGE);
114 return tooManyRequests(token || session ? PAGE_MESSAGE : `${PAGE_MESSAGE.trimEnd()} Signed-in accounts have a higher limit.\n`);
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails115}
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 address116
117/**
118 * The 429 for a repository file on the usercontent origin past its limit,
119 * or null to go on. Nothing there is signed in (it never sees the session
120 * cookie), so a file counts as a signed-out page from its address. Avatars
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)121 * and custom emoji are answered from cache and are not limited.
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 address122 */
123export async function usercontentLimited(env: FrontDoorLimits, request: Request, path: string): Promise<Response | null> {
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)124 if (path.startsWith("/avatars/") || path.startsWith("/emoji/")) return null;
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 address125 const verdict = await checkLimit(env.WEB_ANONYMOUS_LIMIT, `ip:${clientAddress(request)}`);
126 return verdict === "limited" ? tooManyRequests(PAGE_MESSAGE) : null;
127}

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