Make the findings come to you: run the advisors on a schedule.
The Management API exposes the same checks. GET
/v1/projects/{ref}/advisors/security returns an object with a
lints array, and each lint has a name, a
level, categories, a detail and a
remediation. Supabase marks the endpoint experimental ("subject to
change or removal"), and it needs the database:read OAuth scope. The
same API reports project state (GET /v1/projects/{ref}, where the
status field is ACTIVE_HEALTHY when all is well and
INACTIVE, PAUSING, RESTORING or one of eleven
other values when it is not) and per-service health (GET
/v1/projects/{ref}/health). Put the three together in one script that exits
non-zero when a human is needed, and run it from a scheduler you control.
#!/bin/sh
# advisors-check.sh - exit 1 if the project is not healthy or the
# Security Advisor reports an ERROR or WARN finding not on the allowlist.
# SUPABASE_ACCESS_TOKEN is a Management API token for your account:
# keep it in the scheduler's secret store, never in the repo or frontend.
REF="your-project-ref"
API="https://api.supabase.com/v1/projects/$REF"
AUTH="Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
fail=0
# 1. project state: paused, restoring, resizing all need a human
status=$(curl -sf -H "$AUTH" "$API" | jq -r '.status')
[ "$status" = "ACTIVE_HEALTHY" ] || { echo "PROJECT: $status"; fail=1; }
# 2. services: anything not ACTIVE_HEALTHY
health=$(curl -sf -H "$AUTH" "$API/health?services=auth,db,rest,storage") \
|| { echo "HEALTH: call failed, status unknown"; fail=1; }
echo "$health" \
| jq -r '.[] | select(.status != "ACTIVE_HEALTHY") | "SERVICE: " + .name' \
| grep . && fail=1
# 3. security advisor: ERROR and WARN, minus reviewed exceptions
curl -sf -H "$AUTH" "$API/advisors/security" -o security.json \
|| { echo "ADVISOR: call failed, status unknown"; exit 1; }
jq -r '.lints[] | select(.level != "INFO")
| [.level, .name, (.metadata.schema // ""), (.metadata.name // "")]
| join(" ")' security.json \
| grep -v -x -F -f allowlist.txt > findings.txt
[ -s findings.txt ] && { cat findings.txt; fail=1; }
exit $fail
Three choices in that script are deliberate. A failed API call is a failure, not a
pass: if the advisor cannot be reached, the script says so and exits, which is the
same healthy-versus-unchecked distinction Supabase built into the Health checks.
allowlist.txt holds findings you have reviewed and accepted, one line
each, exactly as the script prints them, so a known exception does not page anyone
every day while a new one does; create it empty on day one. And it prints check
names and object names only, never rows, so the log of a monitoring job never
becomes a copy of the data it is protecting.
Run it daily from a scheduled CI job, and after every migration that reaches
production, because an agent's new table is the moment 0013 goes from clean to
ERROR. For the Health checks, call POST
https://api.supabase.com/v2/projects/{ref}/advisors/run with the same bearer
token. The changelog shows the call but not the response shape, so start with the
one thing you can match without parsing: alert if the body contains
_error_rate_high or advisor_check_unavailable, and refine
once you have seen real output from your own project. The Management API token acts
on your account, not on one table, so treat it like the secret key: a scheduler
secret, never a chat message to an agent.
A check you can run without any API
The advisor is Supabase reading your catalogue. You can read it yourself, in the SQL
editor or in CI against a branch, and get the 0013 answer from Postgres directly:
select n.nspname, c.relname from pg_class c join pg_namespace n on n.oid =
c.relnamespace where n.nspname = 'public' and c.relkind in ('r', 'p') and not
c.relrowsecurity; Any row it returns is a table the Data API can reach with
RLS off. For 0024 in its simplest form, select tablename, policyname, cmd,
qual, with_check from pg_policies where schemaname in ('public', 'storage') and
(qual = 'true' or with_check = 'true');