@rebasepro/rls-check 0.16.0 → 0.16.1-canary.g0d7af95

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/README.md CHANGED
@@ -188,6 +188,9 @@ npx @rebasepro/rls-check [connection-string] [options]
188
188
 
189
189
  --json Machine-readable ScanResult on stdout, and nothing else.
190
190
  --schema <name> Restrict the scan to a schema. Repeatable or comma-separated.
191
+ --role <name> Treat this role as one an untrusted caller arrives as, in
192
+ addition to anon, authenticated, web_anon and rebase_user.
193
+ Repeatable or comma-separated.
191
194
  --fail-on <severity> Exit 1 at or above this severity: info, low, medium, high,
192
195
  critical, or none to never fail. Default: high.
193
196
  --only <id> Run only these checks. Repeatable or comma-separated.
@@ -293,6 +296,7 @@ Being clear about this is the point of the tool. It is a **static audit of the c
293
296
  - **It does not execute queries as other roles.** It never connects as `anon`, never sets a JWT claim, and never tries to read a row it should not be able to read. Everything it reports is inferred from what the catalogs say, not observed.
294
297
  - **It cannot prove a policy is correct.** Deciding whether `owner_id = auth.uid()` is the right rule for your application requires knowing your application. `rls-check` can only tell you that certain *shapes* are wrong — a policy that is always true, a view that runs as its owner, a table with RLS switched off.
295
298
  - **A clean report is not a security certification.** It means these fourteen checks found nothing. It does not mean your authorization model is sound, your API layer enforces what it should, or your data is safe.
299
+ - **It recognises app roles by name, and yours may not be one of them.** Every check reports a table as exposed only when a role an untrusted caller can arrive as holds privileges on it. Out of the box that means `PUBLIC`, Supabase's `anon` and `authenticated`, PostgREST's `web_anon`, and Rebase's `rebase_user`. If your application connects as `app_user`, `api` or anything else, name it — `--role app_user` — or the checks have nothing to gate on. A scan that finds a write-holding role it cannot account for says so in a `Note` rather than printing a clean report.
296
300
  - **It does not model your API layer.** Whether a table is actually reachable depends on PostgREST, your server, or your gateway. Findings say "if this table is exposed over an API" when reachability depends on something outside the database — believe that qualifier.
297
301
  - **It does not see what your connection sees.** Almost every connection string handed to a tool like this belongs to a superuser or a table owner, which RLS cannot constrain. That is what lets it read the true catalog; it also means the findings describe what *other* roles get. The report says so, prominently, every time it applies.
298
302
  - **Heuristic checks produce false positives by design.** Junction-table inference and unqualified-column detection match a shape, not a proof. They are reported in a separate section for exactly that reason.
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * A write policy that lets an unauthenticated caller through, backed by a real grant.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * `current_setting('x')` in a policy, without the `missing_ok` second argument.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * A DML privilege granted to PUBLIC.
4
4
  *
@@ -6,7 +6,7 @@
6
6
  * is not, which is why the list is ordered by severity rather than alphabetically
7
7
  * — it is read by humans deciding what to look at first.
8
8
  */
9
- import type { Check, DbSnapshot, Finding } from "../types";
9
+ import type { Check, DbSnapshot, Finding } from "../types.js";
10
10
  export declare const CHECKS: Check[];
