@soulbatical/tetra-core 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generators/rls-auditor.d.ts +39 -0
- package/dist/generators/rls-auditor.d.ts.map +1 -0
- package/dist/generators/rls-auditor.js +505 -0
- package/dist/generators/rls-auditor.js.map +1 -0
- package/dist/generators/rls-checker.d.ts +94 -0
- package/dist/generators/rls-checker.d.ts.map +1 -0
- package/dist/generators/rls-checker.js +215 -0
- package/dist/generators/rls-checker.js.map +1 -0
- package/dist/generators/rls-generator.d.ts +77 -0
- package/dist/generators/rls-generator.d.ts.map +1 -0
- package/dist/generators/rls-generator.js +402 -0
- package/dist/generators/rls-generator.js.map +1 -0
- package/dist/generators/rpc/detail-rpc-generator.d.ts +58 -0
- package/dist/generators/rpc/detail-rpc-generator.d.ts.map +1 -0
- package/dist/generators/rpc/detail-rpc-generator.js +163 -0
- package/dist/generators/rpc/detail-rpc-generator.js.map +1 -0
- package/dist/generators/rpc/index.d.ts +24 -0
- package/dist/generators/rpc/index.d.ts.map +1 -0
- package/dist/generators/rpc/index.js +20 -0
- package/dist/generators/rpc/index.js.map +1 -0
- package/dist/generators/rpc/rpc-generator.d.ts +150 -0
- package/dist/generators/rpc/rpc-generator.d.ts.map +1 -0
- package/dist/generators/rpc/rpc-generator.js +743 -0
- package/dist/generators/rpc/rpc-generator.js.map +1 -0
- package/dist/generators/rpc/templates/array.d.ts +29 -0
- package/dist/generators/rpc/templates/array.d.ts.map +1 -0
- package/dist/generators/rpc/templates/array.js +40 -0
- package/dist/generators/rpc/templates/array.js.map +1 -0
- package/dist/generators/rpc/templates/auth.d.ts +85 -0
- package/dist/generators/rpc/templates/auth.d.ts.map +1 -0
- package/dist/generators/rpc/templates/auth.js +233 -0
- package/dist/generators/rpc/templates/auth.js.map +1 -0
- package/dist/generators/rpc/templates/column.d.ts +39 -0
- package/dist/generators/rpc/templates/column.d.ts.map +1 -0
- package/dist/generators/rpc/templates/column.js +97 -0
- package/dist/generators/rpc/templates/column.js.map +1 -0
- package/dist/generators/rpc/templates/enum.d.ts +33 -0
- package/dist/generators/rpc/templates/enum.d.ts.map +1 -0
- package/dist/generators/rpc/templates/enum.js +93 -0
- package/dist/generators/rpc/templates/enum.js.map +1 -0
- package/dist/generators/rpc/templates/nullable.d.ts +31 -0
- package/dist/generators/rpc/templates/nullable.d.ts.map +1 -0
- package/dist/generators/rpc/templates/nullable.js +50 -0
- package/dist/generators/rpc/templates/nullable.js.map +1 -0
- package/dist/generators/rpc/templates/related.d.ts +47 -0
- package/dist/generators/rpc/templates/related.d.ts.map +1 -0
- package/dist/generators/rpc/templates/related.js +182 -0
- package/dist/generators/rpc/templates/related.js.map +1 -0
- package/dist/generators/rpc/templates/search.d.ts +42 -0
- package/dist/generators/rpc/templates/search.d.ts.map +1 -0
- package/dist/generators/rpc/templates/search.js +81 -0
- package/dist/generators/rpc/templates/search.js.map +1 -0
- package/dist/generators/rpc/templates/time.d.ts +44 -0
- package/dist/generators/rpc/templates/time.d.ts.map +1 -0
- package/dist/generators/rpc/templates/time.js +143 -0
- package/dist/generators/rpc/templates/time.js.map +1 -0
- package/dist/generators/rpc/utils.d.ts +58 -0
- package/dist/generators/rpc/utils.d.ts.map +1 -0
- package/dist/generators/rpc/utils.js +92 -0
- package/dist/generators/rpc/utils.js.map +1 -0
- package/dist/generators/rpc/validator.d.ts +21 -0
- package/dist/generators/rpc/validator.d.ts.map +1 -0
- package/dist/generators/rpc/validator.js +398 -0
- package/dist/generators/rpc/validator.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/shared/auth/index.d.ts +1 -1
- package/dist/shared/auth/index.d.ts.map +1 -1
- package/dist/shared/auth/routes.d.ts +4 -1
- package/dist/shared/auth/routes.d.ts.map +1 -1
- package/dist/shared/auth/routes.js +83 -1
- package/dist/shared/auth/routes.js.map +1 -1
- package/dist/shared/auth/types.d.ts +24 -0
- package/dist/shared/auth/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tetra RLS Auditor v2.0
|
|
3
|
+
*
|
|
4
|
+
* Generates SQL queries that audit RLS compliance against Tetra standards.
|
|
5
|
+
* Run these queries on any Supabase project to find violations.
|
|
6
|
+
*
|
|
7
|
+
* Categories:
|
|
8
|
+
* A. Foundation — RLS enabled, FORCE RLS, policies exist
|
|
9
|
+
* B. Policy quality — roles, expressions, completeness
|
|
10
|
+
* C. Auth functions — exist, correct signature, SECURITY DEFINER
|
|
11
|
+
* D. RLS bypass routes — functions, views, grants that skip RLS
|
|
12
|
+
* E. Data exposure — Realtime, storage, schema leaks
|
|
13
|
+
* F. Migration verification — Tetra standards applied correctly
|
|
14
|
+
*
|
|
15
|
+
* Total: 22 checks (was 10 in v1.0)
|
|
16
|
+
*/
|
|
17
|
+
export interface AuditCheck {
|
|
18
|
+
/** Short identifier */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Human-readable name */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Category for grouping */
|
|
23
|
+
category: 'foundation' | 'policy' | 'auth' | 'bypass' | 'exposure' | 'migration';
|
|
24
|
+
/** Severity: error = must fix, warn = should fix, info = review */
|
|
25
|
+
severity: 'error' | 'warn' | 'info';
|
|
26
|
+
/** SQL query that returns violations (empty result = pass) */
|
|
27
|
+
sql: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get all audit checks.
|
|
31
|
+
* Run each check's SQL on a Supabase project. Non-empty results are violations.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getAuditChecks(): AuditCheck[];
|
|
34
|
+
/**
|
|
35
|
+
* Generate a single SQL query that runs ALL audit checks and returns a summary.
|
|
36
|
+
* Returns one row per violation with check_id, severity, category, table_name, and issue.
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateAuditSQL(): string;
|
|
39
|
+
//# sourceMappingURL=rls-auditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rls-auditor.d.ts","sourceRoot":"","sources":["../../src/generators/rls-auditor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,UAAU;IACzB,uBAAuB;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,QAAQ,EAAE,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAC;IACjF,mEAAmE;IACnE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACpC,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,UAAU,EAAE,CAic7C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CA4CzC"}
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tetra RLS Auditor v2.0
|
|
3
|
+
*
|
|
4
|
+
* Generates SQL queries that audit RLS compliance against Tetra standards.
|
|
5
|
+
* Run these queries on any Supabase project to find violations.
|
|
6
|
+
*
|
|
7
|
+
* Categories:
|
|
8
|
+
* A. Foundation — RLS enabled, FORCE RLS, policies exist
|
|
9
|
+
* B. Policy quality — roles, expressions, completeness
|
|
10
|
+
* C. Auth functions — exist, correct signature, SECURITY DEFINER
|
|
11
|
+
* D. RLS bypass routes — functions, views, grants that skip RLS
|
|
12
|
+
* E. Data exposure — Realtime, storage, schema leaks
|
|
13
|
+
* F. Migration verification — Tetra standards applied correctly
|
|
14
|
+
*
|
|
15
|
+
* Total: 22 checks (was 10 in v1.0)
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Get all audit checks.
|
|
19
|
+
* Run each check's SQL on a Supabase project. Non-empty results are violations.
|
|
20
|
+
*/
|
|
21
|
+
export function getAuditChecks() {
|
|
22
|
+
return [
|
|
23
|
+
// ═══════════════════════════════════════════════════════════
|
|
24
|
+
// A. FOUNDATION — RLS enabled, force, policies exist
|
|
25
|
+
// ═══════════════════════════════════════════════════════════
|
|
26
|
+
{
|
|
27
|
+
id: 'no-rls',
|
|
28
|
+
name: 'Tables without RLS enabled',
|
|
29
|
+
category: 'foundation',
|
|
30
|
+
severity: 'error',
|
|
31
|
+
sql: `
|
|
32
|
+
SELECT c.relname as table_name, 'RLS not enabled' as issue
|
|
33
|
+
FROM pg_class c
|
|
34
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
35
|
+
WHERE n.nspname = 'public'
|
|
36
|
+
AND c.relkind = 'r'
|
|
37
|
+
AND c.relrowsecurity = false
|
|
38
|
+
AND c.relname NOT LIKE 'pg_%'
|
|
39
|
+
AND c.relname NOT LIKE '_supabase%'
|
|
40
|
+
AND c.relname NOT LIKE '_prisma%'
|
|
41
|
+
ORDER BY c.relname;`,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'no-force-rls',
|
|
45
|
+
name: 'Tables with RLS but without FORCE RLS (table owner bypasses)',
|
|
46
|
+
category: 'foundation',
|
|
47
|
+
severity: 'error',
|
|
48
|
+
sql: `
|
|
49
|
+
SELECT c.relname as table_name, 'FORCE RLS not enabled — table owner bypasses all policies' as issue
|
|
50
|
+
FROM pg_class c
|
|
51
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
52
|
+
WHERE n.nspname = 'public'
|
|
53
|
+
AND c.relkind = 'r'
|
|
54
|
+
AND c.relrowsecurity = true
|
|
55
|
+
AND c.relforcerowsecurity = false
|
|
56
|
+
AND c.relname NOT LIKE 'pg_%'
|
|
57
|
+
AND c.relname NOT LIKE '_supabase%'
|
|
58
|
+
ORDER BY c.relname;`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'rls-no-policies',
|
|
62
|
+
name: 'Tables with RLS enabled but no policies (blocks ALL access)',
|
|
63
|
+
category: 'foundation',
|
|
64
|
+
severity: 'error',
|
|
65
|
+
sql: `
|
|
66
|
+
SELECT c.relname as table_name, 'RLS enabled but zero policies — all access blocked including service' as issue
|
|
67
|
+
FROM pg_class c
|
|
68
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
69
|
+
WHERE n.nspname = 'public'
|
|
70
|
+
AND c.relkind = 'r'
|
|
71
|
+
AND c.relrowsecurity = true
|
|
72
|
+
AND c.relname NOT IN (SELECT DISTINCT tablename FROM pg_policies WHERE schemaname = 'public')
|
|
73
|
+
AND c.relname NOT LIKE 'pg_%'
|
|
74
|
+
AND c.relname NOT LIKE '_supabase%'
|
|
75
|
+
ORDER BY c.relname;`,
|
|
76
|
+
},
|
|
77
|
+
// ═══════════════════════════════════════════════════════════
|
|
78
|
+
// B. POLICY QUALITY — roles, expressions, completeness
|
|
79
|
+
// ═══════════════════════════════════════════════════════════
|
|
80
|
+
{
|
|
81
|
+
id: 'public-role-mutate',
|
|
82
|
+
name: 'Mutation policies allowing role public',
|
|
83
|
+
category: 'policy',
|
|
84
|
+
severity: 'error',
|
|
85
|
+
sql: `
|
|
86
|
+
SELECT tablename as table_name, policyname || ' (' || cmd || ')' as issue
|
|
87
|
+
FROM pg_policies
|
|
88
|
+
WHERE schemaname = 'public'
|
|
89
|
+
AND roles::text = '{public}'
|
|
90
|
+
AND cmd != 'SELECT'
|
|
91
|
+
AND qual != 'false'
|
|
92
|
+
ORDER BY tablename, cmd;`,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'always-true-mutate',
|
|
96
|
+
name: 'USING(true) or WITH CHECK(true) on mutations',
|
|
97
|
+
category: 'policy',
|
|
98
|
+
severity: 'error',
|
|
99
|
+
sql: `
|
|
100
|
+
SELECT tablename as table_name,
|
|
101
|
+
policyname || ': ' ||
|
|
102
|
+
CASE
|
|
103
|
+
WHEN qual = 'true' AND cmd != 'SELECT' THEN 'USING(true) on ' || cmd
|
|
104
|
+
WHEN with_check = 'true' AND cmd IN ('INSERT', 'UPDATE') THEN 'WITH CHECK(true) on ' || cmd
|
|
105
|
+
END as issue
|
|
106
|
+
FROM pg_policies
|
|
107
|
+
WHERE schemaname = 'public'
|
|
108
|
+
AND (
|
|
109
|
+
(qual = 'true' AND cmd != 'SELECT')
|
|
110
|
+
OR (with_check = 'true' AND cmd IN ('INSERT', 'UPDATE'))
|
|
111
|
+
)
|
|
112
|
+
ORDER BY tablename, cmd;`,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'insert-no-with-check',
|
|
116
|
+
name: 'INSERT policies without WITH CHECK (allows any data)',
|
|
117
|
+
category: 'policy',
|
|
118
|
+
severity: 'error',
|
|
119
|
+
sql: `
|
|
120
|
+
SELECT tablename as table_name,
|
|
121
|
+
policyname || ': INSERT policy has no WITH CHECK clause' as issue
|
|
122
|
+
FROM pg_policies
|
|
123
|
+
WHERE schemaname = 'public'
|
|
124
|
+
AND cmd = 'INSERT'
|
|
125
|
+
AND (with_check IS NULL OR with_check = '')
|
|
126
|
+
AND qual != 'false'
|
|
127
|
+
ORDER BY tablename;`,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'update-mismatch',
|
|
131
|
+
name: 'UPDATE policies where USING and WITH CHECK differ (can change org_id)',
|
|
132
|
+
category: 'policy',
|
|
133
|
+
severity: 'error',
|
|
134
|
+
sql: `
|
|
135
|
+
SELECT tablename as table_name,
|
|
136
|
+
policyname || ': USING != WITH CHECK — user could UPDATE row to change organization_id' as issue
|
|
137
|
+
FROM pg_policies
|
|
138
|
+
WHERE schemaname = 'public'
|
|
139
|
+
AND cmd = 'UPDATE'
|
|
140
|
+
AND qual IS NOT NULL
|
|
141
|
+
AND with_check IS NOT NULL
|
|
142
|
+
AND qual != 'false'
|
|
143
|
+
AND with_check != 'false'
|
|
144
|
+
AND qual != with_check
|
|
145
|
+
AND qual NOT LIKE '%auth.uid()%'
|
|
146
|
+
ORDER BY tablename;`,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'missing-crud-coverage',
|
|
150
|
+
name: 'Tables with partial CRUD coverage (missing operations)',
|
|
151
|
+
category: 'policy',
|
|
152
|
+
severity: 'warn',
|
|
153
|
+
sql: `
|
|
154
|
+
WITH table_ops AS (
|
|
155
|
+
SELECT tablename, array_agg(DISTINCT cmd ORDER BY cmd) as ops
|
|
156
|
+
FROM pg_policies
|
|
157
|
+
WHERE schemaname = 'public'
|
|
158
|
+
GROUP BY tablename
|
|
159
|
+
)
|
|
160
|
+
SELECT tablename as table_name,
|
|
161
|
+
'Has policies for ' || array_to_string(ops, ', ') ||
|
|
162
|
+
' but missing ' ||
|
|
163
|
+
array_to_string(
|
|
164
|
+
ARRAY(SELECT x FROM unnest(ARRAY['SELECT','INSERT','UPDATE','DELETE']) x WHERE x != ALL(ops)),
|
|
165
|
+
', '
|
|
166
|
+
) as issue
|
|
167
|
+
FROM table_ops
|
|
168
|
+
WHERE NOT (ops @> ARRAY['SELECT','INSERT','UPDATE','DELETE'])
|
|
169
|
+
AND NOT (ops = ARRAY['ALL'])
|
|
170
|
+
AND tablename NOT IN ('organization_members', 'organization_invites')
|
|
171
|
+
ORDER BY tablename;`,
|
|
172
|
+
},
|
|
173
|
+
// ═══════════════════════════════════════════════════════════
|
|
174
|
+
// C. AUTH FUNCTIONS — exist, signature, security
|
|
175
|
+
// ═══════════════════════════════════════════════════════════
|
|
176
|
+
{
|
|
177
|
+
id: 'missing-auth-functions',
|
|
178
|
+
name: 'Missing Tetra auth helper functions',
|
|
179
|
+
category: 'auth',
|
|
180
|
+
severity: 'error',
|
|
181
|
+
sql: `
|
|
182
|
+
SELECT fn as table_name, 'Function not found in public schema' as issue
|
|
183
|
+
FROM unnest(ARRAY[
|
|
184
|
+
'auth_admin_organizations',
|
|
185
|
+
'auth_user_organizations',
|
|
186
|
+
'auth_creator_organizations',
|
|
187
|
+
'auth_current_user_id',
|
|
188
|
+
'auth_is_superadmin'
|
|
189
|
+
]) fn
|
|
190
|
+
WHERE NOT EXISTS (
|
|
191
|
+
SELECT 1 FROM pg_proc p
|
|
192
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
193
|
+
WHERE n.nspname = 'public' AND p.proname = fn
|
|
194
|
+
);`,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'auth-fn-not-security-definer',
|
|
198
|
+
name: 'Auth functions without SECURITY DEFINER (cannot read organization_members)',
|
|
199
|
+
category: 'auth',
|
|
200
|
+
severity: 'error',
|
|
201
|
+
sql: `
|
|
202
|
+
SELECT p.proname as table_name,
|
|
203
|
+
'Not SECURITY DEFINER — function runs as caller who cannot access organization_members' as issue
|
|
204
|
+
FROM pg_proc p
|
|
205
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
206
|
+
WHERE n.nspname = 'public'
|
|
207
|
+
AND p.proname IN ('auth_admin_organizations', 'auth_user_organizations',
|
|
208
|
+
'auth_creator_organizations', 'auth_current_user_id', 'auth_is_superadmin')
|
|
209
|
+
AND NOT p.prosecdef
|
|
210
|
+
ORDER BY p.proname;`,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'auth-fn-wrong-search-path',
|
|
214
|
+
name: 'Auth functions with non-empty search_path (search_path injection risk)',
|
|
215
|
+
category: 'auth',
|
|
216
|
+
severity: 'error',
|
|
217
|
+
sql: `
|
|
218
|
+
SELECT p.proname as table_name,
|
|
219
|
+
'search_path is not empty string — vulnerable to search_path injection. Got: ' ||
|
|
220
|
+
COALESCE(
|
|
221
|
+
(SELECT option_value FROM pg_options_to_table(p.proconfig) WHERE option_name = 'search_path'),
|
|
222
|
+
'(not set)'
|
|
223
|
+
) as issue
|
|
224
|
+
FROM pg_proc p
|
|
225
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
226
|
+
WHERE n.nspname = 'public'
|
|
227
|
+
AND p.proname IN ('auth_admin_organizations', 'auth_user_organizations',
|
|
228
|
+
'auth_creator_organizations', 'auth_current_user_id', 'auth_is_superadmin')
|
|
229
|
+
AND (
|
|
230
|
+
p.proconfig IS NULL
|
|
231
|
+
OR NOT EXISTS (
|
|
232
|
+
SELECT 1 FROM pg_options_to_table(p.proconfig)
|
|
233
|
+
WHERE option_name = 'search_path' AND option_value IN ('', '""')
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
ORDER BY p.proname;`,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: 'auth-fn-not-stable',
|
|
240
|
+
name: 'Auth functions not marked STABLE (causes repeated evaluation per row)',
|
|
241
|
+
category: 'auth',
|
|
242
|
+
severity: 'warn',
|
|
243
|
+
sql: `
|
|
244
|
+
SELECT p.proname as table_name,
|
|
245
|
+
'Volatility is ' || p.provolatile || ', should be s (STABLE) for performance' as issue
|
|
246
|
+
FROM pg_proc p
|
|
247
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
248
|
+
WHERE n.nspname = 'public'
|
|
249
|
+
AND p.proname IN ('auth_admin_organizations', 'auth_user_organizations',
|
|
250
|
+
'auth_creator_organizations', 'auth_current_user_id', 'auth_is_superadmin')
|
|
251
|
+
AND p.provolatile != 's'
|
|
252
|
+
ORDER BY p.proname;`,
|
|
253
|
+
},
|
|
254
|
+
// ═══════════════════════════════════════════════════════════
|
|
255
|
+
// D. RLS BYPASS ROUTES — the real dangers
|
|
256
|
+
// ═══════════════════════════════════════════════════════════
|
|
257
|
+
{
|
|
258
|
+
id: 'security-definer-data-fn',
|
|
259
|
+
name: 'SECURITY DEFINER functions that return data (bypass RLS)',
|
|
260
|
+
category: 'bypass',
|
|
261
|
+
severity: 'error',
|
|
262
|
+
sql: `
|
|
263
|
+
SELECT p.proname as table_name,
|
|
264
|
+
'SECURITY DEFINER + returns ' || pg_catalog.format_type(p.prorettype, NULL) ||
|
|
265
|
+
' — runs as function owner, bypasses RLS' as issue
|
|
266
|
+
FROM pg_proc p
|
|
267
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
268
|
+
WHERE n.nspname = 'public'
|
|
269
|
+
AND p.prosecdef = true
|
|
270
|
+
AND p.proname NOT IN (
|
|
271
|
+
'auth_admin_organizations', 'auth_user_organizations',
|
|
272
|
+
'auth_creator_organizations', 'auth_current_user_id', 'auth_is_superadmin',
|
|
273
|
+
'update_updated_at_column'
|
|
274
|
+
)
|
|
275
|
+
AND p.proname NOT LIKE 'st_%'
|
|
276
|
+
AND p.proname NOT LIKE 'postgis_%'
|
|
277
|
+
AND pg_catalog.format_type(p.prorettype, NULL) NOT IN ('trigger', 'event_trigger', 'void')
|
|
278
|
+
ORDER BY p.proname;`,
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: 'anon-callable-functions',
|
|
282
|
+
name: 'Functions callable by anon role (unauthenticated access)',
|
|
283
|
+
category: 'bypass',
|
|
284
|
+
severity: 'error',
|
|
285
|
+
sql: `
|
|
286
|
+
SELECT p.proname as table_name,
|
|
287
|
+
'EXECUTE granted to anon — unauthenticated users can call this function' as issue
|
|
288
|
+
FROM pg_proc p
|
|
289
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
290
|
+
WHERE n.nspname = 'public'
|
|
291
|
+
AND p.proname NOT LIKE 'st_%'
|
|
292
|
+
AND p.proname NOT LIKE 'postgis_%'
|
|
293
|
+
AND p.proname NOT LIKE '_supabase%'
|
|
294
|
+
AND has_function_privilege('anon', p.oid, 'EXECUTE')
|
|
295
|
+
AND p.proname NOT IN ('auth_admin_organizations', 'auth_user_organizations',
|
|
296
|
+
'auth_creator_organizations', 'auth_current_user_id', 'auth_is_superadmin')
|
|
297
|
+
AND pg_catalog.format_type(p.prorettype, NULL) NOT IN ('trigger', 'event_trigger')
|
|
298
|
+
ORDER BY p.proname;`,
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'views-bypass-rls',
|
|
302
|
+
name: 'Views without security_invoker=true (run as view creator, bypass RLS)',
|
|
303
|
+
category: 'bypass',
|
|
304
|
+
severity: 'error',
|
|
305
|
+
sql: `
|
|
306
|
+
SELECT c.relname as table_name,
|
|
307
|
+
'View runs as DEFINER (creator) — bypasses RLS on underlying tables. Add security_invoker=true' as issue
|
|
308
|
+
FROM pg_class c
|
|
309
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
310
|
+
WHERE n.nspname = 'public'
|
|
311
|
+
AND c.relkind = 'v'
|
|
312
|
+
AND (
|
|
313
|
+
c.reloptions IS NULL
|
|
314
|
+
OR 'security_invoker=true' != ALL(c.reloptions)
|
|
315
|
+
)
|
|
316
|
+
ORDER BY c.relname;`,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: 'anon-table-grants',
|
|
320
|
+
name: 'Direct table grants to anon role',
|
|
321
|
+
category: 'bypass',
|
|
322
|
+
severity: 'error',
|
|
323
|
+
sql: `
|
|
324
|
+
SELECT table_name,
|
|
325
|
+
'anon has ' || privilege_type || ' grant — even with RLS, anon can attempt queries' as issue
|
|
326
|
+
FROM information_schema.table_privileges
|
|
327
|
+
WHERE table_schema = 'public'
|
|
328
|
+
AND grantee = 'anon'
|
|
329
|
+
AND privilege_type IN ('INSERT', 'UPDATE', 'DELETE')
|
|
330
|
+
AND table_name NOT LIKE 'pg_%'
|
|
331
|
+
AND table_name NOT LIKE '_supabase%'
|
|
332
|
+
ORDER BY table_name, privilege_type;`,
|
|
333
|
+
},
|
|
334
|
+
// ═══════════════════════════════════════════════════════════
|
|
335
|
+
// E. DATA EXPOSURE — Realtime, storage, schema
|
|
336
|
+
// ═══════════════════════════════════════════════════════════
|
|
337
|
+
{
|
|
338
|
+
id: 'realtime-on-sensitive',
|
|
339
|
+
name: 'Supabase Realtime enabled on sensitive tables',
|
|
340
|
+
category: 'exposure',
|
|
341
|
+
severity: 'warn',
|
|
342
|
+
sql: `
|
|
343
|
+
SELECT tablename as table_name,
|
|
344
|
+
'Table is in supabase_realtime publication — live data streams bypass application-level auth' as issue
|
|
345
|
+
FROM pg_publication_tables
|
|
346
|
+
WHERE pubname = 'supabase_realtime'
|
|
347
|
+
AND schemaname = 'public'
|
|
348
|
+
AND tablename NOT IN ('messages', 'notifications')
|
|
349
|
+
ORDER BY tablename;`,
|
|
350
|
+
},
|
|
351
|
+
// ═══════════════════════════════════════════════════════════
|
|
352
|
+
// F. MIGRATION VERIFICATION — Tetra standards applied
|
|
353
|
+
// ═══════════════════════════════════════════════════════════
|
|
354
|
+
{
|
|
355
|
+
id: 'legacy-columns',
|
|
356
|
+
name: 'Legacy column names (not snake_case)',
|
|
357
|
+
category: 'migration',
|
|
358
|
+
severity: 'error',
|
|
359
|
+
sql: `
|
|
360
|
+
SELECT table_name, column_name || ' → should be ' ||
|
|
361
|
+
CASE
|
|
362
|
+
WHEN column_name = 'organizationid' THEN 'organization_id'
|
|
363
|
+
WHEN column_name = 'userid' THEN 'user_id'
|
|
364
|
+
WHEN column_name = 'user_public_id' THEN 'user_id'
|
|
365
|
+
WHEN column_name = 'firstname' THEN 'first_name'
|
|
366
|
+
WHEN column_name = 'lastname' THEN 'last_name'
|
|
367
|
+
WHEN column_name = 'createdat' THEN 'created_at'
|
|
368
|
+
WHEN column_name = 'updatedat' THEN 'updated_at'
|
|
369
|
+
WHEN column_name ~ '^[a-z]+id$' AND column_name != 'id' AND length(column_name) > 3
|
|
370
|
+
THEN regexp_replace(column_name, '([a-z])id$', '\\1_id')
|
|
371
|
+
ELSE 'unknown'
|
|
372
|
+
END as issue
|
|
373
|
+
FROM information_schema.columns
|
|
374
|
+
WHERE table_schema = 'public'
|
|
375
|
+
AND table_name NOT LIKE 'pg_%'
|
|
376
|
+
AND table_name NOT LIKE '_supabase%'
|
|
377
|
+
AND (
|
|
378
|
+
column_name IN ('organizationid', 'userid', 'user_public_id', 'firstname', 'lastname', 'createdat', 'updatedat')
|
|
379
|
+
OR (column_name ~ '^[a-z]+id$' AND column_name != 'id' AND length(column_name) > 3)
|
|
380
|
+
)
|
|
381
|
+
ORDER BY table_name, column_name;`,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 'non-tetra-auth-pattern',
|
|
385
|
+
name: 'Policies not using Tetra auth functions',
|
|
386
|
+
category: 'migration',
|
|
387
|
+
severity: 'warn',
|
|
388
|
+
sql: `
|
|
389
|
+
SELECT tablename as table_name,
|
|
390
|
+
policyname || ' (' || cmd || '): uses non-standard auth — ' || left(qual, 60) as issue
|
|
391
|
+
FROM pg_policies
|
|
392
|
+
WHERE schemaname = 'public'
|
|
393
|
+
AND qual IS NOT NULL
|
|
394
|
+
AND qual != 'true'
|
|
395
|
+
AND qual != 'false'
|
|
396
|
+
AND qual NOT LIKE '%auth_admin_organizations%'
|
|
397
|
+
AND qual NOT LIKE '%auth_user_organizations%'
|
|
398
|
+
AND qual NOT LIKE '%auth_creator_organizations%'
|
|
399
|
+
AND qual NOT LIKE '%auth_is_superadmin%'
|
|
400
|
+
AND qual NOT LIKE '%auth.uid()%'
|
|
401
|
+
AND qual NOT LIKE '%is_active%'
|
|
402
|
+
ORDER BY tablename, cmd;`,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
id: 'missing-org-column',
|
|
406
|
+
name: 'Tables without organization_id (except special tables)',
|
|
407
|
+
category: 'migration',
|
|
408
|
+
severity: 'warn',
|
|
409
|
+
sql: `
|
|
410
|
+
SELECT c.relname as table_name, 'No organization_id column — cannot scope by org' as issue
|
|
411
|
+
FROM pg_class c
|
|
412
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
413
|
+
WHERE n.nspname = 'public'
|
|
414
|
+
AND c.relkind = 'r'
|
|
415
|
+
AND c.relname NOT LIKE 'pg_%'
|
|
416
|
+
AND c.relname NOT LIKE '_supabase%'
|
|
417
|
+
AND c.relname NOT LIKE '_prisma%'
|
|
418
|
+
AND c.relname NOT IN ('organizations', 'users_public', 'organization_members', 'organization_invites')
|
|
419
|
+
AND NOT EXISTS (
|
|
420
|
+
SELECT 1 FROM information_schema.columns ic
|
|
421
|
+
WHERE ic.table_schema = 'public'
|
|
422
|
+
AND ic.table_name = c.relname
|
|
423
|
+
AND ic.column_name = 'organization_id'
|
|
424
|
+
)
|
|
425
|
+
ORDER BY c.relname;`,
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: 'org-members-table',
|
|
429
|
+
name: 'organization_members table missing or wrong structure',
|
|
430
|
+
category: 'migration',
|
|
431
|
+
severity: 'error',
|
|
432
|
+
sql: `
|
|
433
|
+
WITH expected_cols AS (
|
|
434
|
+
SELECT unnest(ARRAY['id','user_id','organization_id','role','status','created_at','updated_at']) as col
|
|
435
|
+
),
|
|
436
|
+
actual_cols AS (
|
|
437
|
+
SELECT column_name as col
|
|
438
|
+
FROM information_schema.columns
|
|
439
|
+
WHERE table_schema = 'public' AND table_name = 'organization_members'
|
|
440
|
+
),
|
|
441
|
+
table_exists AS (
|
|
442
|
+
SELECT EXISTS (
|
|
443
|
+
SELECT 1 FROM information_schema.tables
|
|
444
|
+
WHERE table_schema = 'public' AND table_name = 'organization_members'
|
|
445
|
+
) as exists_flag
|
|
446
|
+
)
|
|
447
|
+
SELECT 'organization_members' as table_name,
|
|
448
|
+
CASE
|
|
449
|
+
WHEN NOT (SELECT exists_flag FROM table_exists) THEN 'Table does not exist'
|
|
450
|
+
ELSE 'Missing column: ' || e.col
|
|
451
|
+
END as issue
|
|
452
|
+
FROM expected_cols e
|
|
453
|
+
WHERE NOT (SELECT exists_flag FROM table_exists)
|
|
454
|
+
OR e.col NOT IN (SELECT col FROM actual_cols)
|
|
455
|
+
LIMIT 1;`,
|
|
456
|
+
},
|
|
457
|
+
];
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Generate a single SQL query that runs ALL audit checks and returns a summary.
|
|
461
|
+
* Returns one row per violation with check_id, severity, category, table_name, and issue.
|
|
462
|
+
*/
|
|
463
|
+
export function generateAuditSQL() {
|
|
464
|
+
const checks = getAuditChecks();
|
|
465
|
+
const ctes = checks.map((check, i) => {
|
|
466
|
+
const innerSQL = check.sql.trim().replace(/;$/, '');
|
|
467
|
+
return `check_${i} AS (
|
|
468
|
+
SELECT
|
|
469
|
+
'${check.id}' as check_id,
|
|
470
|
+
'${check.severity}' as severity,
|
|
471
|
+
'${check.category}' as category,
|
|
472
|
+
'${check.name}' as check_name,
|
|
473
|
+
t.table_name,
|
|
474
|
+
t.issue
|
|
475
|
+
FROM (${innerSQL}) t
|
|
476
|
+
)`;
|
|
477
|
+
});
|
|
478
|
+
const unions = checks.map((_, i) => `SELECT * FROM check_${i}`).join('\nUNION ALL\n');
|
|
479
|
+
return `-- ============================================================================
|
|
480
|
+
-- Tetra RLS Audit v2.0
|
|
481
|
+
-- ============================================================================
|
|
482
|
+
-- 22 checks across 6 categories:
|
|
483
|
+
-- A. Foundation — RLS enabled, FORCE RLS, policies exist
|
|
484
|
+
-- B. Policy quality — roles, expressions, completeness
|
|
485
|
+
-- C. Auth functions — exist, signature, SECURITY DEFINER
|
|
486
|
+
-- D. RLS bypass routes — functions, views, grants that skip RLS
|
|
487
|
+
-- E. Data exposure — Realtime, storage, schema leaks
|
|
488
|
+
-- F. Migration verification — Tetra standards applied correctly
|
|
489
|
+
--
|
|
490
|
+
-- Empty result = all checks pass.
|
|
491
|
+
-- Generated by: Tetra RLS Auditor v2.0
|
|
492
|
+
-- ============================================================================
|
|
493
|
+
|
|
494
|
+
WITH
|
|
495
|
+
${ctes.join(',\n')}
|
|
496
|
+
|
|
497
|
+
${unions}
|
|
498
|
+
ORDER BY
|
|
499
|
+
CASE severity WHEN 'error' THEN 1 WHEN 'warn' THEN 2 ELSE 3 END,
|
|
500
|
+
CASE category WHEN 'foundation' THEN 1 WHEN 'policy' THEN 2 WHEN 'auth' THEN 3 WHEN 'bypass' THEN 4 WHEN 'exposure' THEN 5 ELSE 6 END,
|
|
501
|
+
check_id,
|
|
502
|
+
table_name;
|
|
503
|
+
`;
|
|
504
|
+
}
|
|
505
|
+
//# sourceMappingURL=rls-auditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rls-auditor.js","sourceRoot":"","sources":["../../src/generators/rls-auditor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tetra RLS Checker v2.0
|
|
3
|
+
*
|
|
4
|
+
* Runs the Tetra RLS audit checks against a live Supabase project
|
|
5
|
+
* and returns a structured report with pass/fail per check.
|
|
6
|
+
*
|
|
7
|
+
* This is NOT just a config checker — it verifies:
|
|
8
|
+
* - Foundation: RLS enabled, FORCE RLS, policies exist
|
|
9
|
+
* - Policy quality: roles, expressions, CRUD coverage
|
|
10
|
+
* - Auth functions: exist, SECURITY DEFINER, search_path
|
|
11
|
+
* - RLS bypass routes: functions/views/grants that skip RLS entirely
|
|
12
|
+
* - Data exposure: Realtime on sensitive tables
|
|
13
|
+
* - Migration state: Tetra standards actually applied in the DB
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* import { runRLSCheck } from '@tetra/core';
|
|
17
|
+
* const report = await runRLSCheck(supabaseClient);
|
|
18
|
+
* console.log(report.text); // Full formatted report
|
|
19
|
+
* console.log(report.passed); // true only if zero errors
|
|
20
|
+
* if (!report.passed) throw new Error(report.summary);
|
|
21
|
+
*/
|
|
22
|
+
import { type AuditCheck } from './rls-auditor.js';
|
|
23
|
+
export interface RLSViolation {
|
|
24
|
+
table_name: string;
|
|
25
|
+
issue: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface RLSCheckResult {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
category: AuditCheck['category'];
|
|
32
|
+
severity: AuditCheck['severity'];
|
|
33
|
+
passed: boolean;
|
|
34
|
+
violationCount: number;
|
|
35
|
+
violations: RLSViolation[];
|
|
36
|
+
}
|
|
37
|
+
export interface RLSCategorySummary {
|
|
38
|
+
category: string;
|
|
39
|
+
label: string;
|
|
40
|
+
totalChecks: number;
|
|
41
|
+
passedChecks: number;
|
|
42
|
+
errorCount: number;
|
|
43
|
+
warnCount: number;
|
|
44
|
+
passed: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface RLSReport {
|
|
47
|
+
/** true if zero errors (warnings don't fail the report) */
|
|
48
|
+
passed: boolean;
|
|
49
|
+
/** Human-readable summary line */
|
|
50
|
+
summary: string;
|
|
51
|
+
/** Total checks run */
|
|
52
|
+
totalChecks: number;
|
|
53
|
+
passedCount: number;
|
|
54
|
+
errorCount: number;
|
|
55
|
+
warnCount: number;
|
|
56
|
+
infoCount: number;
|
|
57
|
+
/** Per-category breakdown */
|
|
58
|
+
categories: RLSCategorySummary[];
|
|
59
|
+
/** Per-check results */
|
|
60
|
+
checks: RLSCheckResult[];
|
|
61
|
+
/** ISO timestamp */
|
|
62
|
+
timestamp: string;
|
|
63
|
+
/** Formatted text report for logging/display */
|
|
64
|
+
text: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Minimal Supabase client interface — needs rpc() for SQL execution.
|
|
68
|
+
* Works with @supabase/supabase-js service_role client.
|
|
69
|
+
*/
|
|
70
|
+
interface SupabaseExecutor {
|
|
71
|
+
rpc: (fn: string, params: Record<string, unknown>) => Promise<{
|
|
72
|
+
data: unknown;
|
|
73
|
+
error: unknown;
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run all Tetra RLS audit checks against a live Supabase project.
|
|
78
|
+
* Uses the combined CTE approach for a single DB round-trip.
|
|
79
|
+
*/
|
|
80
|
+
export declare function runRLSCheck(supabase: SupabaseExecutor, options?: {
|
|
81
|
+
/** Only run checks of these severities */
|
|
82
|
+
severities?: AuditCheck['severity'][];
|
|
83
|
+
/** Only run checks in these categories */
|
|
84
|
+
categories?: AuditCheck['category'][];
|
|
85
|
+
/** Skip these check IDs */
|
|
86
|
+
skip?: string[];
|
|
87
|
+
}): Promise<RLSReport>;
|
|
88
|
+
/**
|
|
89
|
+
* Run checks one-by-one (slower but shows which check fails if SQL errors).
|
|
90
|
+
* Use this for debugging when runRLSCheck fails.
|
|
91
|
+
*/
|
|
92
|
+
export declare function runRLSCheckDebug(supabase: SupabaseExecutor): Promise<RLSReport>;
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=rls-checker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rls-checker.d.ts","sourceRoot":"","sources":["../../src/generators/rls-checker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IACjC,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,YAAY,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,MAAM,EAAE,OAAO,CAAC;IAChB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,wBAAwB;IACxB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,UAAU,gBAAgB;IACxB,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAClG;AAaD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,CAAC,EAAE;IACR,0CAA0C;IAC1C,UAAU,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;IACtC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;IACtC,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GACA,OAAO,CAAC,SAAS,CAAC,CA+CpB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,SAAS,CAAC,CAkBpB"}
|