-
-
Notifications
You must be signed in to change notification settings - Fork 88
fix(orb): authenticate fleet telemetry ingest #1285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2914,12 +2914,13 @@ export function createApp() { | |
| return c.json(result); | ||
| }); | ||
|
|
||
| // Gittensory Orb (#1255) — central fleet-calibration collector. Receives anonymized, reversal-aware | ||
| // outcome batches from self-hosted instances. No auth required: all data is HMAC-anonymized by the sender; | ||
| // dedup is enforced via UNIQUE(instance_id, repo_hash, pr_hash) in orb_signals. Rate-limited (strict, #1254). | ||
| // Gittensory Orb (#1255) — central fleet-calibration collector. Receives anonymized, reversal-aware outcome | ||
| // batches from self-hosted instances. Sender-side HMAC anonymization is for privacy, not authentication. | ||
| // OPTIONAL shared-token gate (#1285): unset ⇒ OPEN ingress (the live fleet keeps working, as before); set | ||
| // ⇒ the collector REQUIRES it, so an operator can lock the write path down after distributing the matching | ||
| // ORB_COLLECTOR_TOKEN to exporters. Bounded by a hard body ceiling, and dedup'd via UNIQUE(instance_id, repo_hash, pr_hash). | ||
| app.post("/v1/orb/ingest", async (c) => { | ||
| // Open ingress (no shared secret — the fleet topology has no per-instance key the collector could | ||
| // verify), bounded by a hard body ceiling so it can't be used to make us buffer unbounded input. | ||
| if (!isAuthorizedOrbIngest(c.env, extractBearerToken(c.req.header("authorization")))) return c.json({ error: "unauthorized" }, 401); | ||
| const body = await readOrbIngestBody(c.req.raw, c.req.header("content-length")); | ||
| if (body === null) return c.json({ error: "payload_too_large" }, 413); | ||
| if (!body) return c.json({ error: "invalid_request" }, 400); | ||
|
|
@@ -4945,6 +4946,16 @@ function toIsoQueryDate(value: string): string | undefined { | |
| return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : undefined; | ||
| } | ||
|
|
||
|
|
||
| // Optional Orb-ingest auth (#1285). FAIL-OPEN by default: with no ORB_INGEST_TOKEN configured the ingress stays | ||
| // OPEN (matching today's live fleet — deploying this is non-breaking). Once the operator sets the token, the | ||
| // collector REQUIRES an exact bearer match, so the write path can be locked down after the matching | ||
| // ORB_COLLECTOR_TOKEN is rolled out to exporters. | ||
| function isAuthorizedOrbIngest(env: Env, token: string | undefined): boolean { | ||
| if (!env.ORB_INGEST_TOKEN) return true; | ||
| return token === env.ORB_INGEST_TOKEN; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Bearer token comparison uses simple equality vulnerable to timing attacks The Use AI prompt |
||
| } | ||
|
|
||
| function requiresApiToken(path: string): boolean { | ||
| if (path === "/health") return false; | ||
| if (path === "/v1/mcp/compatibility") return false; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Authentication gate fails open when ORB_INGEST_TOKEN is unset
When ORB_INGEST_TOKEN is unset, the ingest endpoint remains unauthenticated, so the vulnerability is not fixed by default.
Make the auth gate fail-closed or log a loud startup warning when the token is missing.
AI prompt