Skip to content

[codex] restore transfer_app guard flag and CI stability#1960

Closed
riderx wants to merge 3 commits into
mainfrom
codex/fix-transfer-app-guard-flag
Closed

[codex] restore transfer_app guard flag and CI stability#1960
riderx wants to merge 3 commits into
mainfrom
codex/fix-transfer-app-guard-flag

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 27, 2026

Summary (AI generated)

  • Restore the capgo.allow_owner_org_transfer guard flag inside public.transfer_app
  • Extend the Cloudflare email-preferences suite hook timeout so CI does not fail on slow isolated app seeding

Motivation (AI generated)

Current main fails supabase test db because public.transfer_app updates owner_org without setting the bypass flag expected by the new reassignment guard trigger. After that database issue is fixed, the Cloudflare email-preferences suite can still fail from a CI-only setup timeout, so the prerequisite branch also carries the minimal timeout adjustment needed for stable checks.

Business Impact (AI generated)

This restores green CI on the current base branch and unblocks focused application fixes from shipping without mixing unrelated changes into their PRs.

Test Plan (AI generated)

  • Reproduce that focused PRs on current main fail in Run Supabase Test DB
  • Verify GitHub Actions passes for this prerequisite branch

Generated with AI

Summary by CodeRabbit

  • New Features

    • Added a secure app ownership transfer flow with organization validation, permission checks, transfer cooldown to prevent rapid reassignments, and automatic propagation of ownership updates across related app data.
  • Tests

    • Increased the email preferences test suite setup/teardown timeout to 60 seconds for more reliable test execution.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

Adds a new database migration that creates public.transfer_app(p_app_id, p_new_org_id) — a SECURITY DEFINER function that validates auth, enforces RBAC and a 32‑day transfer cooldown, updates owner_org and cascades changes to related tables with JSONB transfer history; also tightens function permissions. Also sets explicit 60s timeouts for email-preferences test hooks.

Changes

Cohort / File(s) Summary
Database Migration
supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql
Adds public.transfer_app(character varying, uuid) as SECURITY DEFINER with search_path cleared; locks target app row, verifies auth.uid(), rejects same-org transfers, checks RBAC for source and destination orgs, enforces ≥32 days between transfers when history exists, sets capgo.allow_owner_org_transfer via set_config, updates public.apps.owner_org, updated_at, appends a JSONB entry to transfer_history, and updates related tables (app_versions, app_versions_meta, channel_devices, channels, deploy_history). Sets owner to postgres, revokes broad execute, grants authenticated and service_role, and adds a comment describing required permissions.
Tests
tests/email-preferences.test.ts
Introduces hookTimeoutMs = 60000 and applies it explicitly to beforeAll and afterAll Vitest hooks (60s timeout).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as Client (authenticated user)
    participant DB as PostgreSQL (public.transfer_app)
    participant RBAC as RBAC check
    participant Tables as Related Tables (apps, app_versions, channels, deploy_history)

    Note over Client,DB: Call transfer_app(p_app_id, p_new_org_id)
    Client->>DB: invoke transfer_app
    DB->>DB: LOCK target app row
    DB->>RBAC: check permissions for current owner_org (source)
    RBAC-->>DB: allow/deny
    DB->>RBAC: check permissions for p_new_org_id (destination)
    RBAC-->>DB: allow/deny
    DB->>DB: read latest transfer_history -> enforce 32-day cooldown if present
    alt validations pass
        DB->>DB: set_config('capgo.allow_owner_org_transfer', 'true')
        DB->>Tables: UPDATE apps.owner_org, updated_at, append transfer_history entry
        DB->>Tables: UPDATE app_versions, app_versions_meta, channel_devices, channels, deploy_history owner_org
        DB-->>Client: success (void)
    else validation fails
        DB-->>Client: raise exception
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

codex

Poem

🐰
A hop, a lock, a careful check and sign,
I nudged ownership from branch to vine.
With histories kept and time between the hops,
The rabbit stamps the transfer — then he stops. 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes summary and motivation sections with clear explanation, but lacks the required Test plan and Checklist sections from the template. Add a detailed Test plan section with specific reproduction steps and add a completed Checklist section with appropriate checkboxes marked to match the required template format.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes both main changes: restoring the transfer_app guard flag and addressing CI stability via hook timeout.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 codex/fix-transfer-app-guard-flag

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.1.0)
supabase/migrations/20260427092630_restore_transfer_app_guard_flag.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.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 27, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-transfer-app-guard-flag (6730238) with main (b917a8a)

Open in CodSpeed

@riderx riderx marked this pull request as ready for review April 27, 2026 10:59
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 (2)
supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql (2)

28-82: Consider extracting duplicate JSON keys to constants.

SonarCloud flags that 'app_id', 'new_org_id', and 'old_org_id' are duplicated across multiple jsonb_build_object calls. While functional, extracting these to CONSTANT declarations would improve maintainability.

♻️ Proposed refactor to reduce duplication
 DECLARE
     v_old_org_id uuid;
     v_user_id uuid;
     v_last_transfer jsonb;
     v_last_transfer_date timestamp;
     v_transfer_request_error constant text := 'Unable to process transfer request.';
+    k_app_id constant text := 'app_id';
+    k_new_org_id constant text := 'new_org_id';
+    k_old_org_id constant text := 'old_org_id';
+    k_uid constant text := 'uid';
 BEGIN

Then use k_app_id, k_new_org_id, etc. in jsonb_build_object calls.

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

In `@supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql`
around lines 28 - 82, The repeated literal JSON keys ('app_id', 'new_org_id',
'old_org_id', 'uid') used in multiple pg_log calls should be extracted into
constants to reduce duplication and ease future changes; add constant
declarations (e.g., k_app_id, k_new_org_id, k_old_org_id, k_uid) near the top of
the function or script and replace the string literals in every
pg_catalog.jsonb_build_object call inside the public.pg_log invocations
(references: public.pg_log, pg_catalog.jsonb_build_object, and the error-check
blocks using v_old_org_id, p_new_org_id, p_app_id, v_user_id) so each call uses
the new constants instead of repeated literal keys.

94-112: Consider logging successful transfers for audit completeness.

The function logs denial cases via public.pg_log but doesn't log successful transfers. While transfer_history provides a record, an explicit audit log entry would provide consistency with the denial logging pattern.

📋 Proposed addition for success logging

