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/metrics → 200, 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
- Make both ingest endpoints fail closed when the token is unset; add
/v1/ams/ingest to the strict rate class.
- 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.
- 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.
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: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/ingestis not in the strict rate list (src/auth/rate-limit.ts~114-159) so it lands innormal. Flip both to fail-closed while the fleet is small.2.
/metricsis unauthenticated and pre-router. Served atsrc/server.ts~964-967 before the Hono app, so no auth and no rate limiting.setSelfHostedMetricsMode(true)(~355) deliberately stops redacting therepolabel. Verified live:curl localhost:8787/metrics→ 200, 27KB, containingrepo="JSONbored/loopover"andrepo="JSONbored/awesome-claude"— though third-party repos DO appear asrepo="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
/v1/ams/ingestto the strict rate class./health|/ready|/metricsto a separate admin port/interface, or require a bearer for/metrics; add a deny matcher for/metricsto any reverse-proxy config shipped for self-hosters.Also filed from the same audit: draft OAuth
redirect_uriderives from the request Host header (src/services/draft.ts~613-619) instead ofPUBLIC_API_ORIGIN— the setup wizard explicitly refuses to do this and documents why (src/server.ts~996-1005). Flag-gated behindLOOPOVER_REVIEW_DRAFTand bounded by GitHub validating the registered callback, but it should match the wizard.