Skip to content

DB security hardening: gate leaking RPCs, repair CSV upload, schema-drift columns#793

Merged
njrini99-code merged 3 commits into
mainfrom
fix/db-security-hardening-2026-07-08
Jul 9, 2026
Merged

DB security hardening: gate leaking RPCs, repair CSV upload, schema-drift columns#793
njrini99-code merged 3 commits into
mainfrom
fix/db-security-hardening-2026-07-08

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Follow-up to the 2026-07-08 forensic DB audit (docs/audits/DB_FORENSIC_AUDIT_2026-07-08.md). Independent of PR #792. DB changes already applied to prod via MCP; these migrations are the idempotent repo record.

Fixed

  • 🔴 PII leak (P0): get_users_with_auth() and get_platform_health_stats() were SECURITY DEFINER + authenticated-EXECUTE with no authorization check — any logged-in user could dump every user's email + auth metadata, or read DB/connection telemetry. Now gated with is_admin()/is_super_admin() (still callable by admins, never anon), matching the sibling admin RPCs.
  • 🟠 Ungated RPC cluster (P1): update_user_last_seen (could overwrite any user's timestamp → self-or-admin); dead get_pending_task_reminders/mark_task_reminder_sent, CRM analytics get_crm_click_destinations/get_crm_template_performance, and cron-only refresh_crm_coach_engagement had authenticated EXECUTE revoked. Each verified against app call sites first.
  • 🔴 CSV stat upload broken (P0): uploadStatsCSV wrote upload_batch_id — a column that never existed — so every upload failed the insert. Removed the legacy write; added the 7 real stat columns the type declared but the table lacked (additive/nullable); reconciled the BaseballPlayerStats type with the table exactly.

Verified

tsc 0 · lint 0 · ratchet 0 · unit 4462 · business 4536 · types-drift 0 · schema-invariants 0. Every gated function re-queried post-apply: gates present, admins retain access, anon has none.

Deferred to you (documented, NOT changed)

The course-library cross-tenant write policies (golf_course_tee_holes/golf_course_tees/golf_courses with USING true) let any authenticated user edit any school's course data. This may be intentional (crowd-sourced, soft-delete "grows-from-saves" library). I did not touch it — it's a live golf feature and a product-design call. Decision needed: wiki model (add an audit trigger) vs owner-scoped (tighten policies). See the audit doc §Deferred.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu

Fable Integrator and others added 3 commits July 8, 2026 09:43
…ge holes)

get_users_with_auth() and get_platform_health_stats() were authenticated-
executable with zero authorization — any logged-in user could dump every
user's email + auth metadata. Both now gated with is_admin()/is_super_admin(),
matching sibling admin RPCs. update_user_last_seen() constrained to self-or-
admin; dead task-reminder RPCs, two CRM analytics RPCs, and the cron-only
refresh_crm_coach_engagement had authenticated EXECUTE revoked. Verified each
against app call sites; anon never had access. Applied to prod via MCP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
…write, reconcile type with table

uploadStatsCSV wrote upload_batch_id (a column that never existed on
baseball_player_stats) on every row → every CSV upload failed the insert.
Removed the legacy write (import_run_id is the canonical linkage). Added the 7
real stat columns the type declared but the table lacked (caught_stealing,
sacrifice_bunts, runs_allowed, pitches_thrown, strikes_thrown, launch_angle,
spin_rate) additively; BaseballPlayerStats type now names the 6 real source_*/
import_run_id columns it was missing. Table and type now match exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGqvhig8njW38q9PAwDXgu
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Jul 8, 2026 1:46pm

Request Review

@qodo-code-review

Copy link
Copy Markdown

Qodo is busy working

Check back in a few minutes. Qodo's code review agents are on it.

Grey Divider

@supabase

supabase Bot commented Jul 8, 2026

Copy link
Copy Markdown

Updates to Preview Branch (fix/db-security-hardening-2026-07-08) ↗︎

Deployments Status Updated
Database Wed, 08 Jul 2026 13:50:51 UTC
Services Wed, 08 Jul 2026 13:50:51 UTC
APIs Wed, 08 Jul 2026 13:50:51 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Wed, 08 Jul 2026 13:50:59 UTC
Migrations Wed, 08 Jul 2026 13:51:12 UTC
Seeding Wed, 08 Jul 2026 13:51:14 UTC
Edge Functions ⚠️ Wed, 08 Jul 2026 13:51:14 UTC

