fix(security): remove explain_generic SECURITY DEFINER helper (RCE)
Summary
postgres_ai.explain_generic was a SECURITY DEFINER helper (owned by the provisioning superuser/rds_superuser) that ran EXPLAIN on caller-supplied SQL. This is a confirmed RCE / data-exfiltration vector for the low-privilege monitoring role — see confidential issue #387 (closed) (empirically reproduced: a folded IMMUTABLE function leaked a planted secret through plan output, and explain_generic('select ssn from customer_secrets') returned a full plan while a direct SELECT was denied).
Why it's exploitable
EXPLAIN without ANALYZE still plans, and the planner evaluates functions at plan time:
eval_const_expressionsfoldsIMMUTABLEfunctions with constant args → executes them.- selectivity estimation evaluates
STABLEfunctions inWHERE(e.g.... WHERE x = evil()).
Both run in the definer's superuser context. The semicolon guard is no obstacle (the function-call path needs no semicolon), and this cannot be closed by text validation.
Why removal (not SECURITY INVOKER)
There is no Postgres privilege that lets a role plan/EXPLAIN a query without holding SELECT on the referenced tables — which is exactly why the helper needed DEFINER and why INVOKER fails outright. Any in-place "secure" version must either hold broad data-SELECT (breaks the metadata-only promise) or require explicit per-relation grants (an opt-in product change). Removing the helper kills the RCE now and preserves the metadata-only contract; the opt-in-grant design can be revisited separately.
This supersedes the SECURITY INVOKER attempt in !541.
Changes
cli/sql/06.helpers.sql— dropexplain_generic+ itsgrant.table_describeretained (bounded catalog reader, runs no caller SQL — handled separately).cli/sql/uninit/01.helpers.sql— already dropsexplain_generic, so re-prepare / uninit removes the function from existing installs (no migration needed CLI-side).cli/lib/init.ts,cli/lib/supabase.ts— remove theEXECUTE on explain_generichealth-checks (would otherwise fail verification post-removal). Otherhas_function_privilegechecks (table_describe, RDS tools, optional perms) are unchanged.cli/test/init.integration.test.ts— red→green flip: assert prepare-db does not createexplain_generic.
Follow-ups (out of scope for this MR)
- platform-all:
db/functions/supabase_prepare_db.sqlstill emits the function — needs its own removal + migration, and !541 repurposed there. table_describedefiner-enumeration smell (not RCE) — handle separately.- Keep #387 (closed) confidential until both CLI and platform-all removals ship.
Closes #387 (closed)