Add after the final UPDATE (before END;):

  PERFORM public.pg_log(
    'success: TRANSFER_COMPLETED',
    pg_catalog.jsonb_build_object(
      'app_id', p_app_id,
      'old_org_id', v_old_org_id,
      'new_org_id', p_new_org_id,
      'uid', v_user_id
    )
  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql`
around lines 94 - 112, The function logs transfer denials but not successful
transfers; add an explicit audit log call after the UPDATE that records the
success so it matches the denial pattern: call public.pg_log with a success tag
(e.g. 'success: TRANSFER_COMPLETED') and a jsonb payload containing p_app_id,
v_old_org_id, p_new_org_id and v_user_id; insert this PERFORM public.pg_log(...)
immediately after the UPDATE that modifies owner_org/transfer_history so
successful transfers are auditable alongside existing denial logs.
🤖 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/migrations/20260427092630_restore_transfer_app_guard_flag.sql`:
- Around line 137-140: The SECURITY DEFINER function transfer_app needs explicit
deny-by-default permissions: after setting OWNER (transfer_app), add a REVOKE
ALL ON FUNCTION public.transfer_app(character varying, uuid) FROM PUBLIC; and
then GRANT EXECUTE ON FUNCTION public.transfer_app(character varying, uuid) TO
the minimal role (e.g., authenticated) so only required roles can call
transfer_app; ensure the function signature in the REVOKE/GRANT matches the
existing transfer_app(p_app_id character varying, p_new_org_id uuid)
declaration.

---

Nitpick comments:
In `@supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql`:
- Around line 28-82: The repeated literal JSON keys ('app_id', 'new_org_id',
'old_org_id', 'uid') used in multiple pg_log calls should be extracted into
constants to reduce duplication and ease future changes; add constant
declarations (e.g., k_app_id, k_new_org_id, k_old_org_id, k_uid) near the top of
the function or script and replace the string literals in every
pg_catalog.jsonb_build_object call inside the public.pg_log invocations
(references: public.pg_log, pg_catalog.jsonb_build_object, and the error-check
blocks using v_old_org_id, p_new_org_id, p_app_id, v_user_id) so each call uses
the new constants instead of repeated literal keys.
- Around line 94-112: The function logs transfer denials but not successful
transfers; add an explicit audit log call after the UPDATE that records the
success so it matches the denial pattern: call public.pg_log with a success tag
(e.g. 'success: TRANSFER_COMPLETED') and a jsonb payload containing p_app_id,
v_old_org_id, p_new_org_id and v_user_id; insert this PERFORM public.pg_log(...)
immediately after the UPDATE that modifies owner_org/transfer_history so
successful transfers are auditable alongside existing denial logs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 021dd919-1609-46c4-9f40-ff9cdb09cefa

📥 Commits

Reviewing files that changed from the base of the PR and between 9b7ddc1 and 9f46a50.

📒 Files selected for processing (2)
  • supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql
  • tests/email-preferences.test.ts

@sonarqubecloud
Copy link
Copy Markdown

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/migrations/20260427092630_restore_transfer_app_guard_flag.sql`:
- Around line 8-13: Create a new migration (run bunx supabase migration new
<feature_slug>) that updates public.transfer_app() to save the previous value of
the session setting before enabling the bypass and restore it before any
return/exit: fetch the current value with
current_setting('capgo.allow_owner_org_transfer', true) into a local variable
(e.g., v_prev_allow_transfer), call
set_config('capgo.allow_owner_org_transfer','true', true) to enable the bypass,
and ensure you call set_config('capgo.allow_owner_org_transfer',
v_prev_allow_transfer, true) to restore the original value right before the
function returns and also in any EXCEPTION blocks so
public.guard_owner_org_reassignment() cannot see the bypass after transfer_app()
completes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6132939-7e13-4f9e-8e93-4479c5a6255e

📥 Commits

Reviewing files that changed from the base of the PR and between 9f46a50 and 6730238.

📒 Files selected for processing (1)
  • supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql

Comment on lines +8 to +13
DECLARE
v_old_org_id uuid;
v_user_id uuid;
v_last_transfer jsonb;
v_last_transfer_date timestamp;
v_transfer_request_error constant text := 'Unable to process transfer request.';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In PostgreSQL, does set_config('capgo.allow_owner_org_transfer', 'true', true) remain active for the rest of the current transaction after a PL/pgSQL function returns, and would a later trigger calling current_setting('capgo.allow_owner_org_transfer', true) still see 'true'?

💡 Result:

Yes, the setting set by set_config('capgo.allow_owner_org_transfer', 'true', true) inside a PL/pgSQL function remains active for the rest of the current transaction after the function returns, and a later trigger in the same transaction calling current_setting('capgo.allow_owner_org_transfer', true) will see the value 'true', provided the set_config call is not within a subtransaction (like a BEGIN...EXCEPTION block) that rolls back or ends before the function returns.

Citations:


🏁 Script executed:

# First, check the current state of the migration file
cat -n supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql | head -150

Repository: Cap-go/capgo

Length of output: 5159


🏁 Script executed:

# Check git status to see if file is uncommitted
git status supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql

Repository: Cap-go/capgo

Length of output: 122


Create a new migration to restore the guard flag before returning.

The set_config('capgo.allow_owner_org_transfer', 'true', true) call at line 95 remains active for the rest of the current transaction after public.transfer_app() returns. This leaves the bypass open for any raw owner_org update in the same transaction after the function completes, allowing public.guard_owner_org_reassignment() to still see the flag as enabled.

Per the migration guidelines, create a new migration file using bunx supabase migration new <feature_slug> to fix this by saving the previous setting value before enabling the bypass and restoring it before the function returns.

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

In `@supabase/migrations/20260427092630_restore_transfer_app_guard_flag.sql`
around lines 8 - 13, Create a new migration (run bunx supabase migration new
<feature_slug>) that updates public.transfer_app() to save the previous value of
the session setting before enabling the bypass and restore it before any
return/exit: fetch the current value with
current_setting('capgo.allow_owner_org_transfer', true) into a local variable
(e.g., v_prev_allow_transfer), call
set_config('capgo.allow_owner_org_transfer','true', true) to enable the bypass,
and ensure you call set_config('capgo.allow_owner_org_transfer',
v_prev_allow_transfer, true) to restore the original value right before the
function returns and also in any EXCEPTION blocks so
public.guard_owner_org_reassignment() cannot see the bypass after transfer_app()
completes.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Apr 27, 2026

No longer needed. The TUS HEAD fix PR has been retargeted directly to main so the security fix stays isolated.

@riderx riderx closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant