Skip to content

fix(orb): point collector at the live receiver + rate-limit ingest + document the multi-tenant security model - #1254

Merged
JSONbored merged 1 commit into
mainfrom
feat/orb-go-live
Jun 24, 2026
Merged

fix(orb): point collector at the live receiver + rate-limit ingest + document the multi-tenant security model#1254
JSONbored merged 1 commit into
mainfrom
feat/orb-go-live

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Makes Orb export actually work end-to-end and hardens/documents it for the multi-tenant self-host case (this image is run by many independent maintainers).

1. Export was broken. The default ORB_COLLECTOR_URL was https://orb.gittensory.app/v1/ingest, which does not resolve (verified: HTTP 000) — and the path didn't match the receiver route. Every export silently failed. Pointed it at the deployed receiver https://gittensory-api.aethereal.dev/v1/orb/ingest (verified responding live: HTTP 400 to bad input, the receiver from #1234).

2. Hardened the open ingest endpoint. /v1/orb/ingest is unauthenticated + write, accepting anonymized batches from untrusted instances. Classified it as the strict rate-limit class (10/min per IP — the limiter keys unauth requests by hashed client IP). Legitimate instances export hourly, so this is generous for them and caps abuse.

3. Documented the multi-tenant security model in .env.example.

The security model (why no secret is exposed)

This is the key design point for "self-hosted by other maintainers":

  • The image bakes zero secrets (the Dockerfile injects nothing at build).
  • Each operator creates their OWN Orb App via /orb/setup. The resulting ORB_* secrets are unique to that instance and live only in its /data — never shared, never sent to the collector. gittensory's own App secrets are never in the image.
  • Export carries NO shared key. repo/PR identifiers are HMAC-anonymized with each operator's own ORB_WEBHOOK_SECRET (so even the collector operator can't de-anonymize them), and the collector accepts the batch as untrusted, rate-limited, aggregate-only telemetry. Nothing in the container, if leaked, can compromise the collector, other operators, or the main App.
  • Recommends the ORB_PRIVATE_KEY_FILE convention (mount the key as a file rather than an inline env value).

This mirrors the secret-hygiene principle in entrius/das-github-mirror (each service holds its own App secrets at runtime; consumers use per-consumer issued keys, never a shared secret) — adapted to Orb's push-telemetry direction.

Validation

  • Verified the live collector responds (gittensory-api.aethereal.dev/v1/orb/ingest → 400 on bad input) and the old default is dead (orb.gittensory.app → 000)
  • npm run typecheck clean; selfhost-orb-collector + auth tests pass (42)
  • Added a regression guard test pinning the default collector URL (so it can't silently regress to a dead domain) and a rate-limit-class assertion for /v1/orb/ingest
  • Patch coverage on changed lines covered (orb-collector default URL + the new strict-class branch)

Follow-up (not in this PR)

Per-instance issued API keys (a registration flow → the collector can authenticate + revoke a specific instance) would add abuse control beyond per-IP rate limiting — the pattern das-github-mirror uses for its read API (API_KEYS). Worth doing if poisoning of the aggregate calibration data becomes a concern; v1 relies on anonymization + per-IP limits + dedup + outlier-robust aggregation.

…ngest endpoint

The default ORB_COLLECTOR_URL pointed at https://orb.gittensory.app/v1/ingest, which does not
resolve (DNS HTTP 000) — so every Orb export silently failed. Point it at the deployed receiver
https://gittensory-api.aethereal.dev/v1/orb/ingest (verified responding live). Classify the open,
unauthenticated /v1/orb/ingest endpoint as the `strict` rate-limit class (10/min per IP), since it
accepts anonymized batches from untrusted self-host instances; legitimate instances export hourly.

Document the multi-tenant security model in .env.example, since this image is self-hosted by many
independent maintainers: the image bakes no secrets, each operator creates their OWN Orb App via
/orb/setup (per-instance secrets, local only), and export carries NO shared key — repo/PR identifiers
are HMAC-anonymized with the operator's own ORB_WEBHOOK_SECRET, and the collector treats the batch as
untrusted, rate-limited, aggregate-only telemetry. Also recommend the ORB_PRIVATE_KEY_FILE convention.
@dosubot dosubot Bot added the size:S label Jun 24, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.97%. Comparing base (5655113) to head (e3fe458).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1254   +/-   ##
=======================================
  Coverage   94.97%   94.97%           
=======================================
  Files         177      177           
  Lines       19826    19827    +1     
  Branches     7132     7133    +1     
=======================================
+ Hits        18830    18831    +1     
  Misses        397      397           
  Partials      599      599           
Files with missing lines Coverage Δ
src/auth/rate-limit.ts 98.75% <100.00%> (+0.01%) ⬆️
src/selfhost/orb-collector.ts 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored JSONbored self-assigned this Jun 24, 2026
@JSONbored
JSONbored merged commit 66027b4 into main Jun 24, 2026
20 checks passed
@JSONbored
JSONbored deleted the feat/orb-go-live branch June 24, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

selfhost(telemetry): Gittensory Orb — outcome signal sidecar + calibration loop

1 participant