Summary
bin/compile_json.py's CATEGORIES_RE regex extracts the wrong substring for any lint whose SQL body contains an array[...] literal before the actual array['SECURITY'] as categories / array['PERFORMANCE'] as categories line. The regex is .search() with DOTALL and a non-greedy quantifier, so it matches from the first array[ in the file to the first subsequent ] as categories — which, for a handful of lints, is not the categories literal at all.
Confirmed against the published splinter.json manifest for releases 2026.07.0, 2026.08.0, and 2026.09.0 (unchanged across all three): the categories field is malformed for exactly these lints, because each has an earlier array[...] in its own body:
| Lint |
Level |
What comes before the real categories literal |
rls_policy_always_true (0024) |
WARN |
role_oids = array[0::oid] |
sensitive_columns_exposed (0023) |
ERROR |
unnest(array['password', ...]) (sensitive-column name list) |
public_bucket_allows_listing (0025) |
WARN |
array['public'::name, 'anon'::name, ...] |
The lint files themselves are correct and unchanged (rls_policy_always_true hasn't changed since April) — this is purely a manifest-generation bug.
Repro (3 lines)
git clone https://github.com/supabase/splinter && cd splinter
python3 bin/compile_json.py --output /tmp/s.json
jq '.lints[] | select(.name=="rls_policy_always_true") | .categories' /tmp/s.json
Expected: ["SECURITY"]. Actual: an array of SQL-fragment strings (the text between the first array[ and the first ] as categories), never ["SECURITY"] as a clean single-element array.
Real-world impact
We noticed on our own hosted project that the Management API (GET /v1/projects/{ref}/advisors/security — the same code path used by MCP get_advisors and the CLI's supabase db advisors --linked) never reports rls_policy_always_true findings, while:
- Supabase Studio's Security Advisor tab shows them correctly, and
- executing the lint's own SQL directly against the same database returns the expected rows.
Studio isn't affected because it filters lints by each result row's own, Postgres-evaluated categories column (see apps/studio/hooks/misc/useLints.ts: data.filter((lint) => lint.categories.includes('SECURITY'))) — that value is computed at query execution time and is never touched by the manifest bug. We can't inspect the Management API's closed-source implementation directly, but per #175's own description ("supabase/platform's advisor consumes this manifest instead of scraping splinter.sql"), a consumer that uses the manifest's categories field to decide which lint queries to even run for a category-scoped request would silently skip these three for any /advisors/security-style call — which matches the observed behavior exactly.
This may also be relevant to #181 (public_bucket_allows_listing disappearing from the hosted advisor) — that issue proposes a different mechanism (a transaction-local GUC not being set), but public_bucket_allows_listing is one of the three lints with a corrupted manifest categories field here, so both causes may be in play, or this one may fully explain that report's hosted-advisor symptom instead. Worth checking against this bug before assuming the GUC theory is the whole story.
Since sensitive_columns_exposed is an ERROR-level lint, any consumer that relies on the manifest for lint selection would be silently blind to it — not just missing a warning, but missing a data-exposure-severity finding.
Suggested fix
Anchor the regex to quoted category literals only, rather than matching greedily from the first array[, e.g.:
CATEGORIES_RE = re.compile(
r"array\[((?:\s*'[A-Z_]+'\s*,?)+)\]\s+as\s+categories", re.IGNORECASE
)
and/or add a CI assertion in this repo that every generated manifest entry's categories is a subset of {SECURITY, PERFORMANCE} (a malformed value should fail the build loudly rather than ship silently, which is what let this go unnoticed across three releases).
Happy to test a fix against the reproduction above.
Summary
bin/compile_json.py'sCATEGORIES_REregex extracts the wrong substring for any lint whose SQL body contains anarray[...]literal before the actualarray['SECURITY'] as categories/array['PERFORMANCE'] as categoriesline. The regex is.search()withDOTALLand a non-greedy quantifier, so it matches from the firstarray[in the file to the first subsequent] as categories— which, for a handful of lints, is not the categories literal at all.Confirmed against the published
splinter.jsonmanifest for releases 2026.07.0, 2026.08.0, and 2026.09.0 (unchanged across all three): thecategoriesfield is malformed for exactly these lints, because each has an earlierarray[...]in its own body:categoriesliteralrls_policy_always_true(0024)role_oids = array[0::oid]sensitive_columns_exposed(0023)unnest(array['password', ...])(sensitive-column name list)public_bucket_allows_listing(0025)array['public'::name, 'anon'::name, ...]The lint files themselves are correct and unchanged (
rls_policy_always_truehasn't changed since April) — this is purely a manifest-generation bug.Repro (3 lines)
Expected:
["SECURITY"]. Actual: an array of SQL-fragment strings (the text between the firstarray[and the first] as categories), never["SECURITY"]as a clean single-element array.Real-world impact
We noticed on our own hosted project that the Management API (
GET /v1/projects/{ref}/advisors/security— the same code path used by MCPget_advisorsand the CLI'ssupabase db advisors --linked) never reportsrls_policy_always_truefindings, while:Studio isn't affected because it filters lints by each result row's own, Postgres-evaluated
categoriescolumn (seeapps/studio/hooks/misc/useLints.ts:data.filter((lint) => lint.categories.includes('SECURITY'))) — that value is computed at query execution time and is never touched by the manifest bug. We can't inspect the Management API's closed-source implementation directly, but per #175's own description ("supabase/platform's advisor consumes this manifest instead of scraping splinter.sql"), a consumer that uses the manifest'scategoriesfield to decide which lint queries to even run for a category-scoped request would silently skip these three for any/advisors/security-style call — which matches the observed behavior exactly.This may also be relevant to #181 (
public_bucket_allows_listingdisappearing from the hosted advisor) — that issue proposes a different mechanism (a transaction-local GUC not being set), butpublic_bucket_allows_listingis one of the three lints with a corrupted manifestcategoriesfield here, so both causes may be in play, or this one may fully explain that report's hosted-advisor symptom instead. Worth checking against this bug before assuming the GUC theory is the whole story.Since
sensitive_columns_exposedis an ERROR-level lint, any consumer that relies on the manifest for lint selection would be silently blind to it — not just missing a warning, but missing a data-exposure-severity finding.Suggested fix
Anchor the regex to quoted category literals only, rather than matching greedily from the first
array[, e.g.:and/or add a CI assertion in this repo that every generated manifest entry's
categoriesis a subset of{SECURITY, PERFORMANCE}(a malformed value should fail the build loudly rather than ship silently, which is what let this go unnoticed across three releases).Happy to test a fix against the reproduction above.