Skip to content

fix: authorize org-scoped console event broadcasts#1818

Merged
riderx merged 12 commits intomainfrom
fix/security-events-org-broadcast-auth
Mar 20, 2026
Merged

fix: authorize org-scoped console event broadcasts#1818
riderx merged 12 commits intomainfrom
fix/security-events-org-broadcast-auth

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 17, 2026

Summary (AI generated)

  • authorize org-scoped /private/events console broadcasts before using body.user_id as a realtime target
  • reject forged notifyConsole requests for organizations the caller cannot access
  • add regression coverage for authorized and unauthorized org-scoped console events

Motivation (AI generated)

The oldest valid triage advisory reports that /private/events trusts a user-controlled org identifier for realtime broadcasts. That allows cross-organization event injection into the CLI feed unless the supplied org is authorized first.

Business Impact (AI generated)

This closes a high-severity integrity issue that could let one tenant inject fake deployment activity into another tenant's dashboard and poison org-scoped analytics, reducing incident noise and support risk.

Test Plan (AI generated)

  • bun lint supabase/functions/_backend/private/events.ts tests/events.test.ts
  • bun run supabase:with-env -- bun test tests/events.test.ts
  • bun run test:backend

Generated with AI

Summary by CodeRabbit

  • Bug Fixes
    • Enforced RBAC for console event notifications: requests that ask to notify the console without an organization ID are rejected, and attempts to access other organizations are denied with explicit error responses.
  • Tests
    • Added tests for console event broadcasting: successful broadcast for authorized orgs, rejection when org ID is missing, and rejection for unauthorized/foreign orgs.

@riderx riderx added the codex label Mar 17, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaced a local org-access helper with RBAC enforcement: when body.notifyConsole is true the handler now requires body.user_id and calls checkPermission(c, 'org.read', { orgId }); missing org ID or failed permission checks produce structured errors. Tests for authorized, missing, and foreign org cases were added.

Changes

Cohort / File(s) Summary
Backend: events handler
supabase/functions/_backend/private/events.ts
Removed the canAccessRequestedOrg helper and related imports; added checkPermission from ../utils/rbac.ts; when body.notifyConsole is true require body.user_id and call checkPermission(c, 'org.read', { orgId: body.user_id }); throw missing_org_id if absent and return error on denied permission.
Tests & utils
tests/events.test.ts, test-utils.ts
Exported ORG_ID from test-utils.ts; added three concurrent tests for [POST] /private/events covering: authorized console broadcast, missing user_id (missing_org_id), and foreign org rejection (cannot_access_organization).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Function
  participant RBAC as "RBAC / checkPermission"
  participant Broadcaster as "Console Broadcaster"

  Client->>Function: POST /private/events (body with notifyConsole, user_id, tags)
  alt notifyConsole true
    Function->>RBAC: checkPermission('org.read', {orgId: user_id})
    RBAC-->>Function: allow / deny
    alt allow
      Function->>Broadcaster: broadcast console event (orgId, tags)
      Broadcaster-->>Function: ack
      Function-->>Client: 200 OK
    else deny
      Function-->>Client: error cannot_access_organization (403)
    end
  else notifyConsole false
    Function->>Broadcaster: regular broadcast
    Broadcaster-->>Function: ack
    Function-->>Client: 200 OK
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Possibly related PRs

Poem

🐰 I hopped through handlers, checks in tow,
I asked for org IDs before I’d go,
Permissions green-lit, strangers kept at bay,
Tests bound in circles to prove the new way,
A happy little broadcast — hop, nibble, play!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main security fix: authorizing org-scoped console event broadcasts, which is the primary change across the modified files.
Description check ✅ Passed The description includes a summary and test plan sections matching the template, though it omits manual testing steps and does not complete all checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-events-org-broadcast-auth
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/events.test.ts (1)

125-164: Add regression coverage for missing org ID guard.

The new backend path also rejects notifyConsole requests without user_id (missing_org_id), but this branch isn’t tested yet. Adding one test will lock the guard in place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/events.test.ts` around lines 125 - 164, Add a regression test that
submits a POST to the /private/events endpoint with notifyConsole: true but
omits user_id and assert the response is HTTP 400 with error 'missing_org_id';
update tests/events.test.ts by adding a new it(...) (similar to the existing
"rejects console event broadcast for a foreign org" case) that sends the same
payload fields (channel, event, description, notifyConsole: true) but without
user_id and verifies response.status === 400 and response.json().error ===
'missing_org_id' to lock the org ID guard in place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/events.test.ts`:
- Around line 125-164: Update the two test cases in tests/events.test.ts from
synchronous it(...) to parallel it.concurrent(...): change the 'broadcasts
console event for an authorized org' and 'rejects console event broadcast for a
foreign org' blocks to use it.concurrent so they run in parallel with the test
suite; ensure the same test bodies, names, and assertions remain unchanged but
replace the it symbol with it.concurrent to comply with the project's parallel
test policy.

---

Nitpick comments:
In `@tests/events.test.ts`:
- Around line 125-164: Add a regression test that submits a POST to the
/private/events endpoint with notifyConsole: true but omits user_id and assert
the response is HTTP 400 with error 'missing_org_id'; update
tests/events.test.ts by adding a new it(...) (similar to the existing "rejects
console event broadcast for a foreign org" case) that sends the same payload
fields (channel, event, description, notifyConsole: true) but without user_id
and verifies response.status === 400 and response.json().error ===
'missing_org_id' to lock the org ID guard in place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8ebd4d07-3929-4081-9db5-962131f5bf7c

📥 Commits

Reviewing files that changed from the base of the PR and between 877c35b and b121544.

📒 Files selected for processing (2)
  • supabase/functions/_backend/private/events.ts
  • tests/events.test.ts

Comment thread tests/events.test.ts Outdated
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

CI blocker: the failing Run tests job is in the repo-wide backend suite, not in the event-auth changes from this PR. The failure is a not-null violation on public.app_versions_meta.owner_org while updating version metadata (job: https://github.com/Cap-go/capgo/actions/runs/23203972464/job/67433969864). I did not touch the branch because this looks like a broader backend/test-data issue rather than a small PR-local fix.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

CI still fails on the shared owner_org constraint, not this branch. The Run tests job hits null value in column "owner_org" of relation "app_versions_meta" violates not-null constraint inside tests/app.test.ts, so this needs the broader backend fixture/data fix before rerunning. PR: #1818

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

CI blocker on PR #1818: the failing Run tests job is tripping with . This looks like a shared CLI regression rather than a branch-local change.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

CI blocker on PR #1818: the failing Run tests job is tripping tests/cli-min-version.test.ts > should test auto min version flag with AssertionError: expected false to be true. This looks like a shared CLI regression rather than a branch-local change.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

Run tests fails in tests/cli-min-version.test.ts with result.success becoming false. The failure points to shared CLI/API-key test state, not a narrow change in this PR.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

PR #1818: the failing test is tests/cli-min-version.test.ts. The first upload can be rejected as same bundle content before min-version logic runs because the test reuses identical bundle bytes across uploads. The fix is to vary the bundle payload per upload or otherwise avoid checksum dedupe in the fixture.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
supabase/functions/_backend/private/events.ts (1)

45-50: Consolidate org authorization to a single path for notifyConsole.

There are now two org-access checks for the same flow (precheck at Line 36 and RBAC at Lines 48-50), which can return different error formats/statuses and duplicate permission I/O. A single gate will keep behavior deterministic.

♻️ Suggested consolidation
-  if (requestedOrgId && !(await canAccessRequestedOrg(c, requestedOrgId)))
-    return c.json({ error: 'Forbidden' }, 403)

   // notifyConsole: broadcast to Supabase Realtime only, skip all tracking
   if (body.notifyConsole) {
-    if (!body.user_id) {
+    if (!requestedOrgId) {
       throw simpleError('missing_org_id', 'Missing org ID for console notification')
     }
-    if (!(await checkPermission(c, 'org.read', { orgId: body.user_id }))) {
-      throw simpleError('cannot_access_organization', 'You can\'t access this organization', { org_id: body.user_id })
+    if (!(await checkPermission(c, 'org.read', { orgId: requestedOrgId }))) {
+      throw simpleError('cannot_access_organization', 'You can\'t access this organization', { org_id: requestedOrgId })
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/_backend/private/events.ts` around lines 45 - 50, The
notifyConsole flow currently performs two org-access checks (a precheck and an
RBAC check using checkPermission), causing duplicated permission I/O and
inconsistent error responses; consolidate to a single gate by removing the
redundant precheck and keeping one canonical check: first assert body.user_id
exists, then call checkPermission(c, 'org.read', { orgId: body.user_id }) and on
failure throw a single consistent simpleError (e.g.,
'cannot_access_organization') so all authorization failures follow the same path
and format (update the function notifyConsole to use only this single
presence+RBAC check).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@supabase/functions/_backend/private/events.ts`:
- Around line 45-50: The notifyConsole flow currently performs two org-access
checks (a precheck and an RBAC check using checkPermission), causing duplicated
permission I/O and inconsistent error responses; consolidate to a single gate by
removing the redundant precheck and keeping one canonical check: first assert
body.user_id exists, then call checkPermission(c, 'org.read', { orgId:
body.user_id }) and on failure throw a single consistent simpleError (e.g.,
'cannot_access_organization') so all authorization failures follow the same path
and format (update the function notifyConsole to use only this single
presence+RBAC check).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1567ce87-263c-4ab2-8c41-be7b95eb8e5a

📥 Commits

Reviewing files that changed from the base of the PR and between d56b79f and f76c97e.

📒 Files selected for processing (2)
  • supabase/functions/_backend/private/events.ts
  • tests/events.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/events.test.ts

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f76c97ebb9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_backend/private/events.ts Outdated
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 19, 2026

Pulled in origin/main and resolved the merge in supabase/functions/_backend/private/events.ts by keeping your resolveTrackingUserId logic while wiring in the new canAccessRequestedOrg guard for notifyConsole broadcasts. tests/events.test.ts was rewritten to cover both the notifyConsole happy path (+ cross-org rejection) and the jwt/api-key cases so the merged branch still exercises the new auth behavior.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 19, 2026

@riderx We just need you. Thank you for the pull request. We just need you to reply or fix your pull request according to the AI comments. When the AI reviewer is done and the build passes in the CI, we will merge your pull request.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 19, 2026

CI is failing in Run tests, but the job stops during Run Supabase Start with Error status 502 before it reaches the branch-local auth change in supabase/functions/_backend/private/events.ts. This looks like a Supabase bootstrap/environment failure rather than a fix in this PR. PR link: #1818

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 19, 2026

CI is still red on https://github.com/Cap-go/capgo/actions/runs/23273358364/job/67670834123. The job stops in Run Supabase Start with Error status 502 before it reaches the branch-local auth changes, so this looks like a Supabase/bootstrap failure rather than a simple fix in the PR itself.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cea5914232

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_backend/private/events.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/functions/_backend/private/events.ts`:
- Around line 34-35: The 403 branch that currently returns an ad-hoc JSON should
use the shared error helper: instead of returning c.json({ error: 'Forbidden' },
403) after the checkPermission call (see checkPermission and requestedOrgId),
return the standardized error shape via c.json(simpleError('Forbidden', 403))
(or quickError if your codebase prefers quickError signature); add the needed
import for simpleError/quickError and replace the ad-hoc response so all backend
endpoints share the same error shape and status handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b574034-ca78-4f6c-a1e2-ad675baa4f57

📥 Commits

Reviewing files that changed from the base of the PR and between cea5914 and 616503b.

📒 Files selected for processing (1)
  • supabase/functions/_backend/private/events.ts

Comment thread supabase/functions/_backend/private/events.ts Outdated
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 20, 2026

Merged main and resolved the conflict in supabase/functions/_backend/private/events.ts by reintroducing the ResolveTrackingUserId/CanAccessRequestedOrg helpers and aligning the notifyConsole flow with the upstream logic before pushing the branch.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63b8a723b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/events.test.ts
Comment thread tests/events.test.ts Outdated
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 7a15136 into main Mar 20, 2026
15 checks passed
@riderx riderx deleted the fix/security-events-org-broadcast-auth branch March 20, 2026 04:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant