@nodii/db-rls 0.10.0 → 0.12.0

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.
Files changed (44) hide show
  1. package/dist/auth-schema.d.ts +120 -0
  2. package/dist/auth-schema.d.ts.map +1 -1
  3. package/dist/auth-schema.js +177 -0
  4. package/dist/auth-schema.js.map +1 -1
  5. package/dist/context.d.ts +26 -8
  6. package/dist/context.d.ts.map +1 -1
  7. package/dist/context.js +35 -24
  8. package/dist/context.js.map +1 -1
  9. package/dist/drizzle-client.d.ts +17 -5
  10. package/dist/drizzle-client.d.ts.map +1 -1
  11. package/dist/drizzle-client.js +19 -18
  12. package/dist/drizzle-client.js.map +1 -1
  13. package/dist/index.d.ts +6 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +20 -7
  16. package/dist/index.js.map +1 -1
  17. package/dist/postgres-js-client.d.ts +17 -10
  18. package/dist/postgres-js-client.d.ts.map +1 -1
  19. package/dist/postgres-js-client.js +19 -21
  20. package/dist/postgres-js-client.js.map +1 -1
  21. package/dist/rls-policy-gen.d.ts +43 -27
  22. package/dist/rls-policy-gen.d.ts.map +1 -1
  23. package/dist/rls-policy-gen.js +40 -15
  24. package/dist/rls-policy-gen.js.map +1 -1
  25. package/dist/rls-policy.d.ts +11 -1
  26. package/dist/rls-policy.d.ts.map +1 -1
  27. package/dist/rls-policy.js +31 -4
  28. package/dist/rls-policy.js.map +1 -1
  29. package/dist/tenant-fence.d.ts +165 -0
  30. package/dist/tenant-fence.d.ts.map +1 -0
  31. package/dist/tenant-fence.js +542 -0
  32. package/dist/tenant-fence.js.map +1 -0
  33. package/dist/types.d.ts +18 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/types.js +22 -0
  36. package/dist/types.js.map +1 -1
  37. package/dist/verify-rls-cli.d.ts.map +1 -1
  38. package/dist/verify-rls-cli.js +86 -10
  39. package/dist/verify-rls-cli.js.map +1 -1
  40. package/dist/verify-rls.d.ts +26 -5
  41. package/dist/verify-rls.d.ts.map +1 -1
  42. package/dist/verify-rls.js +73 -5
  43. package/dist/verify-rls.js.map +1 -1
  44. package/package.json +2 -2
@@ -0,0 +1,542 @@
1
+ // verifyTenantFence — the catalog-time half of the guard.
2
+ //
3
+ // Fixing the generator fixes what the generator emits. The operator's
4
+ // requirement is stronger: *"enforce that even if this shape regresses we DON'T
5
+ // ALLOW ANYTHING CROSS-TENANT EVER"*. Three mechanisms cover three different
6
+ // ways a regression arrives, and they are deliberately NOT the same mechanism:
7
+ //
8
+ // 1. GENERATION TIME — `authenticatedPredicate` is the single source of truth
9
+ // for every emitter (both string generators, the Drizzle builder, and the
10
+ // three libraries that inline a copy). One function to change, and the
11
+ // byte-equality tests in saga / dsar / audit-chain fail the moment a copy
12
+ // drifts from it.
13
+ //
14
+ // 2. SESSION TIME — `withUserMembershipContext` throws, and
15
+ // `auth.session_tenant_ids()` raises 42501 on an unbound session, so a
16
+ // tenant-less user session cannot quietly become either a union or a
17
+ // silently-empty grid.
18
+ //
19
+ // 3. CATALOG TIME — THIS MODULE. It reads what POSTGRES ACTUALLY STORED
20
+ // (`pg_policies.qual` / `.with_check`, already deparsed by the server) and
21
+ // refuses to let a deployment proceed unless every `authenticated` policy
22
+ // resolves through `auth.session_tenant_ids()`.
23
+ //
24
+ // (3) is the one a static source lint cannot reach, and that is the whole reason
25
+ // it exists. A source rule sees the repository. It does not see:
26
+ // - a `CREATE POLICY` run by hand against prod during an incident;
27
+ // - a migration that landed from a branch the lint never ran on;
28
+ // - a policy that predates the lint and was never regenerated;
29
+ // - a hand-edited `0010_rls_policies.sql` whose header says "GENERATED";
30
+ // - a service still on an old @nodii/db-rls that emits the union;
31
+ // - a boot-time re-emitter (saga / dsar / audit-chain DROP+CREATE on EVERY
32
+ // boot) that reinstates its own copy after a correct migration ran.
33
+ // The catalog sees all six, because the catalog is the thing that actually
34
+ // decides who reads which row. A source lint and this verifier are complements:
35
+ // the lint fails the PR, this fails the deploy.
36
+ //
37
+ // WHY THE ASSERTION IS POSITIVE.
38
+ //
39
+ // The tempting implementation is "find policies containing
40
+ // `user_tenant_ids` without a fence, and fail if any remain" — an ABSENCE
41
+ // assertion. It passes in a case it must not: a sweep (or a hand-edit, or a
42
+ // regexp_replace repair) that mangles a policy into something which no longer
43
+ // mentions `user_tenant_ids` AND is not correctly fenced either. Absence of the
44
+ // bad string is not presence of the good one. So the rule here is inverted:
45
+ // every policy granted to `authenticated` MUST resolve through
46
+ // `auth.session_tenant_ids()`, or appear on an explicit, argued exemption list.
47
+ // Anything else is an offender, including shapes nobody has thought of yet.
48
+ //
49
+ // Wire it into `db:setup` / `db:verify` next to `verifyRls`:
50
+ //
51
+ // await verifyRls(pool, { requiredTables });
52
+ // await verifyTenantFence(pool); // throws TenantFenceError, loud
53
+ import { SESSION_TENANT_IDS_DEGRADED_MARKER } from "./auth-schema.js";
54
+ import { DbRlsError } from "./types.js";
55
+ /**
56
+ * Thrown by {@link verifyTenantFence}. Carries the structured offender list so
57
+ * a `db:setup` gate can render a precise failure — and so a caller can tell
58
+ * "this is a stale service that needs regenerating" apart from "someone
59
+ * hand-wrote a cross-tenant policy in prod".
60
+ */
61
+ export class TenantFenceError extends DbRlsError {
62
+ offenders;
63
+ constructor(offenders) {
64
+ super(formatFenceMessage(offenders));
65
+ this.name = "TenantFenceError";
66
+ this.offenders = offenders;
67
+ }
68
+ }
69
+ const REASON_TEXT = {
70
+ cross_tenant_union: "consults auth.user_tenant_ids directly instead of auth.session_tenant_ids() — resolves across EVERY tenant the principal belongs to",
71
+ unfenced_permissive: "is permissive with no tenant reference at all — every tenant, every row",
72
+ not_resolved_through_session: "does not resolve through auth.session_tenant_ids() — it may be fenced some other way, but nothing here proves it, and an unrecognized shape is not a safe shape",
73
+ resolver_missing: "auth.session_tenant_ids() is not installed — apply SESSION_TENANT_IDS_SQL (applyAuthSchemaMigrations ships it)",
74
+ resolver_not_loud: "auth.session_tenant_ids() does not RAISE on an unbound session — it was replaced with a NULL-returning getter, which restores silent failure",
75
+ resolver_not_security_definer: "auth.session_tenant_ids() is not SECURITY DEFINER — it cannot read the membership mirror (permission denied), or will recurse into the mirror's own policy (54001)",
76
+ resolver_degraded: "the DEGRADED fence-only resolver is installed — the session tenant is trusted WITHOUT proving membership. Valid only for a service with no membership mirror; pass allowDegradedResolver to accept it deliberately",
77
+ membership_helper_is_stub: "auth.user_tenant_ids is still the bootstrap stub returning the empty set — the strict resolver intersects to nothing, so the authenticated pool is silently deny-all",
78
+ rls_disabled_on_tenant_table: "has a tenant_id column but ROW LEVEL SECURITY is NOT ENABLED — with the canonical blanket grants this table is readable and writable across EVERY tenant, and no policy is ever consulted. Fix: ALTER TABLE … ENABLE ROW LEVEL SECURITY and apply withRls()",
79
+ tenant_unscoped_unique_index: "is a UNIQUE index on a tenant-scoped table that omits the tenant column from its KEY — RLS filters visibility but does NOT partition uniqueness, so one tenant can collide with (or resolve onto) another tenant's row. Fix: make it UNIQUE (tenant_id, …)",
80
+ unprotected_relation: "exposes a tenant column but cannot enforce RLS — a materialized/foreign relation has no row policies at all, and a view without `security_invoker = true` runs as its OWNER and bypasses the underlying table's policies. Fix: CREATE VIEW … WITH (security_invoker = true), or revoke it from authenticated",
81
+ };
82
+ function formatFenceMessage(offenders) {
83
+ const lines = offenders.map((o) => ` - ${o.object} [${o.policy}${o.command === "-" ? "" : ` ${o.command}`}] ${REASON_TEXT[o.reason]}\n ${o.expression ?? "(no expression)"}`);
84
+ return `tenant_fence: ${offenders.length} authenticated-pool policy/helper defect(s). A user session must reach exactly ONE tenant — the one the edge resolved from the request — and nothing else, ever.\n${lines.join("\n")}\nFix: regenerate policies with @nodii/db-rls withRls() / generateRlsPolicy(), which emit \`<col> IN (SELECT auth.session_tenant_ids())\`, and apply the resolver via applyAuthSchemaMigrations. Nodii-staff cross-tenant access is a different path entirely: the platform_ops pool + withPlatformOpsContext.`;
85
+ }
86
+ // Matching notes — the `auth.` prefix is OPTIONAL on purpose.
87
+ //
88
+ // `pg_policies.qual` is `pg_get_expr(...)`, and pg_get_expr omits a schema
89
+ // qualifier for any function already reachable through the CALLER'S
90
+ // search_path. So the identical policy deparses as
91
+ // `(tenant_id IN (SELECT auth.session_tenant_ids()))` with search_path=public
92
+ // `(tenant_id IN (SELECT session_tenant_ids()))` with `auth` on the path
93
+ // A verifier that insisted on `auth.` would silently PASS every policy on every
94
+ // service whose migration runner puts `auth` in its search_path — which is most
95
+ // of them. A security check that quietly finds nothing is worse than no check,
96
+ // because it is reported as a green.
97
+ const RESOLVER_RE = /\b(?:auth\s*\.\s*)?session_tenant_ids\s*\(/i;
98
+ const UNION_RE = /\b(?:auth\s*\.\s*)?user_tenant_ids\s*\(/i;
99
+ /** Does this expression resolve through the session resolver? */
100
+ export function resolvesThroughSession(expr) {
101
+ return expr !== null && RESOLVER_RE.test(expr);
102
+ }
103
+ /** Does this expression consult the raw membership union? */
104
+ export function consultsMembershipUnion(expr) {
105
+ return expr !== null && UNION_RE.test(expr);
106
+ }
107
+ // (Role matching used to live here as a string compare over
108
+ // `pg_policies.roles`. It is now resolved by the catalog in SQL — see the note
109
+ // on the policy query in `verifyTenantFence`. A `{public}` policy applies to
110
+ // authenticated without containing the word, and role membership is
111
+ // transitive; neither fact is visible to a substring test.)
112
+ function isExempt(row, exempt) {
113
+ if (!exempt || exempt.length === 0)
114
+ return false;
115
+ const qualified = `${row.tablename}.${row.policyname}`;
116
+ return exempt.includes(row.policyname) || exempt.includes(qualified);
117
+ }
118
+ /**
119
+ * Classify ONE authenticated policy. Exported so the test-suite can assert the
120
+ * classification directly without standing up every offending shape in the
121
+ * catalog, and so a caller can reuse the rule in a bespoke gate.
122
+ *
123
+ * The order matters: the two SPECIFIC diagnoses come first because they tell a
124
+ * reader what to do, and the general positive-rule failure is the fallback.
125
+ */
126
+ export function classifyAuthPolicy(expr) {
127
+ if (resolvesThroughSession(expr))
128
+ return null;
129
+ if (consultsMembershipUnion(expr))
130
+ return "cross_tenant_union";
131
+ if (expr === null || expr.trim() === "" || /^\(?\s*true\s*\)?$/i.test(expr)) {
132
+ return "unfenced_permissive";
133
+ }
134
+ return "not_resolved_through_session";
135
+ }
136
+ /**
137
+ * Asserts that EVERY policy granted to `authenticated` resolves through
138
+ * `auth.session_tenant_ids()`, and that the resolver itself is installed, loud,
139
+ * and SECURITY DEFINER.
140
+ *
141
+ * @throws {TenantFenceError} listing every offender, with the deparsed
142
+ * expression straight from the catalog.
143
+ */
144
+ export async function verifyTenantFence(querier, opts = {}) {
145
+ const schemas = opts.schema === undefined
146
+ ? ["public"]
147
+ : typeof opts.schema === "string"
148
+ ? [opts.schema]
149
+ : [...opts.schema];
150
+ const checkHelpers = opts.checkHelpers ?? true;
151
+ const offenders = [];
152
+ // ROLE RESOLUTION IS DONE IN SQL, AND IT IS NOT A STRING COMPARE.
153
+ //
154
+ // The first version of this read `pg_policies.roles::text` and skipped any
155
+ // policy whose text did not literally contain "authenticated". Three ways
156
+ // through that, each proven to yield a live cross-tenant read AND write while
157
+ // the verifier reported zero offenders:
158
+ //
159
+ // 1. `CREATE POLICY … FOR ALL USING (true)` with NO `TO` clause. Postgres
160
+ // records `polroles = {0}` (PUBLIC), rendered `{public}`. It APPLIES to
161
+ // authenticated and ORs with the fenced policy — permissive policies are
162
+ // unioned — but the literal string is absent, so it was skipped. This is
163
+ // item 1 on this module's own threat list: the hand-written hotfix.
164
+ // 2. Explicit `TO PUBLIC` — same thing, said out loud.
165
+ // 3. `TO app_reader`, where `authenticated` has been GRANTed app_reader.
166
+ // Role membership is transitive; a string compare does not know that.
167
+ //
168
+ // `pg_has_role(role, role, 'USAGE')` is the authority Postgres itself uses,
169
+ // and OID 0 in `polroles` is the canonical spelling of PUBLIC. Both are
170
+ // resolved here rather than in TypeScript, because the catalog is the only
171
+ // thing that knows the role graph.
172
+ const { rows } = await querier.query(`SELECT n.nspname AS schemaname,
173
+ c.relname AS tablename,
174
+ pol.polname AS policyname,
175
+ CASE pol.polcmd WHEN 'r' THEN 'SELECT' WHEN 'a' THEN 'INSERT'
176
+ WHEN 'w' THEN 'UPDATE' WHEN 'd' THEN 'DELETE'
177
+ ELSE 'ALL' END AS cmd,
178
+ pol.polpermissive AS permissive,
179
+ pg_get_expr(pol.polqual, pol.polrelid) AS qual,
180
+ pg_get_expr(pol.polwithcheck, pol.polrelid) AS with_check,
181
+ (
182
+ -- PUBLIC (OID 0) applies to everyone. Otherwise ask Postgres
183
+ -- whether 'authenticated' IS or INHERITS the policy's role.
184
+ EXISTS (SELECT 1 FROM unnest(pol.polroles) r WHERE r = 0)
185
+ OR (
186
+ EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated')
187
+ AND EXISTS (
188
+ SELECT 1 FROM unnest(pol.polroles) r
189
+ WHERE pg_has_role('authenticated', r, 'USAGE')
190
+ )
191
+ )
192
+ ) AS applies_to_authenticated
193
+ FROM pg_policy pol
194
+ JOIN pg_class c ON c.oid = pol.polrelid
195
+ JOIN pg_namespace n ON n.oid = c.relnamespace
196
+ WHERE n.nspname = ANY($1::text[])
197
+ ORDER BY c.relname, pol.polname`, [schemas]);
198
+ const wanted = opts.tables?.map((t) => t.split(".").pop() ?? t);
199
+ for (const row of rows) {
200
+ if (!row.applies_to_authenticated)
201
+ continue;
202
+ // A RESTRICTIVE policy can only ever narrow access — it is ANDed, never
203
+ // ORed — so it cannot be the source of a cross-tenant grant and is not
204
+ // required to carry the fence itself.
205
+ if (row.permissive === false)
206
+ continue;
207
+ if (wanted && !wanted.includes(row.tablename))
208
+ continue;
209
+ if (isExempt(row, opts.exemptPolicies))
210
+ continue;
211
+ // A policy can carry USING, WITH CHECK, or both. EVERY clause present must
212
+ // resolve — a fenced read with an unfenced WITH CHECK is a cross-tenant
213
+ // WRITE, which is exactly what the refund incident exercised.
214
+ for (const expr of [row.qual, row.with_check]) {
215
+ if (expr === null)
216
+ continue;
217
+ const reason = classifyAuthPolicy(expr);
218
+ if (reason !== null) {
219
+ offenders.push({
220
+ object: `${row.schemaname}.${row.tablename}`,
221
+ policy: row.policyname,
222
+ command: row.cmd,
223
+ reason,
224
+ expression: expr,
225
+ });
226
+ }
227
+ }
228
+ // A policy with NEITHER clause is permissive-everything.
229
+ if (row.qual === null && row.with_check === null) {
230
+ offenders.push({
231
+ object: `${row.schemaname}.${row.tablename}`,
232
+ policy: row.policyname,
233
+ command: row.cmd,
234
+ reason: "unfenced_permissive",
235
+ expression: null,
236
+ });
237
+ }
238
+ }
239
+ if (opts.checkTableCoverage ?? true) {
240
+ offenders.push(...(await checkTenantTableCoverage(querier, schemas, opts)));
241
+ }
242
+ if (opts.checkUniqueIndexes ?? true) {
243
+ offenders.push(...(await checkTenantUniqueIndexes(querier, schemas, opts)));
244
+ }
245
+ if (checkHelpers) {
246
+ offenders.push(...(await checkResolverHelpers(querier, opts.allowDegradedResolver)));
247
+ }
248
+ if (offenders.length > 0)
249
+ throw new TenantFenceError(offenders);
250
+ }
251
+ const RELKIND_LABEL = {
252
+ v: "VIEW",
253
+ m: "MATERIALIZED VIEW",
254
+ f: "FOREIGN TABLE",
255
+ p: "PARTITIONED TABLE",
256
+ r: "TABLE",
257
+ };
258
+ /** Default column names that mark a relation as tenant-scoped. `org_id` is in
259
+ * the default set because `withRls`/`generateRlsPolicy` accept an arbitrary
260
+ * `tenantColumn` and ship a test for `org_id` specifically — coverage keyed on
261
+ * a single configured name exempted exactly the tables the library encourages
262
+ * people to create. */
263
+ const DEFAULT_TENANT_COLUMNS = ["tenant_id", "org_id", "organization_id"];
264
+ /** Normalise a Postgres `text[]` that may arrive parsed or as `{a,b}`. */
265
+ function toStringArray(v) {
266
+ if (Array.isArray(v))
267
+ return v;
268
+ if (typeof v !== "string")
269
+ return [];
270
+ return v
271
+ .replace(/^\{|\}$/g, "")
272
+ .split(",")
273
+ .map((x) => x.replace(/^"|"$/g, "").trim())
274
+ .filter(Boolean);
275
+ }
276
+ function tenantColumnNames(opts) {
277
+ const extra = opts.tenantColumn ? [opts.tenantColumn] : [];
278
+ const all = [...extra, ...(opts.tenantColumns ?? DEFAULT_TENANT_COLUMNS)];
279
+ return [...new Set(all)];
280
+ }
281
+ function tableExempt(row, exempt) {
282
+ if (!exempt || exempt.length === 0)
283
+ return false;
284
+ return (exempt.includes(row.tablename) ||
285
+ exempt.includes(`${row.schemaname}.${row.tablename}`));
286
+ }
287
+ /**
288
+ * THE TABLE-LEVEL POSITIVE ASSERTION.
289
+ *
290
+ * Discovers tenant-scoped tables FROM THE CATALOG — every table carrying a
291
+ * `tenant_id` column — rather than from a caller-supplied list, and requires
292
+ * each to have `relrowsecurity = true`.
293
+ *
294
+ * The discovery is the entire point, and it is why this does not simply reuse
295
+ * `verifyRls`. `verifyRls` takes `requiredTables`: the caller enumerates the
296
+ * tables that need RLS. But the failure being defended against is a migration
297
+ * that adds a tenant-scoped table and forgets to protect it — and an author who
298
+ * forgets `ENABLE ROW LEVEL SECURITY` in that migration is the same author who
299
+ * forgets to add the table to the list, in the same change. A hand-maintained
300
+ * list is blind to precisely the tables at risk, which is why `verifyRls` can
301
+ * sit in a repo, wired and green, while twelve unprotected tables accumulate
302
+ * behind it. Asking the catalog "which tables have a tenant column?" cannot be
303
+ * forgotten, because nobody has to remember anything.
304
+ *
305
+ * Tables WITHOUT a tenant column are deliberately not considered: a
306
+ * platform-global reference table is GRANT-controlled with no RLS by design
307
+ * (08-rls § 7.6), and flagging those would train everyone to pass
308
+ * `exemptTables` until the check means nothing.
309
+ */
310
+ async function checkTenantTableCoverage(querier, schemas, opts) {
311
+ const tenantColumns = tenantColumnNames(opts);
312
+ // relkind: r = table, p = partitioned table, v = view, m = matview,
313
+ // f = foreign table. The original version filtered `relkind = 'r'`, which
314
+ // silently exempted the four kinds that CANNOT protect themselves — and a
315
+ // view over a tenant table is an ordinary migration artifact, not an exotic
316
+ // case. Partitioned parents were excluded too, despite being where the
317
+ // policies for an entire partition set are declared.
318
+ const { rows } = await querier.query(`SELECT n.nspname AS schemaname,
319
+ c.relname AS tablename,
320
+ c.relrowsecurity,
321
+ c.relkind::text AS relkind,
322
+ COALESCE(
323
+ (SELECT true FROM unnest(COALESCE(c.reloptions, '{}')) o
324
+ WHERE lower(replace(o, ' ', '')) = 'security_invoker=true'),
325
+ false) AS security_invoker,
326
+ (SELECT a.attname FROM pg_attribute a
327
+ WHERE a.attrelid = c.oid AND a.attnum > 0 AND NOT a.attisdropped
328
+ AND a.attname = ANY($2::text[]) LIMIT 1) AS tenant_column
329
+ FROM pg_class c
330
+ JOIN pg_namespace n ON n.oid = c.relnamespace
331
+ WHERE c.relkind = ANY('{r,p,v,m,f}')
332
+ AND n.nspname = ANY($1::text[])
333
+ AND EXISTS (
334
+ SELECT 1 FROM pg_attribute a
335
+ WHERE a.attrelid = c.oid
336
+ AND a.attname = ANY($2::text[])
337
+ AND a.attnum > 0
338
+ AND NOT a.attisdropped
339
+ )
340
+ ORDER BY n.nspname, c.relname`, [schemas, tenantColumns]);
341
+ const wanted = opts.tables?.map((t) => t.split(".").pop() ?? t);
342
+ const out = [];
343
+ for (const row of rows) {
344
+ if (wanted && !wanted.includes(row.tablename))
345
+ continue;
346
+ if (tableExempt(row, opts.exemptTables))
347
+ continue;
348
+ const col = row.tenant_column ?? tenantColumns[0];
349
+ if (row.relkind === "v" || row.relkind === "m" || row.relkind === "f") {
350
+ // A view with security_invoker=true executes as the CALLER, so the
351
+ // underlying table's RLS applies and the view is genuinely safe.
352
+ if (row.relkind === "v" && row.security_invoker)
353
+ continue;
354
+ out.push({
355
+ object: `${row.schemaname}.${row.tablename}`,
356
+ policy: `(${RELKIND_LABEL[row.relkind] ?? row.relkind})`,
357
+ command: "-",
358
+ reason: "unprotected_relation",
359
+ expression: `${row.tablename} exposes ${col}; relkind=${row.relkind}${row.relkind === "v" ? ", security_invoker=false" : ", RLS is not supported on this relkind"}`,
360
+ });
361
+ continue;
362
+ }
363
+ if (row.relrowsecurity)
364
+ continue;
365
+ out.push({
366
+ object: `${row.schemaname}.${row.tablename}`,
367
+ policy: "(no policies — RLS is OFF)",
368
+ command: "-",
369
+ reason: "rls_disabled_on_tenant_table",
370
+ expression: `${row.tablename} has a ${col} column; relrowsecurity = false`,
371
+ });
372
+ }
373
+ return out;
374
+ }
375
+ /**
376
+ * Flags UNIQUE indexes on tenant-scoped tables whose KEY omits the tenant
377
+ * column.
378
+ *
379
+ * RLS decides which rows a session may SEE. It does not partition the
380
+ * uniqueness namespace, and the two are independent in a way that is easy to
381
+ * miss: a table can be flawlessly fenced and still let one tenant reach
382
+ * another's row through a unique key.
383
+ *
384
+ * The live exploit that motivated this: a queue table with a caller-supplied
385
+ * `dedupe_key` carrying `UNIQUE (dedupe_key)` rather than
386
+ * `UNIQUE (tenant_id, dedupe_key)`. Tenant A submits the key tenant B already
387
+ * used; the idempotent-mint lookup — a `SELECT` with no tenant predicate —
388
+ * finds B's row and the endpoint returns it. Even with RLS correctly enabled,
389
+ * the collision still surfaces as a 23505 that leaks the row's existence, and
390
+ * an `ON CONFLICT DO UPDATE` still targets a row the session cannot see.
391
+ *
392
+ * KEY COLUMNS COME FROM THE CATALOG, NOT FROM `indexdef`. The first version
393
+ * sliced the DDL text from the first `(` to the LAST `)`, which swallowed
394
+ * everything a unique index can legally carry after its key list, and then
395
+ * substring-matched the tenant column inside it. Both of these were therefore
396
+ * SKIPPED while remaining fully exploitable:
397
+ *
398
+ * UNIQUE (dedupe_key) WHERE (tenant_id IS NOT NULL) -- partial predicate
399
+ * UNIQUE (dedupe_key) INCLUDE (tenant_id) -- non-key payload
400
+ *
401
+ * A partial `WHERE` narrows which rows participate, not which tenants share the
402
+ * namespace; an `INCLUDE` column is payload and is not part of the uniqueness
403
+ * key at all. `pg_index.indnkeyatts` knows the difference and text-slicing
404
+ * cannot.
405
+ */
406
+ async function checkTenantUniqueIndexes(querier, schemas, opts) {
407
+ const tenantColumns = tenantColumnNames(opts);
408
+ const { rows } = await querier.query(`SELECT n.nspname AS schemaname,
409
+ t.relname AS tablename,
410
+ ic.relname AS indexname,
411
+ pg_get_indexdef(i.indexrelid) AS indexdef,
412
+ i.indisprimary AS is_primary,
413
+ ARRAY(
414
+ SELECT a.attname
415
+ FROM generate_subscripts(i.indkey, 1) AS k
416
+ JOIN pg_attribute a
417
+ ON a.attrelid = i.indrelid AND a.attnum = i.indkey[k]
418
+ WHERE k < i.indnkeyatts
419
+ ) AS key_columns,
420
+ NOT EXISTS (
421
+ SELECT 1
422
+ FROM generate_subscripts(i.indkey, 1) AS k
423
+ JOIN pg_attribute a
424
+ ON a.attrelid = i.indrelid AND a.attnum = i.indkey[k]
425
+ WHERE k < i.indnkeyatts
426
+ AND a.atthasdef IS NOT TRUE
427
+ AND a.attidentity = ''
428
+ ) AS all_keys_generated
429
+ FROM pg_index i
430
+ JOIN pg_class t ON t.oid = i.indrelid
431
+ JOIN pg_class ic ON ic.oid = i.indexrelid
432
+ JOIN pg_namespace n ON n.oid = t.relnamespace
433
+ WHERE i.indisunique
434
+ AND n.nspname = ANY($1::text[])
435
+ AND EXISTS (
436
+ SELECT 1 FROM pg_attribute a
437
+ WHERE a.attrelid = t.oid
438
+ AND a.attname = ANY($2::text[])
439
+ AND a.attnum > 0
440
+ AND NOT a.attisdropped
441
+ )
442
+ ORDER BY t.relname, ic.relname`, [schemas, tenantColumns]);
443
+ const wanted = opts.tables?.map((t) => t.split(".").pop() ?? t);
444
+ const out = [];
445
+ for (const row of rows) {
446
+ if (wanted && !wanted.includes(row.tablename))
447
+ continue;
448
+ if (tableExempt(row, opts.exemptTables))
449
+ continue;
450
+ // `RlsQuerier` is driver-agnostic: node-postgres hands back a parsed
451
+ // string[] for text[], postgres.js and a raw wire read can hand back the
452
+ // literal `{a,b}`. Normalise rather than assume — a silently-empty key list
453
+ // would make every index look unsafe (or, with the test inverted, safe).
454
+ const keyCols = toStringArray(row.key_columns);
455
+ // The tenant column is in the KEY — the namespace is partitioned. Safe.
456
+ if (keyCols.some((c) => tenantColumns.includes(c)))
457
+ continue;
458
+ // A SURROGATE primary key is safe, and only a surrogate one. The original
459
+ // exclusion dropped every `contype = 'p'` index on the rationale that "no
460
+ // tenant chooses its value" — true of `id uuid DEFAULT gen_random_uuid()`,
461
+ // false of `dedupe_key text PRIMARY KEY`, which is a caller-supplied
462
+ // natural key and is exactly the shape that was exploited. The honest test
463
+ // is not "is it the primary key" but "did the SERVER generate every key
464
+ // column", which `atthasdef` / `attidentity` answer directly.
465
+ if (row.is_primary && row.all_keys_generated)
466
+ continue;
467
+ out.push({
468
+ object: `${row.schemaname}.${row.tablename}`,
469
+ policy: row.indexname,
470
+ command: "-",
471
+ reason: "tenant_unscoped_unique_index",
472
+ expression: `${row.indexdef} [key columns: ${keyCols.join(", ")}]`,
473
+ });
474
+ }
475
+ return out;
476
+ }
477
+ async function checkResolverHelpers(querier, allowDegraded) {
478
+ const out = [];
479
+ const { rows: procs } = await querier.query(`SELECT p.proname, p.prosrc, p.prosecdef
480
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
481
+ WHERE n.nspname = 'auth'
482
+ AND p.proname IN ('session_tenant_ids','user_tenant_ids')`, []);
483
+ const resolver = procs.find((p) => p.proname === "session_tenant_ids");
484
+ const membership = procs.find((p) => p.proname === "user_tenant_ids");
485
+ if (!resolver) {
486
+ out.push({
487
+ object: "auth.session_tenant_ids()",
488
+ policy: "session_tenant_ids",
489
+ command: "-",
490
+ reason: "resolver_missing",
491
+ expression: null,
492
+ });
493
+ return out;
494
+ }
495
+ if (!/RAISE\s+EXCEPTION/i.test(resolver.prosrc)) {
496
+ out.push({
497
+ object: "auth.session_tenant_ids()",
498
+ policy: "session_tenant_ids",
499
+ command: "-",
500
+ reason: "resolver_not_loud",
501
+ expression: resolver.prosrc.slice(0, 400),
502
+ });
503
+ }
504
+ if (!resolver.prosecdef) {
505
+ out.push({
506
+ object: "auth.session_tenant_ids()",
507
+ policy: "session_tenant_ids",
508
+ command: "-",
509
+ reason: "resolver_not_security_definer",
510
+ expression: resolver.prosrc.slice(0, 400),
511
+ });
512
+ }
513
+ const degraded = resolver.prosrc.includes(SESSION_TENANT_IDS_DEGRADED_MARKER);
514
+ if (degraded && !allowDegraded) {
515
+ out.push({
516
+ object: "auth.session_tenant_ids()",
517
+ policy: "session_tenant_ids",
518
+ command: "-",
519
+ reason: "resolver_degraded",
520
+ expression: resolver.prosrc.slice(0, 400),
521
+ });
522
+ }
523
+ // The stub check only matters for the STRICT resolver: the degraded one never
524
+ // consults `user_tenant_ids`, so a stub there is expected, not a defect.
525
+ if (!degraded && membership && isStubMembershipHelper(membership.prosrc)) {
526
+ out.push({
527
+ object: "auth.user_tenant_ids(uuid)",
528
+ policy: "user_tenant_ids",
529
+ command: "-",
530
+ reason: "membership_helper_is_stub",
531
+ expression: membership.prosrc.trim().slice(0, 400),
532
+ });
533
+ }
534
+ return out;
535
+ }
536
+ /** The bootstrap stub is `SELECT NULL::uuid WHERE false` — any body that can
537
+ * never return a row. Matched structurally rather than by exact string so a
538
+ * reformatted copy is still caught. */
539
+ export function isStubMembershipHelper(prosrc) {
540
+ return /where\s+false/i.test(prosrc) && !/from\s+\w/i.test(prosrc);
541
+ }
542
+ //# sourceMappingURL=tenant-fence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-fence.js","sourceRoot":"","sources":["../src/tenant-fence.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -118,6 +118,24 @@ export declare class PoolUnavailable extends DbRlsError {
118
118
  export declare class MissingTenantContext extends DbRlsError {
119
119
  constructor();
120
120
  }
121
+ /**
122
+ * Thrown when something asks for a database session that would let a tenant
123
+ * user reach more than one tenant's rows.
124
+ *
125
+ * The only caller that triggers this today is `withUserMembershipContext`,
126
+ * which is retained as a throwing stub so a JavaScript consumer gets a sentence
127
+ * rather than `undefined is not a function`. It is a runtime throw and not
128
+ * merely a removed export because the call sites live in services that do not
129
+ * all typecheck against this package at build time.
130
+ *
131
+ * Nodii STAFF cross-tenant access is a different path entirely and is
132
+ * unaffected: the separate, RBAC-gated, audit-stamped `platform_ops` pool
133
+ * (`withPlatformOpsContext`).
134
+ */
135
+ export declare class CrossTenantSessionRefused extends DbRlsError {
136
+ readonly helper: string;
137
+ constructor(helper: string);
138
+ }
121
139
  export declare class TransactionRolledBack extends DbRlsError {
122
140
  readonly cause: unknown;
123
141
  constructor(cause: unknown);
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAoBA;;;;;wDAKwD;AACxD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,eAAe,GAAG,cAAc,CAAC;AAE/E;;wBAEwB;AACxB,MAAM,WAAW,IAAI;IACnB,6CAA6C;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,uDAAuD;IACvD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,oEAAoE;IACpE,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9D;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE;;kDAEkD;AAClD,MAAM,WAAW,WAAW;IAC1B,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,4BAA4B;AAC5B,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8BAA8B;AAC9B,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yBAAyB;AACzB,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,8BAA8B;AAC9B,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,UAAU;;CAOlD;AAED,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;gBACZ,IAAI,EAAE,QAAQ;CAK3B;AAED,qBAAa,oBAAqB,SAAQ,UAAU;;CAOnD;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAA0B,SAAQ,UAAU;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,MAAM;CAO3B;AAED,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBACZ,KAAK,EAAE,OAAO;CAS3B;AAED;;;;;;;;GAQG;AACH,qBAAa,0BAA2B,SAAQ,UAAU;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,MAAM;CAO7B;AAED;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;gBAClC,OAAO,EAAE,MAAM;CAI5B"}
package/dist/types.js CHANGED
@@ -45,6 +45,28 @@ export class MissingTenantContext extends DbRlsError {
45
45
  this.name = "MissingTenantContext";
46
46
  }
47
47
  }
48
+ /**
49
+ * Thrown when something asks for a database session that would let a tenant
50
+ * user reach more than one tenant's rows.
51
+ *
52
+ * The only caller that triggers this today is `withUserMembershipContext`,
53
+ * which is retained as a throwing stub so a JavaScript consumer gets a sentence
54
+ * rather than `undefined is not a function`. It is a runtime throw and not
55
+ * merely a removed export because the call sites live in services that do not
56
+ * all typecheck against this package at build time.
57
+ *
58
+ * Nodii STAFF cross-tenant access is a different path entirely and is
59
+ * unaffected: the separate, RBAC-gated, audit-stamped `platform_ops` pool
60
+ * (`withPlatformOpsContext`).
61
+ */
62
+ export class CrossTenantSessionRefused extends DbRlsError {
63
+ helper;
64
+ constructor(helper) {
65
+ super(`cross_tenant_session_refused: ${helper}() opened an authenticated-pool session with NO tenant bound, so the RLS predicate resolved across EVERY tenant the principal belonged to. That is the mechanism of the cross-tenant approval incident, and it is no longer available. Use withUserContext({ userId, tenantId }, fn) with the tenant the request resolved to. To LIST a user's tenants (switcher / login picker), query auth.user_tenant_ids from a services-pool context — that function is intact; what is gone is opening a data session that spans them. Nodii staff cross-tenant access is unchanged: withPlatformOpsContext.`);
66
+ this.name = "CrossTenantSessionRefused";
67
+ this.helper = helper;
68
+ }
69
+ }
48
70
  export class TransactionRolledBack extends DbRlsError {
49
71
  cause;
50
72
  constructor(cause) {
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,oEAAoE;AACpE,uEAAuE;AACvE,EAAE;AACF,gBAAgB;AAChB,qDAAqD;AACrD,uEAAuE;AACvE,0EAA0E;AAC1E,oDAAoD;AACpD,0EAA0E;AAC1E,wBAAwB;AACxB,+DAA+D;AAC/D,iDAAiD;AACjD,EAAE;AACF,uCAAuC;AACvC,0CAA0C;AAC1C,kDAAkD;AAClD,0EAA0E;AAwH1E,+EAA+E;AAC/E,yBAAyB;AAEzB,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,UAAU;IACjD;QACE,KAAK,CACH,uHAAuH,CACxH,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,UAAU;IACpC,IAAI,CAAW;IACxB,YAAY,IAAc;QACxB,KAAK,CAAC,yBAAyB,IAAI,sCAAsC,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,UAAU;IAClD;QACE,KAAK,CACH,+EAA+E,CAChF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,UAAU;IAC1C,KAAK,CAAU;IACxB,YAAY,KAAc;QACxB,KAAK,CACH,4BACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,0BAA2B,SAAQ,UAAU;IAC/C,QAAQ,CAAS;IAC1B,YAAY,QAAgB;QAC1B,KAAK,CACH,4CAA4C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,gOAAgO,CACrS,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,oEAAoE;AACpE,uEAAuE;AACvE,EAAE;AACF,gBAAgB;AAChB,qDAAqD;AACrD,uEAAuE;AACvE,0EAA0E;AAC1E,oDAAoD;AACpD,0EAA0E;AAC1E,wBAAwB;AACxB,+DAA+D;AAC/D,iDAAiD;AACjD,EAAE;AACF,uCAAuC;AACvC,0CAA0C;AAC1C,kDAAkD;AAClD,0EAA0E;AAwH1E,+EAA+E;AAC/E,yBAAyB;AAEzB,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,UAAU;IACjD;QACE,KAAK,CACH,uHAAuH,CACxH,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,UAAU;IACpC,IAAI,CAAW;IACxB,YAAY,IAAc;QACxB,KAAK,CAAC,yBAAyB,IAAI,sCAAsC,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,UAAU;IAClD;QACE,KAAK,CACH,+EAA+E,CAChF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,yBAA0B,SAAQ,UAAU;IAC9C,MAAM,CAAS;IACxB,YAAY,MAAc;QACxB,KAAK,CACH,iCAAiC,MAAM,okBAAokB,CAC5mB,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,UAAU;IAC1C,KAAK,CAAU;IACxB,YAAY,KAAc;QACxB,KAAK,CACH,4BACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,0BAA2B,SAAQ,UAAU;IAC/C,QAAQ,CAAS;IAC1B,YAAY,QAAgB;QAC1B,KAAK,CACH,4CAA4C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,gOAAgO,CACrS,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"verify-rls-cli.d.ts","sourceRoot":"","sources":["../src/verify-rls-cli.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAqDhC"}
1
+ {"version":3,"file":"verify-rls-cli.d.ts","sourceRoot":"","sources":["../src/verify-rls-cli.ts"],"names":[],"mappings":"AAmFA,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;CACjB;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAsEhC"}