Skip to content

orb(security): ingest endpoints fail OPEN when their token is unset, and /metrics is unauthenticated #9046

Description

@JSONbored

Two lower-severity auth gaps, both currently shielded by network isolation (the tunnel exposes only ^/loopover/shot.*$; verified /metrics → 404 from the internet, 8787 bound to 127.0.0.1). Worth closing before the hosted Orb removes that shield.

1. Ingest fails open. src/api/routes.ts ~6601-6614:

async function isAuthorizedOrbIngest(env, token) {
  if (!env.ORB_INGEST_TOKEN) return true;   // unset ⇒ open ingress
  return timingSafeEqual(token, env.ORB_INGEST_TOKEN);
}

With the token unset (the shipped default) anyone with network access can POST fleet-calibration batches — which feed the published accuracy numbers. Real mitigations exist (strict rate class on /v1/orb/ingest, body ceiling, UNIQUE(instance_id, repo_hash, pr_hash) dedup, only registered instances counted). Note /v1/ams/ingest is not in the strict rate list (src/auth/rate-limit.ts ~114-159) so it lands in normal. Flip both to fail-closed while the fleet is small.

2. /metrics is unauthenticated and pre-router. Served at src/server.ts ~964-967 before the Hono app, so no auth and no rate limiting. setSelfHostedMetricsMode(true) (~355) deliberately stops redacting the repo label. Verified live: curl localhost:8787/metrics200, 27KB, containing repo="JSONbored/loopover" and repo="JSONbored/awesome-claude" — though third-party repos DO appear as repo="redacted-1"/"redacted-2", so the redaction is partial rather than absent (better than a pure code read suggests).

Secondary: renderMetrics() awaits every gauge callback per scrape (src/selfhost/metrics.ts ~325-347), ~10 of which are live DB queries — an unauthenticated DoS primitive if ever exposed.

Fix

  1. Make both ingest endpoints fail closed when the token is unset; add /v1/ams/ingest to the strict rate class.
  2. Bind /health|/ready|/metrics to a separate admin port/interface, or require a bearer for /metrics; add a deny matcher for /metrics to any reverse-proxy config shipped for self-hosters.
  3. Cache the metrics render for a few seconds.

Also filed from the same audit: draft OAuth redirect_uri derives from the request Host header (src/services/draft.ts ~613-619) instead of PUBLIC_API_ORIGIN — the setup wizard explicitly refuses to do this and documents why (src/server.ts ~996-1005). Flag-gated behind LOOPOVER_REVIEW_DRAFT and bounded by GitHub validating the registered callback, but it should match the wizard.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.orbGittensory Orb related - maintainer self-hosting analytics.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions