@rebasepro/rls-check 0.17.3 → 0.18.1
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 +153 -111
- package/dist/checks/policy-anonymous-tautology.d.ts +50 -2
- package/dist/checks/policy-authenticated-tautology.d.ts +27 -0
- package/dist/checks/util.d.ts +28 -0
- package/dist/index.es.js +775 -293
- package/dist/index.es.js.map +1 -1
- package/dist/introspect.d.ts +33 -0
- package/dist/redact.d.ts +17 -1
- package/dist/types.d.ts +19 -4
- package/package.json +33 -21
package/dist/introspect.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClientConfig } from "pg";
|
|
1
2
|
import type { DbSnapshot } from "./types.js";
|
|
2
3
|
export interface ConnectOptions {
|
|
3
4
|
connectionString: string;
|
|
@@ -55,13 +56,45 @@ export interface IntrospectDiagnostics {
|
|
|
55
56
|
* imply that it looked.
|
|
56
57
|
*/
|
|
57
58
|
unrecognizedGrantees: string[];
|
|
59
|
+
/**
|
|
60
|
+
* The connecting role, when it is not privileged and was therefore added to
|
|
61
|
+
* the exposed set. Disclosed rather than silent: it changes which findings
|
|
62
|
+
* appear, so a reader comparing two runs has to be able to see it.
|
|
63
|
+
*/
|
|
64
|
+
scanningAsExposedRole?: string | null;
|
|
58
65
|
}
|
|
59
66
|
export interface IntrospectResult {
|
|
60
67
|
snapshot: DbSnapshot;
|
|
61
68
|
diagnostics: IntrospectDiagnostics;
|
|
62
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* A `--role` that is not in `pg_roles`.
|
|
72
|
+
*
|
|
73
|
+
* Same reasoning as an unknown `--skip` id: a typo here does not widen the scan
|
|
74
|
+
* and get noticed, it *narrows* it silently. `exposedRolesFor` drops the name,
|
|
75
|
+
* every check that gates on a grant to an exposed role stops matching, and the
|
|
76
|
+
* run prints "No findings" on a database nobody looked at properly. So it is an
|
|
77
|
+
* error, not a warning.
|
|
78
|
+
*/
|
|
79
|
+
export declare class UnknownRoleError extends Error {
|
|
80
|
+
readonly roles: string[];
|
|
81
|
+
constructor(roles: string[]);
|
|
82
|
+
}
|
|
63
83
|
export declare function introspect(opts: ConnectOptions): Promise<DbSnapshot>;
|
|
64
84
|
export declare function introspectWithDiagnostics(opts: ConnectOptions): Promise<IntrospectResult>;
|
|
85
|
+
/**
|
|
86
|
+
* The keywords in a libpq keyword string that this tool cannot honour, in the
|
|
87
|
+
* order they were written. Empty for a URL, and empty for a keyword string it
|
|
88
|
+
* can translate in full.
|
|
89
|
+
*/
|
|
90
|
+
export declare function unsupportedConnectionKeywords(connectionString: string): string[];
|
|
91
|
+
/**
|
|
92
|
+
* `Client` options for a libpq keyword string. Throws on a keyword it cannot
|
|
93
|
+
* honour. Exported for the test that pins the translation: getting `host` or
|
|
94
|
+
* `port` wrong here sends a production scan somewhere nobody asked for, and the
|
|
95
|
+
* failure would still be reported against the host the user typed.
|
|
96
|
+
*/
|
|
97
|
+
export declare function clientConfigFromKeywords(keywords: Map<string, string>): ClientConfig;
|
|
65
98
|
/**
|
|
66
99
|
* Which of the database's schemas this scan covers.
|
|
67
100
|
*
|
package/dist/redact.d.ts
CHANGED
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
* - the user ends at the FIRST `:` in the userinfo
|
|
17
17
|
*
|
|
18
18
|
* A libpq keyword string (`host=... password=...`) is understood too, because
|
|
19
|
-
* `psql "$PGSTRING"` users paste those.
|
|
19
|
+
* `psql "$PGSTRING"` users paste those. Understood here means *parsed*: `pg`
|
|
20
|
+
* itself cannot read that form, so anything that opens a connection has to go
|
|
21
|
+
* through {@link parseKeywordConnectionString} and build the `Client` options
|
|
22
|
+
* by hand. Parsing it here and handing the raw string to the driver would
|
|
23
|
+
* report failures against a host the user never named.
|
|
20
24
|
*/
|
|
21
25
|
/** What replaces every credential. */
|
|
22
26
|
export declare const REDACTED = "***";
|
|
@@ -31,6 +35,18 @@ export interface ConnectionTarget {
|
|
|
31
35
|
user: string | null;
|
|
32
36
|
password: string | null;
|
|
33
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* The libpq keyword/value form (`host=… dbname=…`) as a map of lowercased
|
|
40
|
+
* keyword to unquoted value, or `null` when the string is not that form.
|
|
41
|
+
*
|
|
42
|
+
* Exported because `pg` cannot read this form at all: `pg-connection-string`
|
|
43
|
+
* only understands URLs, so a `Client({ connectionString: "host=127.0.0.1
|
|
44
|
+
* port=1 …" })` connects to the *default* host and reports a failure against an
|
|
45
|
+
* endpoint nobody asked for. Whoever opens the connection has to translate
|
|
46
|
+
* these keywords into `Client` options itself — see `connect()` in
|
|
47
|
+
* `introspect.ts`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseKeywordConnectionString(raw: string): Map<string, string> | null;
|
|
34
50
|
/**
|
|
35
51
|
* Split a connection string into its parts. Returns `null` when the input is
|
|
36
52
|
* not recognisably a connection string — callers must treat that as "unknown",
|
package/dist/types.d.ts
CHANGED
|
@@ -198,6 +198,13 @@ export interface ScanResult {
|
|
|
198
198
|
serverVersion: string;
|
|
199
199
|
platform: DbSnapshot["platform"];
|
|
200
200
|
scannerIsPrivileged: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* The roles every check gated on. Reported because it is the single fact
|
|
203
|
+
* that decides whether a clean run means anything: a check only calls a
|
|
204
|
+
* table exposed when one of these can reach it, so a reader who does not
|
|
205
|
+
* see their own app role here knows the run did not cover their API.
|
|
206
|
+
*/
|
|
207
|
+
exposedRoles: string[];
|
|
201
208
|
stats: {
|
|
202
209
|
schemas: number;
|
|
203
210
|
tables: number;
|
|
@@ -233,11 +240,19 @@ export interface ScanResult {
|
|
|
233
240
|
reason: "system" | "platform" | "not-requested";
|
|
234
241
|
}[];
|
|
235
242
|
/**
|
|
236
|
-
* Roles holding write privileges that the scan
|
|
237
|
-
* exposed nor can explain as trusted. Non-empty
|
|
238
|
-
* set may be incomplete, and every check gates on
|
|
239
|
-
* the difference between "clean" and "clean as far
|
|
243
|
+
* Roles holding read or write privileges on scanned tables that the scan
|
|
244
|
+
* neither recognises as exposed nor can explain as trusted. Non-empty
|
|
245
|
+
* means the exposed-role set may be incomplete, and every check gates on
|
|
246
|
+
* that set — so this is the difference between "clean" and "clean as far
|
|
247
|
+
* as I could tell".
|
|
240
248
|
*/
|
|
241
249
|
unrecognizedGrantees: string[];
|
|
250
|
+
/**
|
|
251
|
+
* The role the scan connected as, when RLS constrains it and it was
|
|
252
|
+
* therefore treated as exposed. `null` or absent when the scan connected
|
|
253
|
+
* as a superuser, an owner or a BYPASSRLS role — the case
|
|
254
|
+
* `scannerIsPrivileged` already describes.
|
|
255
|
+
*/
|
|
256
|
+
scanningAsExposedRole?: string | null;
|
|
242
257
|
};
|
|
243
258
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/rls-check",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"postgres",
|
|
7
|
+
"postgresql",
|
|
8
|
+
"rls",
|
|
9
|
+
"row-level-security",
|
|
10
|
+
"security",
|
|
11
|
+
"audit",
|
|
12
|
+
"linter",
|
|
13
|
+
"supabase",
|
|
14
|
+
"policy"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://rebase.pro",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/rebasepro/rebase/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
23
|
+
"directory": "packages/rls-check"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "rebase.pro",
|
|
5
27
|
"main": "./dist/index.es.js",
|
|
6
28
|
"module": "./dist/index.es.js",
|
|
7
29
|
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.es.js",
|
|
34
|
+
"default": "./dist/index.es.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
8
38
|
"type": "module",
|
|
9
|
-
"source": "src/index.ts",
|
|
10
39
|
"bin": {
|
|
11
40
|
"rls-check": "bin/rls-check.js"
|
|
12
41
|
},
|
|
@@ -19,27 +48,9 @@
|
|
|
19
48
|
"bin",
|
|
20
49
|
"README.md"
|
|
21
50
|
],
|
|
22
|
-
"repository": {
|
|
23
|
-
"type": "git",
|
|
24
|
-
"url": "https://github.com/rebasepro/rebase.git",
|
|
25
|
-
"directory": "packages/rls-check"
|
|
26
|
-
},
|
|
27
51
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
52
|
+
"node": ">=22.22.0"
|
|
29
53
|
},
|
|
30
|
-
"keywords": [
|
|
31
|
-
"postgres",
|
|
32
|
-
"postgresql",
|
|
33
|
-
"rls",
|
|
34
|
-
"row-level-security",
|
|
35
|
-
"security",
|
|
36
|
-
"audit",
|
|
37
|
-
"linter",
|
|
38
|
-
"supabase",
|
|
39
|
-
"policy"
|
|
40
|
-
],
|
|
41
|
-
"author": "rebase.pro",
|
|
42
|
-
"license": "MIT",
|
|
43
54
|
"dependencies": {
|
|
44
55
|
"pg": "^8.22.0"
|
|
45
56
|
},
|
|
@@ -52,6 +63,7 @@
|
|
|
52
63
|
},
|
|
53
64
|
"scripts": {
|
|
54
65
|
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
55
67
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
56
68
|
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
|
|
57
69
|
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f",
|