@rebasepro/rls-check 0.22.0 → 0.23.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.
- package/README.md +3 -3
- package/dist/checks/policy-always-true.d.ts +2 -2
- package/dist/checks/rls-disabled.d.ts +4 -0
- package/dist/checks/rls-enabled-not-forced.d.ts +11 -4
- package/dist/checks/util.d.ts +15 -1
- package/dist/checks/view-bypasses-rls.d.ts +2 -2
- package/dist/cli.d.ts +8 -21
- package/dist/index.es.js +423 -143
- package/dist/index.es.js.map +1 -1
- package/dist/introspect.d.ts +82 -1
- package/dist/report.d.ts +32 -0
- package/package.json +2 -2
package/dist/introspect.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClientConfig } from "pg";
|
|
2
|
-
import type { DbSnapshot } from "./types.js";
|
|
2
|
+
import type { DbForeignKey, DbGrant, DbRelation, DbSnapshot, DbView } from "./types.js";
|
|
3
3
|
export interface ConnectOptions {
|
|
4
4
|
connectionString: string;
|
|
5
5
|
/** Explicit schema allowlist. Default: every non-system schema. */
|
|
@@ -80,8 +80,24 @@ export declare class UnknownRoleError extends Error {
|
|
|
80
80
|
readonly roles: string[];
|
|
81
81
|
constructor(roles: string[]);
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* A `--schema` that is not in `pg_namespace`.
|
|
85
|
+
*
|
|
86
|
+
* The same failure as an unknown `--role`, and worse: a typo such as
|
|
87
|
+
* `--schema pubic`, or `--schema Public` for a schema named `public`, scanned
|
|
88
|
+
* nothing at all. Zero tables, "No findings", exit 0, for as long as the typo
|
|
89
|
+
* sat in a CI job. A name that matches nothing only ever narrows the scan, so
|
|
90
|
+
* it is an error.
|
|
91
|
+
*/
|
|
92
|
+
export declare class UnknownSchemaError extends Error {
|
|
93
|
+
readonly schemas: string[];
|
|
94
|
+
constructor(schemas: string[]);
|
|
95
|
+
}
|
|
83
96
|
export declare function introspect(opts: ConnectOptions): Promise<DbSnapshot>;
|
|
84
97
|
export declare function introspectWithDiagnostics(opts: ConnectOptions): Promise<IntrospectResult>;
|
|
98
|
+
type SslOption = false | {
|
|
99
|
+
rejectUnauthorized: boolean;
|
|
100
|
+
};
|
|
85
101
|
/**
|
|
86
102
|
* The keywords in a libpq keyword string that this tool cannot honour, in the
|
|
87
103
|
* order they were written. Empty for a URL, and empty for a keyword string it
|
|
@@ -95,6 +111,17 @@ export declare function unsupportedConnectionKeywords(connectionString: string):
|
|
|
95
111
|
* failure would still be reported against the host the user typed.
|
|
96
112
|
*/
|
|
97
113
|
export declare function clientConfigFromKeywords(keywords: Map<string, string>): ClientConfig;
|
|
114
|
+
/**
|
|
115
|
+
* The TLS postures to try, in order, for an `sslmode`. Each entry says whether
|
|
116
|
+
* certificate verification had to be given up to get there.
|
|
117
|
+
*/
|
|
118
|
+
export declare function connectionAttempts(sslmode: string | undefined): {
|
|
119
|
+
ssl: SslOption;
|
|
120
|
+
downgraded: boolean;
|
|
121
|
+
}[];
|
|
122
|
+
export interface Reader {
|
|
123
|
+
query<R extends Record<string, unknown>>(what: string, text: string, values?: unknown[]): Promise<R[]>;
|
|
124
|
+
}
|
|
98
125
|
/**
|
|
99
126
|
* Which of the database's schemas this scan covers.
|
|
100
127
|
*
|
|
@@ -103,3 +130,57 @@ export declare function clientConfigFromKeywords(keywords: Map<string, string>):
|
|
|
103
130
|
* {@link PLATFORM_SCHEMAS} silently stops being checked.
|
|
104
131
|
*/
|
|
105
132
|
export declare function selectSchemas(all: string[], requested: string[] | undefined, diagnostics: IntrospectDiagnostics): string[];
|
|
133
|
+
/**
|
|
134
|
+
* Relations a scanned view reads or a scanned foreign key points at that the
|
|
135
|
+
* scan did not read, each named once.
|
|
136
|
+
*
|
|
137
|
+
* View dependencies are read across schemas on purpose, and foreign keys
|
|
138
|
+
* always were — but a dependency on a relation that is not in the snapshot
|
|
139
|
+
* resolves to nothing. `CREATE VIEW public.files AS SELECT * FROM
|
|
140
|
+
* storage.objects`, granted to anon, produced no finding on a default scan
|
|
141
|
+
* (which leaves `storage` out), and a junction table pointing at `auth.users`
|
|
142
|
+
* was never considered.
|
|
143
|
+
*/
|
|
144
|
+
export declare function outOfScopeReferences(relations: DbRelation[], views: DbView[], foreignKeys: DbForeignKey[]): {
|
|
145
|
+
schema: string;
|
|
146
|
+
name: string;
|
|
147
|
+
}[];
|
|
148
|
+
/**
|
|
149
|
+
* Read the relations {@link outOfScopeReferences} names. Columns are not read:
|
|
150
|
+
* nothing judges a referenced relation by its columns, only by whether it has
|
|
151
|
+
* row-level security.
|
|
152
|
+
*/
|
|
153
|
+
export declare function readReferencedRelations(db: Reader, refs: {
|
|
154
|
+
schema: string;
|
|
155
|
+
name: string;
|
|
156
|
+
}[]): Promise<DbRelation[]>;
|
|
157
|
+
/**
|
|
158
|
+
* Privileges on each scanned relation, from the relation's ACL and from its
|
|
159
|
+
* columns' ACLs.
|
|
160
|
+
*
|
|
161
|
+
* A column privilege is a privilege on the relation for what these checks ask:
|
|
162
|
+
* `GRANT SELECT (id, email) ON users TO anon` on a table with RLS off hands
|
|
163
|
+
* anon those columns of every row, and a column-level UPDATE grant next to a
|
|
164
|
+
* `USING (true)` policy lets anon rewrite them. Reading `relacl` alone missed
|
|
165
|
+
* both, so `rls-disabled` and `anonymous-write-allowed` said nothing.
|
|
166
|
+
*/
|
|
167
|
+
export declare function readGrants(db: Reader, schemas: string[]): Promise<DbGrant[]>;
|
|
168
|
+
export interface ViewDependencyRow extends Record<string, unknown> {
|
|
169
|
+
view_schema: string;
|
|
170
|
+
view_name: string;
|
|
171
|
+
ref_schema: string;
|
|
172
|
+
ref_name: string;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The transitive closure of direct view dependencies, keyed by
|
|
176
|
+
* `JSON.stringify([schema, name])`.
|
|
177
|
+
*
|
|
178
|
+
* Every view with a dependency of its own is expanded, wherever it lives, and
|
|
179
|
+
* names are kept as pairs: `schema.name` split at the first dot is wrong for a
|
|
180
|
+
* schema whose name has a dot in it.
|
|
181
|
+
*/
|
|
182
|
+
export declare function resolveViewDependencies(rows: ViewDependencyRow[]): Map<string, {
|
|
183
|
+
schema: string;
|
|
184
|
+
table: string;
|
|
185
|
+
}[]>;
|
|
186
|
+
export {};
|
package/dist/report.d.ts
CHANGED
|
@@ -24,6 +24,38 @@ export declare function maxSeverity(findings: readonly Finding[]): Severity | nu
|
|
|
24
24
|
* would make the exit code disagree with the report.
|
|
25
25
|
*/
|
|
26
26
|
export declare function exceedsThreshold(findings: readonly Finding[], failOn: Severity | "none"): boolean;
|
|
27
|
+
/** Clean, or nothing at or above `--fail-on`. */
|
|
28
|
+
export declare const EXIT_OK = 0;
|
|
29
|
+
/** Findings at or above `--fail-on`. */
|
|
30
|
+
export declare const EXIT_FINDINGS = 1;
|
|
31
|
+
/** The scan did not happen: bad arguments, bad connection, timeout. */
|
|
32
|
+
export declare const EXIT_ERROR = 2;
|
|
33
|
+
/**
|
|
34
|
+
* The verdict, as an exit code.
|
|
35
|
+
*
|
|
36
|
+
* Pulled out of `runCli` so it can be tested: `runCli` needs a database, and
|
|
37
|
+
* the one line that decides whether CI goes red had no coverage at all —
|
|
38
|
+
* deleting it broke no test.
|
|
39
|
+
*
|
|
40
|
+
* A degraded scan exits 2, the same code a crash uses, rather than 0. Checks
|
|
41
|
+
* whose catalogue reads failed return no findings, which is indistinguishable
|
|
42
|
+
* from finding none, so exiting 0 would have the scanner answer "no problems"
|
|
43
|
+
* to a question it never managed to ask. Both codes mean the same thing here:
|
|
44
|
+
* no verdict.
|
|
45
|
+
*/
|
|
46
|
+
export declare function exitCodeFor(result: ScanResult, failOn: Severity | "none"): number;
|
|
47
|
+
/**
|
|
48
|
+
* The summary's last word: the code the process exits with, and why.
|
|
49
|
+
*
|
|
50
|
+
* Both reports print it, and both used to work it out for themselves from the
|
|
51
|
+
* findings alone, so a degraded scan printed "Exit code 0" or "Exit code 1"
|
|
52
|
+
* and then exited 2. It comes from {@link exitCodeFor} now, the same function
|
|
53
|
+
* the CLI exits with.
|
|
54
|
+
*/
|
|
55
|
+
export declare function exitVerdict(result: ScanResult, failOn: Severity | "none"): {
|
|
56
|
+
code: number;
|
|
57
|
+
reason: string;
|
|
58
|
+
};
|
|
27
59
|
export declare function clampWidth(columns: number | undefined): number;
|
|
28
60
|
/**
|
|
29
61
|
* Human-readable object path for a finding. Kept boring on purpose — people
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/rls-check",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Audit Row-Level Security on any PostgreSQL database — finds tables served without RLS, permissive tautologies, unqualified columns in policy subqueries, RLS-bypassing views and unprotected junction tables. Read-only.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"postgres",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@types/pg": "^8.11.10",
|
|
60
60
|
"typescript": "^6.0.3",
|
|
61
61
|
"vite": "^8.1.5",
|
|
62
|
-
"vitest": "^4.1.
|
|
62
|
+
"vitest": "^4.1.11"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"test": "vitest run",
|