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

Commit

otel.g1t.sh no longer makes a project's store for a token that names no project, and counts OTLP requests per address before reading them. A token is g1to_<project id>_<secret>, and the endpoint took any id it was given: the store for that name was woken (and its tables made) before the token was checked against it, so anyone could make stores without end with made-up tokens. Now only a project id as g1t makes them (prj_ and 26 Crockford base32 characters) reaches a store. And TELEMETRY_INGEST_LIMIT, a Workers Rate Limiting binding listed in RATE_LIMITS like the others, allows 600 OTLP requests a minute per client address, checked before the body is read; past it the answer is 429. It fails open, as every limit does. The rate limits page lists it, and says sign-ups with a password are limited to 5 an hour per network.

syntaqxcommitted Parent1dd53cbBrowse files
6 files+33−30/6 viewed
+4−0
2424 | Pages on g1t.sh, signed out | Client IP address | 600 |
2525 | Archive downloads, workflow run pages, log downloads and search, signed out | Client IP address | 30 |
2626 | [Raw files](/guides/git/#raw-files) on g1tusercontent.com | Client IP address, together with pages signed out | 600 |
27+| [OpenTelemetry ingest](/guides/observability/) at otel.g1t.sh | Client IP address | 600 |
2728 | Container and package registries | See [storage and pull limits](/guides/containers/#storage-and-pull-limits) | |
2829
2930 The REST API and the MCP server count apart: calls to one do not use up
3233 request from one IP address, signed in or not, also counts toward 3,000 a
3334 minute.
3435
36+Signing up with a password is limited to 5 accounts an hour from one
37+network; see [too many attempts](/guides/authentication/#too-many-attempts).
38+
3539 A running job's live log, which its run's page asks for about once a
3640 second, counts as a page, not as a log download.
3741 A signed archive link, the address
+4−1
4444 * a person browsing makes a few requests a second at most, a clone is three
4545 * git requests, and nothing a person does needs dozens of archives a minute.
4646 * Namespace ids go in blocks of a hundred per Worker: 41xx packages,
47− * 42xx web, 43xx repos, 44xx api, 45xx og, 46xx status.
47+ * 42xx web, 43xx repos, 44xx api, 45xx og, 46xx status, 47xx telemetry.
4848 */
4949 export const RATE_LIMITS = {
5050 // services/packages (src/limits.rs): registry pulls and token requests.
7070 // apps/status: asking for a subscription, which sends an email.
7171 STATUS_SUBSCRIBE_LIMIT: { worker: "apps/status", namespaceId: 4601, limit: 3, per: "address" },
7272 STATUS_EMAIL_LIMIT: { worker: "apps/status", namespaceId: 4602, limit: 2, per: "email address" },
73+ // services/telemetry (src/index.ts): OTLP requests to otel.g1t.sh, counted
74+ // before the body is read or a project's store is woken.
75+ TELEMETRY_INGEST_LIMIT: { worker: "services/telemetry", namespaceId: 4701, limit: 600, per: "address, OTLP requests" },
7376 } as const satisfies Record<string, RateLimitSpec>;
7477
7578 export type RateLimitName = keyof typeof RATE_LIMITS;
+8−0
2222 * called once they have checked the viewer may read (or manage) the
2323 * project; and `GET /live?project_id=…`, a project's live tail.
2424 */
25+import { type RateLimitBinding, clientAddress, isLimited } from "@g1t/contracts/rate-limits";
2526 import { TELEMETRY_TOKEN_PREFIX, type ErrorGroup } from "@g1t/contracts/telemetry";
2627
2728 import { emptyBatch, type Batch } from "./batch.ts";
3839 DOMAINS?: KVNamespace;
3940 /** The secret Cloudflare's OpenTelemetry export sends, as `x-g1t-export-key`. */
4041 CLOUDFLARE_EXPORT_KEY?: string;
42+ /** OTLP requests per client address (RATE_LIMITS in packages/contracts). Fails open without it. */
43+ TELEMETRY_INGEST_LIMIT?: RateLimitBinding;
4144 };
4245
4346 const DEPLOYMENTS_DOMAIN = "g1t.page";
135138 if (!token) return refused(401, `No token: send the project's ingest token as "Authorization: Bearer ${TELEMETRY_TOKEN_PREFIX}…". Make one under the project's Observability settings.`);
136139 const project = projectOfToken(token);
137140 if (!project) return refused(401, "That is not a g1t ingest token.");
141+ // Counted before the body is read or the project's store is woken, so a
142+ // flood of requests (with made-up tokens or real ones) costs little.
143+ if (await isLimited(env.TELEMETRY_INGEST_LIMIT, `ip:${clientAddress(request)}`)) {
144+ return refused(429, "Too many requests from this address. Batch more per request, and wait a minute: https://docs.g1t.sh/reference/rate-limits/");
145+ }
138146 let read: { batch: Batch; protobuf: boolean };
139147 try {
140148 read = await readOtlp(request, signal, OTLP_DOOR);
+8−1
3434 return bearer ?? request.headers.get("x-g1t-token");
3535 }
3636
37+/**
38+ * A project id as g1t makes them (`newId("prj")`): `prj_` and a UUIDv7 in
39+ * lowercase Crockford base32. Anything else names no project, so it never
40+ * reaches (or makes) a store.
41+ */
42+const PROJECT_ID = /^prj_[0-9abcdefghjkmnpqrstvwxyz]{26}$/;
43+
3744 /** The project a token is for: tokens are `g1to_<project id>_<secret>`. */
3845 export function projectOfToken(token: string): string | null {
3946 if (!token.startsWith(TELEMETRY_TOKEN_PREFIX)) return null;
4148 const at = rest.lastIndexOf("_");
4249 if (at <= 0 || at === rest.length - 1) return null;
4350 const project = rest.slice(0, at);
44− return /^[A-Za-z0-9_-]{1,64}$/.test(project) ? project : null;
51+ return PROJECT_ID.test(project) ? project : null;
4552 }
4653
4754 async function readBody(request: Request): Promise<Uint8Array> {
+5−1
128128 });
129129
130130 test("a token names its project", () => {
131− assert.equal(projectOfToken("g1to_prj_01J9ZK3_abcdef0123"), "prj_01J9ZK3");
131+ assert.equal(projectOfToken("g1to_prj_01jb2k7x9hfq0b3zj0f5s2m8ra_abcdef0123"), "prj_01jb2k7x9hfq0b3zj0f5s2m8ra");
132+ // Only a project id as g1t makes them: anything else never reaches a store.
133+ assert.equal(projectOfToken("g1to_prj_01J9ZK3_abcdef0123"), null);
134+ assert.equal(projectOfToken("g1to_anything-at-all_abcdef0123"), null);
135+ assert.equal(projectOfToken("g1to_prj_01jb2k7x9hfq0b3zj0f5s2m8rax_abcdef0123"), null);
132136 assert.equal(projectOfToken("g1t_prj_x_y"), null);
133137 assert.equal(projectOfToken("g1to_nounderscore"), null);
134138 });
+4−0
2424 // Secret, optional: CLOUDFLARE_EXPORT_KEY, which a Workers
2525 // Observability destination on the account sends as x-g1t-export-key
2626 // to https://otel.g1t.sh/cloudflare/v1/{traces,logs}.
27+ // OTLP requests per client address, counted before the body is read or a
28+ // project's store is woken (src/index.ts). Id and limit: RATE_LIMITS in
29+ // packages/contracts.
30+ "ratelimits": [{ "name": "TELEMETRY_INGEST_LIMIT", "namespace_id": "4701", "simple": { "limit": 600, "period": 60 } }],
2731 // Its own logs: few, as every request to an app is a tail event here.
2832 "observability": { "enabled": true, "head_sampling_rate": 0.05 }
2933 }

Comments

Sign in to comment.