You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
orb(security): the 'registered instance' trust gate on the published guarantee authenticates nothing — instance_id is body-supplied, and an absent arm remotely DELETEs the live guarantee #9121
The "registered instance" trust gate protecting the published ≥95% close guarantee authenticates
nothing. The instance identity it checks is a plain field of the caller's own request body.
handleOrbIngest (src/orb/ingest.ts) destructures the identity from the payload:
121: const{ instance_id, events, health }=payloadasOrbIngestPayload;
and then uses that attacker-supplied string as the entire trust anchor for the risk-control write:
211: // #8835: ... TRUST GATE: stored only when the sender is a REGISTERED instance — a published accuracy212: // guarantee is the strongest claim on the homepage, and open ingest must not let a stranger plant one.218: constregisteredRow=awaitdb.prepare("SELECT registered FROM orb_instances WHERE instance_id = ?").bind(instance_id).first...
219: if(registeredRow?.registered===1){
Nothing binds the presented credential to the claimed instance_id:
isAuthorizedIngest (src/api/routes.ts ~6626) compares a single fleet-wideORB_INGEST_TOKEN,
which every exporter in the fleet holds as ORB_COLLECTOR_TOKEN. It proves "some fleet member", never
"this instance".
The sender's x-orb-signature is not verified by handleOrbIngest — it cannot be, since the
collector does not hold the sender's anon secret (src/selfhost/orb-collector.ts ~226-229).
The id is not secret either: instanceId() is sha256(ORB_APP_ID).slice(0,16) over a small integer
(orb-collector.ts:108-109), brute-forceable offline, and it is sent in clear as x-orb-instance.
So any holder of the shared collector token — any fleet member, or anyone who extracts it from any
self-host deployment — can present a registered instance's id and write the guarantee.
The retraction path is worse. The else arm fires whenever an arm is merely absent from the
payload:
227: }else{228: // The sender no longer publishes this arm — retract the fleet copy too (stale guarantees lie).229: awaitdb.prepare(`DELETE FROM system_flags WHERE key = ?`).bind(`riskcontrol:fleet:${arm}`).run();
A payload carrying risk_control: {} therefore deletesriskcontrol:fleet:close and riskcontrol:fleet:merge. Both keys are global singletons, and loadFleetGuarantee
(src/review/public-stats.ts ~456-460) reads them straight onto /v1/public/stats — an endpoint
confirmed publicly reachable (HTTP 200 on api.loopover.ai, 2026-07-26).
The strongest public claim the project makes is writable, and deletable, by any fleet peer rather than
only by the instance that earned it. This is the same claim an external evaluator is being pointed at.
Requirements
Derive the writer's identity from the credential, not the body: per-instance enrolment secret or
per-instance token → instance_id. A body-supplied id may be accepted only when it matches the
credential's own identity; a mismatch is a 403, not a silent accept.
Scope the flag per instance (riskcontrol:fleet:<instance>:<arm>) and aggregate at read time, so one
compromised or buggy peer cannot overwrite the fleet's single global cell.
Require an explicit retraction (e.g. {"close": null}) rather than inferring retraction from an
absent key, so a truncated, partial, or older-schema payload cannot silently delete a live guarantee.
99%+ patch coverage, branch-counted. Both arms of the identity check (credential matches / mismatches
the claimed id), the explicit-retraction arm, and the absent-key arm (must now be a no-op, asserted
by a regression test naming this issue).
Distinct from #9068, which covers last-writer-wins semantics and missing validation on this key.
Even with #9068 fully fixed, registered gates nothing: the id is unauthenticated and enumerable, and
the DELETE-retraction path stays remotely reachable. #9068 is "the value is unvalidated"; this is "the
writer is unauthenticated".
Boundaries
Ingest authentication + flag scoping + retraction semantics. No change to how risk-control calibrates
locally, and no change to the guarantee's statistics.
maintainer-only — public trust-surface and fleet-credential authority.
Problem
The "registered instance" trust gate protecting the published ≥95% close guarantee authenticates
nothing. The instance identity it checks is a plain field of the caller's own request body.
handleOrbIngest(src/orb/ingest.ts) destructures the identity from the payload:and then uses that attacker-supplied string as the entire trust anchor for the risk-control write:
Nothing binds the presented credential to the claimed
instance_id:isAuthorizedIngest(src/api/routes.ts~6626) compares a single fleet-wideORB_INGEST_TOKEN,which every exporter in the fleet holds as
ORB_COLLECTOR_TOKEN. It proves "some fleet member", never"this instance".
x-orb-signatureis not verified byhandleOrbIngest— it cannot be, since thecollector does not hold the sender's anon secret (
src/selfhost/orb-collector.ts~226-229).instanceId()issha256(ORB_APP_ID).slice(0,16)over a small integer(
orb-collector.ts:108-109), brute-forceable offline, and it is sent in clear asx-orb-instance.So any holder of the shared collector token — any fleet member, or anyone who extracts it from any
self-host deployment — can present a registered instance's id and write the guarantee.
The retraction path is worse. The
elsearm fires whenever an arm is merely absent from thepayload:
A payload carrying
risk_control: {}therefore deletesriskcontrol:fleet:closeandriskcontrol:fleet:merge. Both keys are global singletons, andloadFleetGuarantee(
src/review/public-stats.ts~456-460) reads them straight onto/v1/public/stats— an endpointconfirmed publicly reachable (HTTP 200 on
api.loopover.ai, 2026-07-26).Trigger
→ homepage guarantee becomes attacker-chosen. Send
"risk_control": {}instead → guarantee retracted.Impact
The strongest public claim the project makes is writable, and deletable, by any fleet peer rather than
only by the instance that earned it. This is the same claim an external evaluator is being pointed at.
Requirements
per-instance token →
instance_id. A body-supplied id may be accepted only when it matches thecredential's own identity; a mismatch is a 403, not a silent accept.
riskcontrol:fleet:<instance>:<arm>) and aggregate at read time, so onecompromised or buggy peer cannot overwrite the fleet's single global cell.
{"close": null}) rather than inferring retraction from anabsent key, so a truncated, partial, or older-schema payload cannot silently delete a live guarantee.
validation half; this issue owns the authentication half).
Test Coverage Requirements
99%+ patch coverage, branch-counted. Both arms of the identity check (credential matches / mismatches
the claimed id), the explicit-retraction arm, and the absent-key arm (must now be a no-op, asserted
by a regression test naming this issue).
Links & Resources
src/orb/ingest.ts~121, ~211-236;src/api/routes.ts~6626 (isAuthorizedIngest);src/selfhost/orb-collector.ts~108-111, ~226-229;src/review/public-stats.ts~456-460fix makes the token required, but it is still fleet-wide and so still not an identity)
Dedup
Distinct from #9068, which covers last-writer-wins semantics and missing validation on this key.
Even with #9068 fully fixed,
registeredgates nothing: the id is unauthenticated and enumerable, andthe DELETE-retraction path stays remotely reachable. #9068 is "the value is unvalidated"; this is "the
writer is unauthenticated".
Boundaries
Ingest authentication + flag scoping + retraction semantics. No change to how risk-control calibrates
locally, and no change to the guarantee's statistics.
maintainer-only — public trust-surface and fleet-credential authority.