Skip to content

[Bug]: Orb relay forward picks an arbitrary enrollment when multiple enrolled rows exist #1783

Description

@philluiz2323

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

  1. Enroll installation 800 via issueOrbEnrollment (row A).
  2. Enroll the same installation again via issueOrbEnrollment (row B — row A remains enrolled).
  3. Register push relay on row B's secret: registerOrbRelay(secretB, "https://new-host.example/v1/orb/relay").
  4. Forward a pull_request event for installation 800.
  5. Expected: POST to https://new-host.example/v1/orb/relay signed with secret B.
  6. 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

  • I removed secrets, tokens, wallet details, private keys, local paths, and private scoring output.

Metadata

Metadata

Assignees

No one assigned

    Labels

    slopAI slop and/or attempts to game additional points via manipulation or alt profiles.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions