Skip to content

fix: restrict cleanup_expired_demo_apps execution#1807

Merged
riderx merged 8 commits into
mainfrom
fix/ghsa-48wc-cleanup-expired-demo-apps
Mar 20, 2026
Merged

fix: restrict cleanup_expired_demo_apps execution#1807
riderx merged 8 commits into
mainfrom
fix/ghsa-48wc-cleanup-expired-demo-apps

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 17, 2026

Summary (AI generated)

  • revoke PUBLIC, anon, and authenticated execute access on public.cleanup_expired_demo_apps()
  • keep service_role execution so the internal cron path still works
  • add a regression test that asserts the final database grants match that contract

Motivation (AI generated)

cleanup_expired_demo_apps() is an internal maintenance function. A later grant state left it callable outside the intended cron/service-role path, which made the advisory actionable even though the function is not meant to be public.

Business Impact (AI generated)

This removes an avoidable data-deletion risk from a public-facing surface and keeps Capgo's maintenance-only cleanup flow constrained to internal callers.

Test Plan (AI generated)

  • TMPDIR=/tmp TEMP=/tmp TMP=/tmp bunx sqlfluff lint --dialect postgres supabase/migrations/20260317011315_revoke_cleanup_expired_demo_apps_public_exec.sql
  • TMPDIR=/tmp TEMP=/tmp TMP=/tmp bunx eslint tests/cleanup-expired-demo-apps-rpc.test.ts
  • SUPABASE_DB_URL='postgresql://postgres:postgres@127.0.0.1:59782/postgres' TMPDIR=/tmp TEMP=/tmp TMP=/tmp bunx vitest run tests/cleanup-expired-demo-apps-rpc.test.ts

Generated with AI

Summary by CodeRabbit

  • Tests

    • Added integration tests to verify access control restrictions for internal operations.
  • Chores

    • Updated access control policies for internal operations to enforce security boundaries.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 17, 2026

Tracking advisory GHSA-48wc-p6jm-q5j2.

@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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 317831df-5e22-40d0-896c-94a85b1a67e4

📥 Commits

Reviewing files that changed from the base of the PR and between 244b4a2 and 4947f97.

📒 Files selected for processing (1)
  • supabase/migrations/20260317020500_revoke_cleanup_expired_demo_apps_public_exec.sql
✅ Files skipped from review due to trivial changes (1)
  • supabase/migrations/20260317020500_revoke_cleanup_expired_demo_apps_public_exec.sql

📝 Walkthrough

Walkthrough

This PR adds a SQL migration to revoke RPC authorization from anonymous and authenticated roles on the cleanup_expired_demo_apps() function, restricting access to the service role. An integration test verifies these authorization restrictions are properly enforced across all role types.

Changes

Cohort / File(s) Summary
RPC Authorization Test
tests/cleanup-expired-demo-apps-rpc.test.ts
New integration test that verifies EXECUTE privileges on cleanup_expired_demo_apps() function are restricted to service_role only, confirming anon, authenticated, and public roles lack access.
Authorization Migration
supabase/migrations/20260317020500_revoke_cleanup_expired_demo_apps_public_exec.sql
SQL migration that revokes all privileges on public.cleanup_expired_demo_apps() function from PUBLIC, ANON, and AUTHENTICATED roles to restrict execution to service_role.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 A function once open, now locked up tight,
Service role only—no public in sight!
Tests verify fences where anon can't tread,
Security wrapped 'round each line that's spread. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main security-focused change: restricting execution of the cleanup_expired_demo_apps function to authorized roles only.
Description check ✅ Passed The description includes a comprehensive summary with motivation and business impact, and provides a detailed test plan with specific commands executed. However, it lacks the structured format of the template with proper markdown sections and a filled checklist.
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/ghsa-48wc-cleanup-expired-demo-apps
📝 Coding Plan
  • Generate coding plan for human review comments

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 SQLFluff (4.0.4)
supabase/migrations/20260317020500_revoke_cleanup_expired_demo_apps_public_exec.sql

User Error: No dialect was specified. You must configure a dialect or specify one on the command line using --dialect after the command. Available dialects:
ansi, athena, bigquery, clickhouse, databricks, db2, doris, duckdb, exasol, flink, greenplum, hive, impala, mariadb, materialize, mysql, oracle, postgres, redshift, snowflake, soql, sparksql, sqlite, starrocks, teradata, trino, tsql, vertica


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

🤖 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/cleanup-expired-demo-apps-rpc.test.ts`:
- Line 12: Change the test declaration for "keeps execute privilege only for
service-role callers" from it(...) to it.concurrent(...) so the test runs in
parallel; locate the test function with that exact description in the file and
replace the it call with it.concurrent to comply with the repository-wide
parallel test contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1252e08-5e03-4bbd-b07b-4c1e6562dc9e

📥 Commits

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

📒 Files selected for processing (2)
  • supabase/migrations/20260317011315_revoke_cleanup_expired_demo_apps_public_exec.sql
  • tests/cleanup-expired-demo-apps-rpc.test.ts

Comment thread tests/cleanup-expired-demo-apps-rpc.test.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.

🧹 Nitpick comments (1)
tests/cleanup-expired-demo-apps-rpc.test.ts (1)

26-29: Consider asserting row presence before privilege assertions.

This makes failures more explicit if the query shape changes unexpectedly.

Proposed tweak
-    expect(rows[0]?.service_role_can_execute).toBe(true)
-    expect(rows[0]?.anon_can_execute).toBe(false)
-    expect(rows[0]?.authenticated_can_execute).toBe(false)
-    expect(rows[0]?.public_can_execute).toBe(false)
+    expect(rows).toHaveLength(1)
+    const row = rows[0]
+    if (!row)
+      throw new Error('Expected one privilege row')
+
+    expect(row.service_role_can_execute).toBe(true)
+    expect(row.anon_can_execute).toBe(false)
+    expect(row.authenticated_can_execute).toBe(false)
+    expect(row.public_can_execute).toBe(false)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cleanup-expired-demo-apps-rpc.test.ts` around lines 26 - 29, The test
currently asserts privilege flags on rows[0] without first ensuring a row
exists; add an explicit presence assertion (e.g.,
expect(rows.length).toBeGreaterThan(0) or expect(rows[0]).toBeDefined()) before
the checks of rows[0]?.service_role_can_execute, rows[0]?.anon_can_execute,
rows[0]?.authenticated_can_execute, and rows[0]?.public_can_execute so failures
show a clear missing-row error rather than silent undefined flag assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/cleanup-expired-demo-apps-rpc.test.ts`:
- Around line 26-29: The test currently asserts privilege flags on rows[0]
without first ensuring a row exists; add an explicit presence assertion (e.g.,
expect(rows.length).toBeGreaterThan(0) or expect(rows[0]).toBeDefined()) before
the checks of rows[0]?.service_role_can_execute, rows[0]?.anon_can_execute,
rows[0]?.authenticated_can_execute, and rows[0]?.public_can_execute so failures
show a clear missing-row error rather than silent undefined flag assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4df93453-a8b0-4434-8a37-31bae41b288f

📥 Commits

Reviewing files that changed from the base of the PR and between fe843aa and 37371be.

📒 Files selected for processing (1)
  • tests/cleanup-expired-demo-apps-rpc.test.ts

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

CI is blocked by a repo-wide backend test failure, not by this PR. The failing \ job hits a \ error, with related \ constraint failures while exercising version metadata writes. I did not patch this branch because the regression appears outside this PR; once the shared backend issue is fixed, CI should be rerun.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

CI is blocked by a repo-wide backend test failure, not by this PR. The failing Run tests job hits a null value in column "owner_org" of relation "app_versions_meta" error, with related apps.owner_org constraint failures while exercising version metadata writes. I did not patch this branch because the regression appears outside this PR; once the shared backend issue is fixed, CI should be rerun.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

CI still fails on the shared owner_org constraint. The Run tests job hits null value in column "owner_org" on apps and app_versions_meta inserts before this migration can finish, so this needs the broader backend data/fixture fix. PR: #1807

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

Run tests fails in tests/apikeys-expiration.test.ts because a valid API key is returning 401 instead of 200. That points to a broader API-key auth regression, not just the new grant/revoke change.

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 `@tests/cleanup-expired-demo-apps-rpc.test.ts`:
- Line 28: Replace the single-line if statement "if (!row) throw new
Error('Expected exactly one privilege row')" with a multi-line block to satisfy
the antfu/if-newline rule: change the condition that checks the variable row
into a normal if block with braces and put the throw on the next line (e.g., if
(!row) { throw new Error('Expected exactly one privilege row') }), ensuring the
newline after the if condition and correct indentation; update the test in
cleanup-expired-demo-apps-rpc.test.ts where the variable row is asserted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a704e52a-2893-4ad2-b396-8f288907f6f8

📥 Commits

Reviewing files that changed from the base of the PR and between 37371be and 37a37f9.

📒 Files selected for processing (1)
  • tests/cleanup-expired-demo-apps-rpc.test.ts

Comment thread tests/cleanup-expired-demo-apps-rpc.test.ts Outdated
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 18, 2026

PR #1807: the failure is the same src/components/DialogV2.vue lint mismatch, but the branch does not reproduce it locally. I verified bunx eslint src/components/DialogV2.vue passes on the current worktree, so I would treat this as stale CI and re-run.

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: c537cb313a

ℹ️ 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".

@@ -0,0 +1,9 @@
REVOKE ALL ON FUNCTION public.cleanup_expired_demo_apps() FROM PUBLIC;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Rename this migration to a newer timestamp

This file is backfilled as 20260317011315, but the parent already contains later migrations such as 20260317020451_secure_remaining_helper_rpcs.sql and 20260317160518_sso_skip_org_on_sso_domain.sql. Supabase applies migrations by timestamp history, and db push only backfills older missing versions when --include-all is used, so any environment that has already advanced past those later versions will skip this revoke during the normal deploy path. In that scenario cleanup_expired_demo_apps() stays executable for anon/authenticated in existing databases even though a fresh db reset and the new test both pass.

Useful? React with 👍 / 👎.

@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.

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: 1594b16b35

ℹ️ 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 on lines +8 to +9
GRANT EXECUTE ON FUNCTION public.cleanup_expired_demo_apps()
TO SERVICE_ROLE;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Drop the unneeded service_role grant on this cron helper

cleanup_expired_demo_apps() is only wired into the internal cron runner via public.cron_tasks in supabase/migrations/20260121000000_add_demo_app_support.sql, and a repo-wide search shows no direct RPC caller that needs service_role execution. Per /workspace/capgo/AGENTS.md's least-privilege rule for PostgreSQL helper functions, granting service_role here keeps a destructive maintenance routine callable by any service-role client even though the existing cron-only helpers in supabase/migrations/20260104120000_revoke_process_function_queue_public_access.sql revoke that access entirely.

Useful? React with 👍 / 👎.

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 953da69 into main Mar 20, 2026
15 checks passed
@riderx riderx deleted the fix/ghsa-48wc-cleanup-expired-demo-apps branch March 20, 2026 04:51
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