Conversation
📝 WalkthroughWalkthroughRecreates and restricts three overloads of public.get_total_metrics, adding SECURITY DEFINER, org membership and auth checks, cache seeding/validation, dynamic date resolution (via Stripe anchor_day), ownership changes to postgres, and updated grants/revocations. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Caller as Caller Context
participant Auth as Auth System
participant OrgDB as Org Database
participant Cache as Metrics Cache
participant Metrics as Metrics Seeder/Calc
Client->>Caller: invoke get_total_metrics(org_id, start_date, end_date) or overload
Caller->>Auth: resolve auth.uid() / service_role check
Auth->>OrgDB: verify org exists and user is org member (if not service_role)
OrgDB-->>Auth: org/user validated
Auth->>Cache: check cached metrics for org/date range
alt cache missing or stale
Cache->>Metrics: seed_org_metrics_cache(org_id, start_date, end_date)
Metrics-->>Cache: updated metrics
Cache-->>Client: return fresh metrics
else cache valid
Cache-->>Client: return cached metrics
end
sequenceDiagram
participant Client
participant Auth as Auth System
participant Stripe as Billing Data
participant DateCalc as Date Logic
participant Main as get_total_metrics(org_id, start_date, end_date)
Client->>Auth: invoke get_total_metrics(org_id)
Auth->>Stripe: fetch anchor_day for org
Stripe-->>DateCalc: anchor_day
DateCalc->>Auth: compute v_start_date, v_end_date
Auth->>Main: call get_total_metrics(org_id, v_start_date, v_end_date)
Main-->>Client: return metrics
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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/20260224093000_fix_get_total_metrics_auth.sqlUser Error: No dialect was specified. You must configure a dialect or specify one on the command line using --dialect after the command. Available dialects: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e55d3f4769
ℹ️ 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".
| v_request_user := (SELECT auth.uid()); | ||
|
|
||
| IF v_request_user IS NULL THEN | ||
| IF COALESCE(current_setting('role', true), '') NOT IN ('service_role', 'postgres') THEN |
There was a problem hiding this comment.
Detect privileged callers via session_user
Using current_setting('role', true) here does not reliably allow internal postgres/service-role sessions: in regular SQL sessions (including pg
tasks and psql without SET ROLE), that setting is typically 'none', so when auth.uid() is NULL this branch returns early and get_total_metrics yields no rows for callers you intended to preserve. In a SECURITY DEFINER function, use session_user (or JWT role claims) for this bypass check instead.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql (2)
56-69:pg_stat_xact_user_tables.relnameis not schema-qualified.The
relnamecolumn doesn't carry schema information. If identically named tables exist in other schemas, this check could trigger false cache refreshes. Consider also filtering onschemaname = 'public'to tighten the match.Suggested diff
FROM pg_catalog.pg_stat_xact_user_tables WHERE relname IN ( 'apps', 'deleted_apps', 'daily_mau', 'daily_bandwidth', 'daily_build_time', 'daily_version', 'app_versions', 'app_versions_meta' ) + AND schemaname = 'public' AND (n_tup_ins > 0 OR n_tup_upd > 0 OR n_tup_del > 0)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql` around lines 56 - 69, The WHERE clause currently compares pg_stat_xact_user_tables.relname against a list of table names (e.g., 'apps','deleted_apps','daily_mau', etc.) without schema qualification, which can match same-named tables in other schemas; update the condition on pg_stat_xact_user_tables in this migration so it also filters on schemaname = 'public' (or the intended schema) to restrict matches to the correct schema and avoid false cache refreshes, keeping the existing relname IN (...) predicate intact.
144-157: Duplicate auth check —org_usersis queried twice per 1-arg call.The 1-arg overload performs its own auth/membership check (lines 144–157), then delegates to the 3-arg overload (line 187) which runs the same check again. This is fine as defense-in-depth, but if the double lookup on
org_usersis a concern, the inner call could be refactored to a shared internal helper that skips the auth gate.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql` around lines 144 - 157, The 1-arg get_total_metrics performs an auth membership check against public.org_users and then calls the 3-arg overload which repeats the same check; refactor by introducing a shared internal helper (e.g. get_total_metrics_internal or add a boolean parameter skip_auth) that implements the core aggregation logic without re-checking membership, have the 1-arg overload call that helper after its auth check, and update the 3-arg overload to call the helper only when it should perform auth (or respect skip_auth) so the duplicate org_users lookup is avoided while preserving the guarded path.
🤖 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/20260224093000_fix_get_total_metrics_auth.sql`:
- Around line 159-164: The WHERE clause in the SELECT that sets v_anchor_day
uses an unqualified org_id which may resolve to a column instead of the function
parameter; update the filter to reference the function parameter explicitly
(e.g., use get_total_metrics.org_id) so the condition reads against the function
parameter; ensure this same qualification approach is applied to any other bare
org_id usages in this function and that v_anchor_day and
subscription_anchor_start remain referenced as shown.
- Around line 35-38: The WHERE clause in function get_total_metrics uses an
unqualified parameter org_id (WHERE o.id = org_id); qualify it as
get_total_metrics.org_id to match the existing pattern in this function (see
other references at lines 42, 51, 87) and comply with the coding standard
requiring fully qualified names.
---
Nitpick comments:
In `@supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql`:
- Around line 56-69: The WHERE clause currently compares
pg_stat_xact_user_tables.relname against a list of table names (e.g.,
'apps','deleted_apps','daily_mau', etc.) without schema qualification, which can
match same-named tables in other schemas; update the condition on
pg_stat_xact_user_tables in this migration so it also filters on schemaname =
'public' (or the intended schema) to restrict matches to the correct schema and
avoid false cache refreshes, keeping the existing relname IN (...) predicate
intact.
- Around line 144-157: The 1-arg get_total_metrics performs an auth membership
check against public.org_users and then calls the 3-arg overload which repeats
the same check; refactor by introducing a shared internal helper (e.g.
get_total_metrics_internal or add a boolean parameter skip_auth) that implements
the core aggregation logic without re-checking membership, have the 1-arg
overload call that helper after its auth check, and update the 3-arg overload to
call the helper only when it should perform auth (or respect skip_auth) so the
duplicate org_users lookup is avoided while preserving the guarded path.
supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql
Outdated
Show resolved
Hide resolved
supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/20260224093000_fix_get_total_metrics_auth.sql`:
- Line 155: Replace the unqualified parameter org_id in the call to
public.get_total_metrics with the fully qualified parameter name used elsewhere
in this function (get_total_metrics.org_id); update the FROM clause that
currently calls public.get_total_metrics(org_id, v_start_date, v_end_date) to
pass get_total_metrics.org_id instead so it matches other references (e.g.,
get_total_metrics.org_id) and complies with the qualification guideline.
- Line 56: The call to public.seed_org_metrics_cache and subsequent comparisons
use unqualified start_date and end_date while org_id is qualified; update all
references to start_date and end_date in the get_total_metrics function to be
fully qualified as get_total_metrics.start_date and get_total_metrics.end_date
(including the arguments in the call that sets cache_entry and the comparisons
around lines where start_date/end_date are used) so that cache_entry :=
public.seed_org_metrics_cache(get_total_metrics.org_id,
get_total_metrics.start_date, get_total_metrics.end_date) and any comparisons
reference get_total_metrics.start_date/get_total_metrics.end_date consistently.
- Around line 179-195: The v_request_org_id uuid variable is being assigned
current_setting(...) (text) directly and compared to '' which causes invalid
uuid casts; instead introduce a temporary text variable (e.g.,
v_request_org_id_text), SELECT current_setting('request.jwt.claim.org_id', true)
INTO that text variable, test IF v_request_org_id_text IS NULL OR
v_request_org_id_text = '' THEN handle empty (return or fallback to org_users
lookup) ELSE CAST v_request_org_id_text::uuid INTO v_request_org_id; update
references to use v_request_org_id after the safe cast and remove the direct
text-to-uuid assignment/comparison that was causing the type errors.
| ) | ||
| AND (n_tup_ins > 0 OR n_tup_upd > 0 OR n_tup_del > 0) | ||
| ) THEN | ||
| cache_entry := public.seed_org_metrics_cache(get_total_metrics.org_id, start_date, end_date); |
There was a problem hiding this comment.
Qualify start_date and end_date parameters for consistency.
Parameters start_date and end_date are passed/compared unqualified at lines 56, 75, and 76, while org_id is consistently qualified as get_total_metrics.org_id throughout this same function. Per coding guidelines, all references should be fully qualified.
♻️ Proposed fix
- cache_entry := public.seed_org_metrics_cache(get_total_metrics.org_id, start_date, end_date);
+ cache_entry := public.seed_org_metrics_cache(get_total_metrics.org_id, get_total_metrics.start_date, get_total_metrics.end_date);- AND cache_entry.start_date = start_date
- AND cache_entry.end_date = end_date
+ AND cache_entry.start_date = get_total_metrics.start_date
+ AND cache_entry.end_date = get_total_metrics.end_dateAlso applies to: 75-76
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql` at line
56, The call to public.seed_org_metrics_cache and subsequent comparisons use
unqualified start_date and end_date while org_id is qualified; update all
references to start_date and end_date in the get_total_metrics function to be
fully qualified as get_total_metrics.start_date and get_total_metrics.end_date
(including the arguments in the call that sets cache_entry and the comparisons
around lines where start_date/end_date are used) so that cache_entry :=
public.seed_org_metrics_cache(get_total_metrics.org_id,
get_total_metrics.start_date, get_total_metrics.end_date) and any comparisons
reference get_total_metrics.start_date/get_total_metrics.end_date consistently.
| metrics.fail, | ||
| metrics.install, | ||
| metrics.uninstall | ||
| FROM public.get_total_metrics(org_id, v_start_date, v_end_date) AS metrics; |
There was a problem hiding this comment.
Qualify org_id function argument for consistency.
org_id at line 155 is the function parameter but is referenced unqualified in the delegate call, inconsistent with get_total_metrics.org_id used throughout the rest of the file (including line 132 in this same function). As per coding guidelines, fully qualified names should be used for all references.
♻️ Proposed fix
- FROM public.get_total_metrics(org_id, v_start_date, v_end_date) AS metrics;
+ FROM public.get_total_metrics(get_total_metrics.org_id, v_start_date, v_end_date) AS metrics;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| FROM public.get_total_metrics(org_id, v_start_date, v_end_date) AS metrics; | |
| FROM public.get_total_metrics(get_total_metrics.org_id, v_start_date, v_end_date) AS metrics; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql` at line
155, Replace the unqualified parameter org_id in the call to
public.get_total_metrics with the fully qualified parameter name used elsewhere
in this function (get_total_metrics.org_id); update the FROM clause that
currently calls public.get_total_metrics(org_id, v_start_date, v_end_date) to
pass get_total_metrics.org_id instead so it matches other references (e.g.,
get_total_metrics.org_id) and complies with the qualification guideline.
| v_request_org_id uuid; | ||
| BEGIN | ||
| SELECT public.get_identity() INTO v_request_user; | ||
|
|
||
| IF v_request_user IS NULL THEN | ||
| RETURN; | ||
| END IF; | ||
|
|
||
| SELECT current_setting('request.jwt.claim.org_id', true) INTO v_request_org_id; | ||
|
|
||
| IF v_request_org_id IS NULL OR v_request_org_id = '' THEN | ||
| SELECT org_users.org_id | ||
| INTO v_request_org_id | ||
| FROM public.org_users | ||
| WHERE org_users.user_id = v_request_user | ||
| ORDER BY org_users.org_id | ||
| LIMIT 1; |
There was a problem hiding this comment.
uuid variable receives raw text GUC — throws on empty string; comparison = '' is also a type error.
Two related bugs:
-
Line 187 —
current_setting(...)returnstext. Assigning it directlyINTO v_request_org_id uuidperforms an implicit text→uuid cast. If PostgREST setsrequest.jwt.claim.org_idto''(an empty JWT claim field), PostgreSQL raisesinvalid input syntax for type uuid: ""— the function 500s instead of returning empty. -
Line 189 —
v_request_org_id = ''compares auuidcolumn to atextempty-string literal. PostgreSQL will attempt to cast''touuid, which also fails. In practice this line is never reached with''because line 187 already threw, but it would need fixing regardless.
The fix is to capture the raw setting into a text variable and only cast to uuid after confirming it is non-empty:
🐛 Proposed fix
DECLARE
v_request_user uuid;
v_request_org_id uuid;
+ v_raw_org_id text;
BEGIN
SELECT public.get_identity() INTO v_request_user;
IF v_request_user IS NULL THEN
RETURN;
END IF;
- SELECT current_setting('request.jwt.claim.org_id', true) INTO v_request_org_id;
-
- IF v_request_org_id IS NULL OR v_request_org_id = '' THEN
+ v_raw_org_id := current_setting('request.jwt.claim.org_id', true);
+
+ IF v_raw_org_id IS NOT NULL AND v_raw_org_id <> '' THEN
+ BEGIN
+ v_request_org_id := v_raw_org_id::uuid;
+ EXCEPTION WHEN invalid_text_representation THEN
+ v_request_org_id := NULL;
+ END;
+ END IF;
+
+ IF v_request_org_id IS NULL THEN
SELECT org_users.org_id
INTO v_request_org_id
FROM public.org_users
WHERE org_users.user_id = v_request_user
ORDER BY org_users.org_id
LIMIT 1;
END IF;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@supabase/migrations/20260224093000_fix_get_total_metrics_auth.sql` around
lines 179 - 195, The v_request_org_id uuid variable is being assigned
current_setting(...) (text) directly and compared to '' which causes invalid
uuid casts; instead introduce a temporary text variable (e.g.,
v_request_org_id_text), SELECT current_setting('request.jwt.claim.org_id', true)
INTO that text variable, test IF v_request_org_id_text IS NULL OR
v_request_org_id_text = '' THEN handle empty (return or fallback to org_users
lookup) ELSE CAST v_request_org_id_text::uuid INTO v_request_org_id; update
references to use v_request_org_id after the safe cast and remove the direct
text-to-uuid assignment/comparison that was causing the type errors.
|
* fix(security): revoke anon access to exist_app_v2 rpc * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * fix(security): revoke PUBLIC execute on exist_app_v2 rpc * fix(security): revoke anon execute on exist_app_v2 rpc * fix(database): enforce org-scoped webhook rls (#1676) * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(security): revoke anon access to exist_app_v2 rpc * fix(security): revoke PUBLIC execute on exist_app_v2 rpc * fix(security): revoke anon execute on exist_app_v2 rpc * fix(frontend): require confirmation before URL login session (#1688) * fix(frontend): require confirmation for URL session login * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * fix(frontend): retain tokens until query login succeeds * fix(database): enforce org-scoped webhook rls (#1676) * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(frontend): require confirmation for URL session login * fix(frontend): retain tokens until query login succeeds --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * chore(release): 12.116.4 * Riderx/fix email otp rpc reopen (#1693) * fix(security): restrict email otp verification rpc path * fix(security): also revoke otp rpc execute from public * fix(security): record email otp verification via service-side rpc * fix(security): harden email otp verification RPC usage * fix(db): drop legacy record_email_otp_verified overload * fix(frontend): delete replaced profile images from storage (#1683) * fix(frontend): delete replaced profile images from storage * fix(backend): clean stale unlinked user avatars * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * fix: address sonar regex exec suggestions * fix(database): enforce org-scoped webhook rls (#1676) * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(frontend): delete replaced profile images from storage * fix(backend): clean stale unlinked user avatars * fix: address sonar regex exec suggestions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: restrict find_apikey_by_value RPC to service role (#1672) * fix(security): restrict find_apikey_by_value to service role * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * fix(database): enforce org-scoped webhook rls (#1676) * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(security): restrict find_apikey_by_value to service role --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: secure get_total_metrics rpc (#1671) * fix(db): harden get_total_metrics rpc auth * fix(db): qualify org_id and harden rpc role checks * fix(db): align get_total_metrics auth overloads * fix(db): harden get_total_metrics rpc auth * fix(db): qualify org_id and harden rpc role checks * fix(db): align get_total_metrics auth overloads * fix(db): harden get_total_metrics rpc auth * fix(db): qualify org_id and harden rpc role checks * fix(db): align get_total_metrics auth overloads * fix(backend): validate stripe redirect URLs (#1681) * fix(backend): validate stripe redirect URLs * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * test(backend): add stripe redirect validation tests * test(backend): fix stripe redirect unit test env setup * fix(database): enforce org-scoped webhook rls (#1676) * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(backend): validate stripe redirect URLs * test(backend): add stripe redirect validation tests * test(backend): fix stripe redirect unit test env setup --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * feat(api): auto cleanup EXIF image metadata (#1673) * feat(api): auto cleanup image metadata on updates * fix: preserve content type when stripping image metadata * fix(security): restrict get_orgs_v6(userid uuid) access (#1677) * fix(security): restrict get_orgs_v6(uuid) execution to private roles * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * fix(database): enforce org-scoped webhook rls (#1676) * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(security): restrict get_orgs_v6(uuid) execution to private roles --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix(security): revoke anon access to apikey oracle RPCs (#1670) * fix(security): restrict apikey oracle rpc execution * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * fix: remove anon-backed get_user_id calls in private apikey flows * fix(database): enforce org-scoped webhook rls (#1676) * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(security): restrict apikey oracle rpc execution * fix: remove anon-backed get_user_id calls in private apikey flows --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix(security): require capgkey auth in exist_app_v2 * fix(api): block scoped apikey key creation (#1685) * fix(api): block scoped apikeys from creating keys * fix(build): restore TUS HEAD upload routing (#1664) * fix(build): handle tus HEAD upload route * fix(build): chain HEAD middleware correctly * chore(release): 12.116.2 * fix(security): protect replication endpoint (#1686) * fix(security): protect replication endpoint * fix(api): require replication endpoint internal api secret * fix(api): allow admin JWT access to replication endpoint * fix(frontend): use admin session only when no replication secret * fix(database): enforce org-scoped webhook rls (#1676) * test: fix apikey test lint violations * fix: use exact app_id match for preview lookup (#1674) * fix(backend): use exact app_id match for preview lookup * fix(backend): preserve case-insensitive preview app lookup * chore(release): 12.116.3 * fix(api): block scoped apikeys from creating keys * test: fix apikey test lint violations --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: restrict webhook secret access to admin-only (#1692) * fix(security): restrict webhook secret read access * fix(rls): restrict webhook reads to admins * fix(security): keep only apikey-based exist_app_v2 check * fix(security): require capgkey auth in exist_app_v2 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>



Summary (AI generated)
public.get_total_metricsto block unauthenticated/public access.public.org_usersfor user-based requests.service_roleandpostgresrole calls without a JWT user context.anon/publicgrants on both overloads.Motivation (AI generated)
The RPC was callable with only the publishable key and exposed usage/financial signals for valid organizations, creating an org-existence oracle and operational metrics leak.
Business Impact (AI generated)
Test Plan (AI generated)
bun lint.POST /rest/v1/rpc/get_total_metricswith anon/sb_publishable key now returns no rows.Summary by CodeRabbit