Update lints for self-hosted (#40043)

* Update lints for self-hosted

* update comment

* Update apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx

Co-authored-by: Ali Waseem <waseema393@gmail.com>

* revert

* Fix

* update comment

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
This commit is contained in:
Joshen LimandAli Waseem authored and GitHub committed 2025-11-01 01:28:05 +08:00
1 parent dad5f01140
commit 0d44090aac
2 files changed
+286 -28

No files matched your search

+273 -27
View File
@@ -1,28 +1,41 @@
import { paths } from 'api-types'
import { DOCS_URL } from 'lib/constants'
import { executeQuery } from './query'
import { paths } from 'api-types'
import { WrappedResult } from 'lib/api/self-hosted/types'
interface GetLintsOptions {
headers?: HeadersInit
exposedSchemas?: string
}
export async function getLints({ headers }: GetLintsOptions) {
return await executeQuery<ResponseData[number]>({ query: enrichQuery(LINT_SQL), headers })
export async function getLints({ headers, exposedSchemas }: GetLintsOptions) {
return await executeQuery<ResponseData[number]>({
query: enrichLintsQuery(LINT_SQL, exposedSchemas),
headers,
})
}
export type ResponseData =
paths['/platform/projects/{ref}/run-lints']['get']['responses']['200']['content']['application/json']
export const enrichQuery = (query: string) => `
export const enrichLintsQuery = (query: string, exposedSchemas?: string) => {
const literalSchemas = exposedSchemas ? `'${exposedSchemas}'` : ''
return `
set pg_stat_statements.track = none;
set local pgrst.db_schemas = ${literalSchemas};
-- source: dashboard
-- user: ${'self host'}
-- date: ${new Date().toISOString()}
${query}
`
}
// Pulled from https://github.com/supabase/splinter/blob/main/splinter.sql
/**
* Pulled from https://github.com/supabase/splinter/blob/main/splinter.sql
* Things to do after copy pasting from splinter.sql
* - Replace all "\`%s\`" with backquotes to escape the tick character ("\`%s\`")
* - Replace docs url with DOCS_URL (${DOCS_URL})
*/
const LINT_SQL = /* SQL */ `set local search_path = '';
(
@@ -83,14 +96,14 @@ from
foreign_keys fk
left join index_ idx
on fk.table_oid = idx.table_oid
and fk.col_attnums = idx.col_attnums
and fk.col_attnums = idx.col_attnums[1:array_length(fk.col_attnums, 1)]
left join pg_catalog.pg_depend dep
on idx.table_oid = dep.objid
and dep.deptype = 'e'
where
idx.index_ is null
and fk.schema_name not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and dep.objid is null -- exclude tables owned by extensions
order by
@@ -222,9 +235,9 @@ select
'WARN' as level,
'EXTERNAL' as facing,
array['PERFORMANCE'] as categories,
'Detects if calls to \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row' as description,
'Detects if calls to \`current_setting()\` and \`auth.<function>()\` in RLS policies are being unnecessarily re-evaluated for each row' as description,
format(
'Table \`%s.%s\` has a row level security policy \`%s\` that re-evaluates an auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](${DOCS_URL}/guides/database/postgres/row-level-security#call-functions-with-select) for more info.',
'Table \`%s.%s\` has a row level security policy \`%s\` that re-evaluates current_setting() or auth.<function>() for each row. This produces suboptimal query performance at scale. Resolve the issue by replacing \`auth.<function>()\` with \`(select auth.<function>())\`. See [docs](${DOCS_URL}/guides/database/postgres/row-level-security#call-functions-with-select) for more info.',
schema_name,
table_name,
policy_name
@@ -242,7 +255,7 @@ where
is_rls_active
-- NOTE: does not include realtime in support of monitoring policies on realtime.messages
and schema_name not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and (
-- Example: auth.uid()
@@ -262,6 +275,10 @@ where
qual like '%auth.email()%'
and lower(qual) not like '%select auth.email()%'
)
or (
qual like '%current\_setting(%)%'
and lower(qual) not like '%select current\_setting(%)%'
)
or (
with_check like '%auth.uid()%'
and lower(with_check) not like '%select auth.uid()%'
@@ -278,6 +295,10 @@ where
with_check like '%auth.email()%'
and lower(with_check) not like '%select auth.email()%'
)
or (
with_check like '%current\_setting(%)%'
and lower(with_check) not like '%select current\_setting(%)%'
)
))
union all
(
@@ -316,7 +337,7 @@ from
where
pgc.relkind = 'r' -- regular tables
and pgns.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and dep.objid is null -- exclude tables owned by extensions
group by
@@ -366,7 +387,7 @@ where
and not pi.indisprimary
and dep.objid is null -- exclude tables owned by extensions
and psui.schemaname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
))
union all
(
@@ -428,7 +449,7 @@ where
c.relkind = 'r' -- regular tables
and p.polpermissive -- policy is permissive
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and r.rolname not like 'pg_%'
and r.rolname not like 'supabase%admin'
@@ -479,7 +500,7 @@ from
where
c.relkind = 'r' -- regular tables
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
-- RLS is disabled
and not c.relrowsecurity
@@ -524,7 +545,7 @@ from
where
c.relkind = 'r' -- regular tables
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
-- RLS is enabled
and c.relrowsecurity
@@ -578,7 +599,7 @@ from
where
c.relkind in ('r', 'm') -- tables and materialized views
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and dep.objid is null -- exclude tables owned by extensions
group by
@@ -629,7 +650,7 @@ where
and substring(pg_catalog.version() from 'PostgreSQL ([0-9]+)') >= '15' -- security invoker was added in pg15
and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and dep.objid is null -- exclude views owned by extensions
and not (
@@ -649,7 +670,7 @@ select
'WARN' as level,
'EXTERNAL' as facing,
array['SECURITY'] as categories,
'Detects functions where the search_path parameter is not set to an empty string.' as description,
'Detects functions where the search_path parameter is not set.' as description,
format(
'Function \`%s.%s\` has a role mutable search_path',
n.nspname,
@@ -676,11 +697,15 @@ from
and dep.deptype = 'e'
where
n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and dep.objid is null -- exclude functions owned by extensions
-- Search path not set to ''
and not coalesce(p.proconfig, '{}') && array['search_path=""'])
-- Search path not set
and not exists (
select 1
from unnest(coalesce(p.proconfig, '{}')) as config
where config like 'search_path=%'
))
union all
(
select
@@ -720,7 +745,7 @@ where
)
and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
))
union all
(
@@ -800,7 +825,7 @@ from
policies
where
schema_name not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and (
-- Example: auth.jwt() -> 'user_metadata'
@@ -851,7 +876,7 @@ where
)
and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and dep.objid is null)
union all
@@ -894,7 +919,7 @@ where
)
and n.nspname = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ',')))))
and n.nspname not in (
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
'_timescaledb_cache', '_timescaledb_catalog', '_timescaledb_config', '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgmq', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault'
)
and dep.objid is null)
union all
@@ -939,4 +964,225 @@ from
where
tn.nspname = 'pg_catalog'
and t.typname in ('regcollation', 'regconfig', 'regdictionary', 'regnamespace', 'regoper', 'regoperator', 'regproc', 'regprocedure')
and n.nspname not in ('pg_catalog', 'information_schema', 'pgsodium'))`.trim()
and n.nspname not in ('pg_catalog', 'information_schema', 'pgsodium'))
union all
(
select
'insecure_queue_exposed_in_api' as name,
'Insecure Queue Exposed in API' as title,
'ERROR' as level,
'EXTERNAL' as facing,
array['SECURITY'] as categories,
'Detects cases where an insecure Queue is exposed over Data APIs' as description,
format(
'Table \`%s.%s\` is public, but RLS has not been enabled.',
n.nspname,
c.relname
) as detail,
'${DOCS_URL}/guides/database/database-linter?lint=0019_insecure_queue_exposed_in_api' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', c.relname,
'type', 'table'
) as metadata,
format(
'rls_disabled_in_public_%s_%s',
n.nspname,
c.relname
) as cache_key
from
pg_catalog.pg_class c
join pg_catalog.pg_namespace n
on c.relnamespace = n.oid
where
c.relkind in ('r', 'I') -- regular or partitioned tables
and not c.relrowsecurity -- RLS is disabled
and (
pg_catalog.has_table_privilege('anon', c.oid, 'SELECT')
or pg_catalog.has_table_privilege('authenticated', c.oid, 'SELECT')
)
and n.nspname = 'pgmq' -- tables in the pgmq schema
and c.relname like 'q_%' -- only queue tables
-- Constant requirements
and 'pgmq_public' = any(array(select trim(unnest(string_to_array(current_setting('pgrst.db_schemas', 't'), ','))))))
union all
(
with constants as (
select current_setting('block_size')::numeric as bs, 23 as hdr, 4 as ma
),
bloat_info as (
select
ma,
bs,
schemaname,
tablename,
(datawidth + (hdr + ma - (case when hdr % ma = 0 then ma else hdr % ma end)))::numeric as datahdr,
(maxfracsum * (nullhdr + ma - (case when nullhdr % ma = 0 then ma else nullhdr % ma end))) as nullhdr2
from (
select
schemaname,
tablename,
hdr,
ma,
bs,
sum((1 - null_frac) * avg_width) as datawidth,
max(null_frac) as maxfracsum,
hdr + (
select 1 + count(*) / 8
from pg_stats s2
where
null_frac <> 0
and s2.schemaname = s.schemaname
and s2.tablename = s.tablename
) as nullhdr
from pg_stats s, constants
group by 1, 2, 3, 4, 5
) as foo
),
table_bloat as (
select
schemaname,
tablename,
cc.relpages,
bs,
ceil((cc.reltuples * ((datahdr + ma -
(case when datahdr % ma = 0 then ma else datahdr % ma end)) + nullhdr2 + 4)) / (bs - 20::float)) as otta
from
bloat_info
join pg_class cc
on cc.relname = bloat_info.tablename
join pg_namespace nn
on cc.relnamespace = nn.oid
and nn.nspname = bloat_info.schemaname
and nn.nspname <> 'information_schema'
where
cc.relkind = 'r'
and cc.relam = (select oid from pg_am where amname = 'heap')
),
bloat_data as (
select
'table' as type,
schemaname,
tablename as object_name,
round(case when otta = 0 then 0.0 else table_bloat.relpages / otta::numeric end, 1) as bloat,
case when relpages < otta then 0 else (bs * (table_bloat.relpages - otta)::bigint)::bigint end as raw_waste
from
table_bloat
)
select
'table_bloat' as name,
'Table Bloat' as title,
'INFO' as level,
'EXTERNAL' as facing,
array['PERFORMANCE'] as categories,
'Detects if a table has excess bloat and may benefit from maintenance operations like vacuum full or cluster.' as description,
format(
'Table \`%s\`.\`%s\` has excessive bloat',
bloat_data.schemaname,
bloat_data.object_name
) as detail,
'Consider running vacuum full (WARNING: incurs downtime) and tweaking autovacuum settings to reduce bloat.' as remediation,
jsonb_build_object(
'schema', bloat_data.schemaname,
'name', bloat_data.object_name,
'type', bloat_data.type
) as metadata,
format(
'table_bloat_%s_%s',
bloat_data.schemaname,
bloat_data.object_name
) as cache_key
from
bloat_data
where
bloat > 70.0
and raw_waste > (20 * 1024 * 1024) -- filter for waste > 200 MB
order by
schemaname,
object_name)
union all
(
select
'fkey_to_auth_unique' as name,
'Foreign Key to Auth Unique Constraint' as title,
'ERROR' as level,
'EXTERNAL' as facing,
array['SECURITY'] as categories,
'Detects user defined foreign keys to unique constraints in the auth schema.' as description,
format(
'Table \`%s\`.\`%s\` has a foreign key \`%s\` referencing an auth unique constraint',
n.nspname, -- referencing schema
c_rel.relname, -- referencing table
c.conname -- fkey name
) as detail,
'Drop the foreign key constraint that references the auth schema.' as remediation,
jsonb_build_object(
'schema', n.nspname,
'name', c_rel.relname,
'foreign_key', c.conname
) as metadata,
format(
'fkey_to_auth_unique_%s_%s_%s',
n.nspname, -- referencing schema
c_rel.relname, -- referencing table
c.conname
) as cache_key
from
pg_catalog.pg_constraint c
join pg_catalog.pg_class c_rel
on c.conrelid = c_rel.oid
join pg_catalog.pg_namespace n
on c_rel.relnamespace = n.oid
join pg_catalog.pg_class ref_rel
on c.confrelid = ref_rel.oid
join pg_catalog.pg_namespace cn
on ref_rel.relnamespace = cn.oid
join pg_catalog.pg_index i
on c.conindid = i.indexrelid
where c.contype = 'f'
and cn.nspname = 'auth'
and i.indisunique
and not i.indisprimary)
union all
(
select
'extension_versions_outdated' as name,
'Extension Versions Outdated' as title,
'WARN' as level,
'EXTERNAL' as facing,
array['SECURITY'] as categories,
'Detects extensions that are not using the default (recommended) version.' as description,
format(
'Extension \`%s\` is using version \`%s\` but version \`%s\` is available. Using outdated extension versions may expose the database to security vulnerabilities.',
ext.name,
ext.installed_version,
ext.default_version
) as detail,
'${DOCS_URL}/guides/database/database-linter?lint=0022_extension_versions_outdated' as remediation,
jsonb_build_object(
'extension_name', ext.name,
'installed_version', ext.installed_version,
'default_version', ext.default_version
) as metadata,
format(
'extension_versions_outdated_%s_%s',
ext.name,
ext.installed_version
) as cache_key
from
pg_catalog.pg_available_extensions ext
join
-- ignore versions not in pg_available_extension_versions
-- e.g. residue of pg_upgrade
pg_catalog.pg_available_extension_versions extv
on extv.name = ext.name and extv.installed
where
ext.installed_version is not null
and ext.default_version is not null
and ext.installed_version != ext.default_version
order by
ext.name)`.trim()
@@ -11,7 +11,19 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
switch (method) {
case 'GET':
const { data, error } = await getLints({ headers: constructHeaders(req.headers) })
/**
* [Joshen] JFYI technically the exposed schemas is being set here via docker-compose.yml
* https://github.com/supabase/supabase/blob/master/docker/docker-compose.yml#L183
* https://github.com/supabase/supabase/blob/474a78721e510301d15ca9dbd41f05ce10fa29e5/docker/.env.example#L55
*
* But i noticed that the local API route on config/postgrest.ts has currently hardcoded db_schema to `public, storage`
* As such, this is only just a temporary patch here that we're hardcoding the exposed schemas but we will need to figure
* out how to get the dashboard to retrieve the values from docker-compose
*/
const { data, error } = await getLints({
headers: constructHeaders(req.headers),
exposedSchemas: 'public, storage',
})
if (error) {
return res.status(400).json(error)