⚠️ Warning — Only Functions declared in config.toml will be automatically deployed to branches: [functions.my-slug]


View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • Documentation

    • Added a new audit note summarizing database health, security findings, and remaining risks.
  • Bug Fixes

    • Tightened access to admin-only reporting and telemetry features.
    • Restricted several background and analytics actions to approved access paths.
    • Fixed CSV stat imports so uploads store the right provenance details.
    • Restored missing baseball stat fields in the live database to match the app.

Walkthrough

This PR adds two Supabase migrations gating SECURITY DEFINER RPCs (admin telemetry/PII functions and user last_seen update) and revoking execute grants on several ungated RPCs, adds missing columns to baseball_player_stats, replaces upload_batch_id with new source/import provenance fields in the BaseballPlayerStats type and CSV insert logic, and adds a forensic audit document.

Changes

DB Security Gating and Audit

Layer / File(s) Summary
Gate admin telemetry/PII RPCs
supabase/migrations/20260708020000_gate_admin_telemetry_rpcs_pii.sql
Adds admin authorization checks (raising Forbidden 42501) to get_users_with_auth() and get_platform_health_stats() SECURITY DEFINER functions.
Restrict last_seen updates and revoke ungated RPC grants
supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql
Adds owner/admin check to update_user_last_seen; revokes EXECUTE on task reminder, CRM analytics, and CRM engagement refresh RPCs from authenticated/anon.
Forensic audit documentation
docs/audits/DB_FORENSIC_AUDIT_2026-07-08.md
Documents dimension grades, fixed security items, deferred issues, and health verification findings.

Baseball Stats Provenance and Schema Fix

Layer / File(s) Summary
Update provenance fields and CSV insert
src/lib/types/index.ts, src/app/baseball/actions/stats.ts
Removes upload_batch_id; adds source_trust_level, source_visibility, source_match_confidence, source_match_tier, source_external_id, import_run_id to BaseballPlayerStats; CSV insert sets source: 'csv_upload' instead of upload_batch_id.
Add missing real stat columns
supabase/migrations/20260708022000_baseball_player_stats_real_stat_columns.sql
Adds caught_stealing, sacrifice_bunts, runs_allowed, pitches_thrown, strikes_thrown, launch_angle, spin_rate columns via ADD COLUMN IF NOT EXISTS.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant get_platform_health_stats
  participant is_super_admin_is_admin
  participant pg_catalog
  Caller->>get_platform_health_stats: call RPC
  get_platform_health_stats->>is_super_admin_is_admin: check authorization
  is_super_admin_is_admin-->>get_platform_health_stats: allowed / Forbidden 42501
  get_platform_health_stats->>pg_catalog: query pg_stat_activity, pg_database_size, pg_class
  pg_catalog-->>get_platform_health_stats: counts and table sizes
  get_platform_health_stats-->>Caller: health stats table
Loading

Possibly related PRs

  • njrini99-code/helmv3#613: Adds the import_run_id column referenced by the type/CSV changes in this PR that remove upload_batch_id.
  • njrini99-code/helmv3#733: Also revokes execute grants on SECURITY DEFINER RPCs, directly related at the DB-auth/RPC gating level.

Suggested labels: security, database

🚥 Pre-merge checks | ✅ 10 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Title check ⚠️ Warning Title is relevant, but it does not follow the required Conventional Commits format or include a required scope. Rewrite it as a Conventional Commit with a required scope, e.g. fix(baseball): harden RPCs, repair CSV upload, add schema drift columns.
Conventional Commits ❓ Inconclusive HEAD subject matches the regex, but the PR title is not available in-repo, so full validation is impossible. Provide the PR title metadata (or a link/export of the PR header) so it can be checked against the same conventional-commit regex.
✅ Passed checks (10 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly matches the database hardening, baseball CSV repair, and deferred golf policy changes in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
No Service-Role In Client Bundles ✅ Passed No changed file outside allowed admin paths references SUPABASE_SERVICE_ROLE_KEY or builds a service-role Supabase client; only generic createClient calls appear in stats.ts.
Rls Coverage On New Tables ✅ Passed PASS: touched migrations only have functions/revokes or ALTER TABLE (20260708020000:10-97, 20260708021000:8-42, 20260708022000:10-17); no CREATE TABLE/RLS/policy.
Auth Check In Server Actions ✅ Passed PASS: The only changed action file, src/app/baseball/actions/stats.ts, exports thin wrappers; DB access sits in withBaseballAction, which auths via supabase.auth.getUser() before any queries.
Sport-Prefixed Table Names ✅ Passed PASS: The only changed runtime TS file queries only baseball-prefixed tables (baseball_*); the other TS changes are type/schema declarations.
No Destructive Writes ✅ Passed No DELETE→INSERT rebuild pattern appears in changed files; stats.ts:436-? only updates/inserts, and migrations 20260708020000:10-97, 20260708021000:8-42, 20260708022000:10-17 are definer/GRANT/ALTE...
No Edits To Historical Migrations ✅ Passed PR only changes three new migrations dated 20260708, all after the 20260527120000 baseline; no historical supabase/migrations files were modified.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/db-security-hardening-2026-07-08
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

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.

🔧 ast-grep (0.44.1)

ast-grep could not parse rule config: /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/lib/types/index.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


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

@coderabbitai coderabbitai Bot added database Schema, migrations, indexes, SQL security Auth, secrets, RLS, PII, webhooks labels Jul 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/types/index.ts`:
- Around line 179-187: The provenance fields in the type definitions currently
use bare string types instead of the file’s existing literal-union pattern, so
tighten `source_trust_level`, `source_visibility`, and `source_match_tier` in
`Baseball...` type declarations to dedicated union aliases declared near the
other enum-like types in this file. Add symbols such as
`BaseballSourceTrustLevel`, `BaseballSourceVisibility`, and a matching
`BaseballSourceMatchTier` if the value set is fixed, then update the interface
fields to use those aliases (with nullability preserved where needed). Verify
the allowed values against the DB constraints before naming the union members.
- Around line 179-187: `source_visibility` in `src/lib/types/index.ts` is typed
too strictly and should allow null to match the database schema. Update the
`baseball_player_stats` type definition so `source_visibility` is `string |
null`, alongside the existing nullable provenance fields like
`source_trust_level` and `import_run_id`; no other fields in this block need to
change.

In `@supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql`:
- Around line 29-42: The REVOKE statements in the migration only remove EXECUTE
from authenticated and anon, leaving the default PUBLIC path open on these
definer RPCs. Update each REVOKE for public.get_pending_task_reminders,
public.mark_task_reminder_sent, public.get_crm_click_destinations,
public.get_crm_template_performance, and public.refresh_crm_coach_engagement to
also revoke PUBLIC, while preserving access for the service_role/admin caller
used by the cron and CRM admin surfaces.
- Around line 8-24: Harden public.update_user_last_seen by tightening its
authorization check and grants. In the update_user_last_seen function, replace
the target_user_id comparison with IS DISTINCT FROM so NULL auth.uid() values
cannot bypass the forbidden branch, and keep the public.is_admin() exception
path intact. Also update the migration’s GRANT/REVOKE section to revoke EXECUTE
from PUBLIC in addition to anon and authenticated, matching the other SECURITY
DEFINER hardening patterns.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 451ccfe2-58ae-4638-978c-a291c7f5925e

📥 Commits

Reviewing files that changed from the base of the PR and between e63de60 and 15ceeb6.

⛔ Files ignored due to path filters (1)
  • src/lib/types/database.ts is excluded by !src/lib/types/database.ts
📒 Files selected for processing (6)
  • docs/audits/DB_FORENSIC_AUDIT_2026-07-08.md
  • src/app/baseball/actions/stats.ts
  • src/lib/types/index.ts
  • supabase/migrations/20260708020000_gate_admin_telemetry_rpcs_pii.sql
  • supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql
  • supabase/migrations/20260708022000_baseball_player_stats_real_stat_columns.sql
💤 Files with no reviewable changes (1)
  • src/app/baseball/actions/stats.ts

Comment thread src/lib/types/index.ts
Comment on lines +179 to +187
// Source-trust / import provenance (added by the source-trust + import-run
// migrations; import_run_id supersedes the removed legacy upload_batch_id).
source_trust_level: string | null;
source_visibility: string;
source_match_confidence: number | null;
source_match_tier: string | null;
source_external_id: string | null;
import_run_id: string | null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten source_trust_level / source_visibility / source_match_tier to literal unions instead of string.

This file already models every other enum-like DB column as a literal union (BaseballStatSource, BaseballUploadStatus, BaseballTrend, BaseballInsightStatus, etc.) declared right above this interface. The three new provenance fields regress that convention by using bare string, which loses compile-time protection against typos ('staff_only' vs 'staff_Only') and silently accepts any string a caller passes.

Suggested fix, matching the existing style in this file:

export type BaseballSourceTrustLevel =
  | 'official' | 'verified_vendor' | 'coach_reviewed' | 'player_submitted' | 'unverified' | 'inferred';
export type BaseballSourceVisibility = 'staff_only' | 'player_visible' | 'restricted';

then apply them to source_trust_level: BaseballSourceTrustLevel | null; and source_visibility: BaseballSourceVisibility; (and similarly for source_match_tier if its value set is fixed). Confirm the exact permitted values against the corresponding CHECK constraints before naming the union members.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/types/index.ts` around lines 179 - 187, The provenance fields in the
type definitions currently use bare string types instead of the file’s existing
literal-union pattern, so tighten `source_trust_level`, `source_visibility`, and
`source_match_tier` in `Baseball...` type declarations to dedicated union
aliases declared near the other enum-like types in this file. Add symbols such
as `BaseballSourceTrustLevel`, `BaseballSourceVisibility`, and a matching
`BaseballSourceMatchTier` if the value set is fixed, then update the interface
fields to use those aliases (with nullability preserved where needed). Verify
the allowed values against the DB constraints before naming the union members.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify nullability + defaults for the new provenance columns and confirm write-path coverage.
grep -n "source_visibility\|import_run_id\|source_trust_level" src/app/baseball/actions/stats.ts
rg -n "ADD COLUMN.*source_visibility|ADD COLUMN.*import_run_id" supabase/migrations

Repository: njrini99-code/helmv3

Length of output: 1726


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== stats.ts relevant insert/update path =="
sed -n '430,620p' src/app/baseball/actions/stats.ts

echo
echo "== migration for source_visibility =="
sed -n '1,140p' supabase/migrations/20260624000460_baseball_import_registry_load_bearing.sql

echo
echo "== migration for import_run_id on baseball_player_stats =="
sed -n '1,120p' supabase/migrations/20260701007000_baseball_player_stats_import_run_id.sql

echo
echo "== source_visibility / import_run_id in generated DB types =="
rg -n "baseball_player_stats.*source_visibility|baseball_player_stats.*import_run_id|source_visibility:|import_run_id:" src/lib/types/database.ts

Repository: njrini99-code/helmv3

Length of output: 16149


Make source_visibility nullable in src/lib/types/index.ts:179-187.
supabase/migrations/20260624000460_baseball_import_registry_load_bearing.sql:48-60 allows baseball_player_stats.source_visibility to be NULL and treats NULL as the legacy/manual value. This type should be string | null; import_run_id is already nullable and doesn’t need a change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/types/index.ts` around lines 179 - 187, `source_visibility` in
`src/lib/types/index.ts` is typed too strictly and should allow null to match
the database schema. Update the `baseball_player_stats` type definition so
`source_visibility` is `string | null`, alongside the existing nullable
provenance fields like `source_trust_level` and `import_run_id`; no other fields
in this block need to change.

Comment on lines +8 to +24
CREATE OR REPLACE FUNCTION public.update_user_last_seen(target_user_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
BEGIN
IF target_user_id <> auth.uid() AND NOT public.is_admin() THEN
RAISE EXCEPTION 'Forbidden' USING ERRCODE = '42501';
END IF;

UPDATE users
SET last_seen = NOW()
WHERE id = target_user_id
AND (last_seen IS NULL OR last_seen < NOW() - INTERVAL '5 minutes');
END;
$function$;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP "update_user_last_seen|FUNCTION public\.is_admin\b|GRANT EXECUTE .*update_user_last_seen" supabase/migrations

Repository: njrini99-code/helmv3

Length of output: 1279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql

printf '\n---\n'

rg -n "CREATE OR REPLACE FUNCTION public\.is_admin|CREATE OR REPLACE FUNCTION .*is_admin" supabase/migrations

Repository: njrini99-code/helmv3

Length of output: 2426


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'MIGRATION FILE:\n'
cat -n supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql | sed -n '1,120p'

printf '\nBASELINE is_admin DEF:\n'
sed -n '3853,3895p' supabase/migrations/20260527000000_prod_public_baseline.sql

printf '\nBASELINE update_user_last_seen GRANTS:\n'
sed -n '20905,20920p' supabase/migrations/20260527000000_prod_public_baseline.sql

Repository: njrini99-code/helmv3

Length of output: 4738


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "REVOKE EXECUTE ON FUNCTION .*FROM PUBLIC|ALTER DEFAULT PRIVILEGES.*FUNCTIONS|REVOKE .*EXECUTE.*FUNCTIONS.*PUBLIC" supabase/migrations

Repository: njrini99-code/helmv3

Length of output: 2326


Harden public.update_user_last_seen authz and grants.

  • supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql:15 should use IS DISTINCT FROM instead of <>; auth.uid() is NULL for anon/service_role, so the current guard can skip the exception.
  • supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql:29-42 should mirror the other hardening migrations and revoke EXECUTE from PUBLIC too, not just anon/authenticated.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql` around
lines 8 - 24, Harden public.update_user_last_seen by tightening its
authorization check and grants. In the update_user_last_seen function, replace
the target_user_id comparison with IS DISTINCT FROM so NULL auth.uid() values
cannot bypass the forbidden branch, and keep the public.is_admin() exception
path intact. Also update the migration’s GRANT/REVOKE section to revoke EXECUTE
from PUBLIC in addition to anon and authenticated, matching the other SECURITY
DEFINER hardening patterns.

Comment on lines +29 to +42
REVOKE EXECUTE ON FUNCTION public.get_pending_task_reminders() FROM authenticated, anon;
REVOKE EXECUTE ON FUNCTION public.mark_task_reminder_sent(uuid) FROM authenticated, anon;

-- (3) CRM analytics RPCs: zero app call sites, and every sibling CRM analytics
-- RPC is admin-gated. Revoke authenticated/anon EXECUTE (admin surfaces call
-- CRM analytics via the service_role admin client).
REVOKE EXECUTE ON FUNCTION public.get_crm_click_destinations(text, integer) FROM authenticated, anon;
REVOKE EXECUTE ON FUNCTION public.get_crm_template_performance(text) FROM authenticated, anon;

-- (4) refresh_crm_coach_engagement: only caller is the cron route, which uses
-- the service_role admin client (src/app/api/cron/refresh-engagement/route.ts
-- -> createAdminClient). Revoke authenticated to remove the shared-DB DoS
-- surface (a materialized-view refresh any user could otherwise trigger).
REVOKE EXECUTE ON FUNCTION public.refresh_crm_coach_engagement() FROM authenticated, anon;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
for fn in get_pending_task_reminders mark_task_reminder_sent get_crm_click_destinations get_crm_template_performance refresh_crm_coach_engagement; do
  echo "== $fn =="
  rg -nP "FUNCTION public\.$fn\b" supabase/migrations
done

Repository: njrini99-code/helmv3

Length of output: 1131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target migration =="
sed -n '1,140p' supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql

echo
echo "== search for PUBLIC revokes/default privileges =="
rg -n "REVOKE EXECUTE ON FUNCTION .* FROM PUBLIC|ALTER DEFAULT PRIVILEGES.*FUNCTIONS|ALTER DEFAULT PRIVILEGES.*EXECUTE" supabase/migrations

echo
echo "== search for the five RPC definitions and any explicit grants/revokes =="
rg -n "get_pending_task_reminders|mark_task_reminder_sent|get_crm_click_destinations|get_crm_template_performance|refresh_crm_coach_engagement" supabase/migrations

Repository: njrini99-code/helmv3

Length of output: 10100


Revoke PUBLIC too supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql:29-42 only removes authenticated/anon; the default PUBLIC EXECUTE path on these functions stays open. Add PUBLIC to each REVOKE (keep service_role if the cron/admin caller still needs it).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260708021000_gate_ungated_definer_rpcs.sql` around
lines 29 - 42, The REVOKE statements in the migration only remove EXECUTE from
authenticated and anon, leaving the default PUBLIC path open on these definer
RPCs. Update each REVOKE for public.get_pending_task_reminders,
public.mark_task_reminder_sent, public.get_crm_click_destinations,
public.get_crm_template_performance, and public.refresh_crm_coach_engagement to
also revoke PUBLIC, while preserving access for the service_role/admin caller
used by the cron and CRM admin surfaces.

@njrini99-code njrini99-code merged commit 3b3d59c into main Jul 9, 2026
42 checks passed
@njrini99-code njrini99-code deleted the fix/db-security-hardening-2026-07-08 branch July 9, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

database Schema, migrations, indexes, SQL security Auth, secrets, RLS, PII, webhooks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant