Summary
forwardOrbEvent in src/orb/relay.ts loads the brokered self-host relay target with a .first() query on orb_enrollments that has no ORDER BY. issueOrbEnrollment inserts a new enrolled row on every enrollment without revoking prior rows for the same installation_id. After a maintainer re-enrolls and registers a new push relay URL, forwardOrbEvent may read an older enrollment row (stale relay URL or no relay registered) and skip or POST to the wrong container.
Area
GitHub App
Expected behavior
When multiple active enrollment rows exist for one installation, relay forwarding should use the most recently registered enrollment (by relay_registered_at, then enrolled_at), so re-enrollment and relay registration always target the current self-host container.
Actual behavior
The enrollment lookup uses SELECT ... WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL with .first() and no ordering. D1/SQLite returns an arbitrary row when several match, so a stale enrollment without a relay URL can win and the event is skipped, or an old push URL receives the HMAC-signed payload intended for the new container.
Reproduction
- Enroll installation 800 via
issueOrbEnrollment (row A).
- Enroll the same installation again via
issueOrbEnrollment (row B — row A remains enrolled).
- Register push relay on row B's secret:
registerOrbRelay(secretB, "https://new-host.example/v1/orb/relay").
- Forward a
pull_request event for installation 800.
- Expected: POST to
https://new-host.example/v1/orb/relay signed with secret B.
- Actual: if row A is returned first and has no relay URL, result is
skipped; if row A has an old URL, event goes to the stale host.
Validation
Integration test in test/integration/orb-relay.test.ts: double-enroll same installation, register relay on the second secret only, assert forwardOrbEvent forwards to the new URL.
Public-safety check
Summary
forwardOrbEventinsrc/orb/relay.tsloads the brokered self-host relay target with a.first()query onorb_enrollmentsthat has noORDER BY.issueOrbEnrollmentinserts a newenrolledrow on every enrollment without revoking prior rows for the sameinstallation_id. After a maintainer re-enrolls and registers a new push relay URL,forwardOrbEventmay read an older enrollment row (stale relay URL or no relay registered) and skip or POST to the wrong container.Area
GitHub App
Expected behavior
When multiple active enrollment rows exist for one installation, relay forwarding should use the most recently registered enrollment (by
relay_registered_at, thenenrolled_at), so re-enrollment and relay registration always target the current self-host container.Actual behavior
The enrollment lookup uses
SELECT ... WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULLwith.first()and no ordering. D1/SQLite returns an arbitrary row when several match, so a stale enrollment without a relay URL can win and the event isskipped, or an old push URL receives the HMAC-signed payload intended for the new container.Reproduction
issueOrbEnrollment(row A).issueOrbEnrollment(row B — row A remainsenrolled).registerOrbRelay(secretB, "https://new-host.example/v1/orb/relay").pull_requestevent for installation 800.https://new-host.example/v1/orb/relaysigned with secret B.skipped; if row A has an old URL, event goes to the stale host.Validation
Integration test in
test/integration/orb-relay.test.ts: double-enroll same installation, register relay on the second secret only, assertforwardOrbEventforwards to the new URL.Public-safety check