11
11
  export interface RunOptions {
12
12
  /** Run only these check ids. */
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * The edge table of a many-to-many relationship, left without RLS while both
4
4
  * endpoints have it.
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * A materialized view over an RLS-protected table, readable by an untrusted role.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * A PERMISSIVE policy whose expression is a constant truth, targeted at a role
4
4
  * an untrusted caller reaches.
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * `auth.uid() IS NOT NULL` and its relatives.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * Every policy on a table names roles that nothing can arrive as.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * A table with RLS off *and* a DML grant to a role an untrusted caller arrives as.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * RLS on, zero policies — deny-all for everyone except the table's owner.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * RLS on, FORCE off — the table owner is exempt from its own policies.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * A SECURITY DEFINER routine whose `search_path` is not pinned.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { Check } from "../types";
1
+ import type { Check } from "../types.js";
2
2
  /**
3
3
  * A bare column reference inside a policy subquery that resolves to the *inner*
4
4
  * relation when the outer row was meant.
@@ -6,7 +6,7 @@
6
6
  * noise, and noise is what gets a tool like this uninstalled — so the helpers
7
7
  * below err towards "no" whenever the catalog is ambiguous.
8
8
  */
9
- import type { DbGrant, DbPolicy, DbRelation, DbSnapshot, Finding, Severity } from "../types";
9
+ import type { DbGrant, DbPolicy, DbRelation, DbSnapshot, Finding, Severity } from "../types.js";
10
10
  export declare const SEVERITY_ORDER: Severity[];
11
11
  export declare const docsFor: (id: string) => string;
12
12
  export type Privilege = DbGrant["privileges"][number];
@@ -1,4 +1,4 @@
1
- import type { Check, DbSnapshot, DbView } from "../types";
1
+ import type { Check, DbSnapshot, DbView } from "../types.js";
2
2
  /** Base relations of `view` that have RLS turned on. */
3
3
  export declare function protectedBaseTables(snapshot: DbSnapshot, view: DbView): string[];
4
4
  /**
package/dist/cli.d.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * could have come from `pg`, Node or the user goes through `redactSecrets`
19
19
  * on its way out.
20
20
  */
21
- import type { ScanResult, Severity } from "./types";
21
+ import type { ScanResult, Severity } from "./types.js";
22
22
  /** Clean, or nothing at or above `--fail-on`. */
23
23
  export declare const EXIT_OK = 0;
24
24
  /** Findings at or above `--fail-on`. */
@@ -29,6 +29,12 @@ export interface ScanOptions {
29
29
  connectionString: string;
30
30
  /** Restrict to these schemas. Empty or omitted means "every user schema". */
31
31
  schemas?: string[];
32
+ /**
33
+ * Additional roles an untrusted caller can arrive as, on top of the names
34
+ * this tool recognises. Needed by any stack whose app role is not called
35
+ * `anon`, `authenticated`, `web_anon` or `rebase_user`.
36
+ */
37
+ roles?: string[];
32
38
  /** Run only these check ids. */
33
39
  only?: string[];
34
40
  /** Skip these check ids. */
@@ -75,7 +81,17 @@ export declare function exitCodeFor(result: ScanResult, failOn: Severity | "none
75
81
  export interface CliOptions {
76
82
  connectionString: string | null;
77
83
  json: boolean;
84
+ /**
85
+ * `--html <path>`: also write a self-contained HTML report there.
86
+ *
87
+ * "Also", not "instead": the terminal output is what a CI log keeps, and
88
+ * the file is what gets forwarded to whoever owns the database. Making the
89
+ * flag replace stdout would trade one audience for the other.
90
+ */
91
+ html: string | null;
78
92
  schemas: string[];
93
+ /** Extra roles to treat as reachable by an untrusted caller. */
94
+ roles: string[];
79
95
  failOn: Severity | "none";
80
96
  skip: string[];
81
97
  only: string[];
@@ -138,6 +154,11 @@ export declare function resolveVersion(): string;
138
154
  export interface CliIo {
139
155
  stdout(text: string): void;
140
156
  stderr(text: string): void;
157
+ /**
158
+ * Write a file, for `--html`. Injected rather than imported so the CLI
159
+ * tests can exercise the whole path without touching a disk.
160
+ */
161
+ writeFile?(path: string, contents: string): void;
141
162
  env: NodeJS.ProcessEnv;
142
163
  cwd: string;
143
164
  /** Whether stdout is a terminal; drives colour when nothing else decides. */
package/dist/index.d.ts CHANGED
@@ -16,14 +16,16 @@
16
16
  * at production databases by people who have never heard of us, and the install
17
17
  * has to be something they can read in full before they run it.
18
18
  */
19
- export { runCli, scan, selectCheckIds, EXIT_OK, EXIT_FINDINGS, EXIT_ERROR } from "./cli";
20
- export type { ScanOptions, CliIo, CliOptions } from "./cli";
21
- export { introspect } from "./introspect";
22
- export type { ConnectOptions } from "./introspect";
23
- export { CHECKS, runChecks } from "./checks";
24
- export { renderReport, renderJson, renderCheckCatalog, severityRank, maxSeverity, exceedsThreshold, formatTarget } from "./report";
25
- export type { RenderOptions } from "./report";
26
- export { redactConnectionString, redactSecrets, parseConnectionString, formatEndpoint } from "./redact";
27
- export type { ConnectionTarget } from "./redact";
28
- export { SEVERITIES } from "./types";
29
- export type { Check, DbColumn, DbForeignKey, DbGrant, DbPolicy, DbRelation, DbRole, DbRoutine, DbSnapshot, DbView, Finding, FindingTarget, PolicyCommand, PgRelationKind, ScanResult, Severity } from "./types";
19
+ export { runCli, scan, selectCheckIds, EXIT_OK, EXIT_FINDINGS, EXIT_ERROR } from "./cli.js";
20
+ export type { ScanOptions, CliIo, CliOptions } from "./cli.js";
21
+ export { introspect } from "./introspect.js";
22
+ export type { ConnectOptions } from "./introspect.js";
23
+ export { CHECKS, runChecks } from "./checks/index.js";
24
+ export { renderReport, renderJson, renderCheckCatalog, severityRank, maxSeverity, exceedsThreshold, formatTarget } from "./report.js";
25
+ export type { RenderOptions } from "./report.js";
26
+ export { renderHtml, escapeHtml } from "./report-html.js";
27
+ export type { HtmlRenderOptions } from "./report-html.js";
28
+ export { redactConnectionString, redactSecrets, parseConnectionString, formatEndpoint } from "./redact.js";
29
+ export type { ConnectionTarget } from "./redact.js";
30
+ export { SEVERITIES } from "./types.js";
31
+ export type { Check, DbColumn, DbForeignKey, DbGrant, DbPolicy, DbRelation, DbRole, DbRoutine, DbSnapshot, DbView, Finding, FindingTarget, PolicyCommand, PgRelationKind, ScanResult, Severity } from "./types.js";