@safegres/test 0.0.2 → 1.30.7
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 +825 -412
- package/adapters.d.ts +8 -0
- package/adapters.js +18 -0
- package/ast/helpers.d.ts +16 -0
- package/ast/helpers.js +40 -0
- package/ast/parse.d.ts +31 -0
- package/ast/parse.js +87 -0
- package/ast/walk.d.ts +27 -0
- package/ast/walk.js +79 -0
- package/callgraph/baseline.d.ts +28 -0
- package/callgraph/baseline.js +55 -0
- package/callgraph/extract.d.ts +93 -0
- package/callgraph/extract.js +340 -0
- package/callgraph/graph.d.ts +82 -0
- package/callgraph/graph.js +276 -0
- package/checks/anti-patterns.d.ts +53 -0
- package/checks/anti-patterns.js +247 -0
- package/checks/column-grants.d.ts +20 -0
- package/checks/column-grants.js +74 -0
- package/checks/coverage.d.ts +43 -0
- package/checks/coverage.js +110 -0
- package/checks/definer-function.d.ts +91 -0
- package/checks/definer-function.js +458 -0
- package/checks/definer-view.d.ts +167 -0
- package/checks/definer-view.js +392 -0
- package/checks/indexes.d.ts +82 -0
- package/checks/indexes.js +263 -0
- package/checks/lattice.d.ts +179 -0
- package/checks/lattice.js +487 -0
- package/checks/object-acls.d.ts +32 -0
- package/checks/object-acls.js +129 -0
- package/checks/policy-index.d.ts +78 -0
- package/checks/policy-index.js +349 -0
- package/checks/revocable-grants.d.ts +140 -0
- package/checks/revocable-grants.js +377 -0
- package/checks/rls-flags.d.ts +25 -0
- package/checks/rls-flags.js +82 -0
- package/checks/role-reach.d.ts +329 -0
- package/checks/role-reach.js +277 -0
- package/checks/role-trust.d.ts +45 -0
- package/checks/role-trust.js +91 -0
- package/checks/set-role.d.ts +21 -0
- package/checks/set-role.js +66 -0
- package/checks/stats.d.ts +52 -0
- package/checks/stats.js +192 -0
- package/checks/view-exposure.d.ts +68 -0
- package/checks/view-exposure.js +236 -0
- package/checks/view-writes.d.ts +88 -0
- package/checks/view-writes.js +350 -0
- package/ci/baseline.d.ts +104 -0
- package/ci/baseline.js +111 -0
- package/ci/github.d.ts +47 -0
- package/ci/github.js +197 -0
- package/cli/audit.d.ts +3 -0
- package/cli/audit.js +560 -0
- package/cli/baseline.d.ts +3 -0
- package/cli/baseline.js +104 -0
- package/cli/commands.d.ts +2 -0
- package/cli/commands.js +60 -0
- package/cli/doctor.d.ts +3 -0
- package/cli/doctor.js +72 -0
- package/cli/eval.d.ts +3 -0
- package/cli/eval.js +153 -0
- package/cli/print-config.d.ts +3 -0
- package/cli/print-config.js +54 -0
- package/cli/shared.d.ts +43 -0
- package/cli/shared.js +153 -0
- package/cli.d.ts +2 -0
- package/cli.js +69 -0
- package/commands/audit.d.ts +65 -0
- package/commands/audit.js +779 -0
- package/commands/doctor.d.ts +19 -0
- package/commands/doctor.js +181 -0
- package/commands/eval.d.ts +67 -0
- package/commands/eval.js +93 -0
- package/config/fingerprint.d.ts +40 -0
- package/config/fingerprint.js +71 -0
- package/config/loader.d.ts +44 -0
- package/config/loader.js +210 -0
- package/config/presets.d.ts +95 -0
- package/config/presets.js +327 -0
- package/config/resolve.d.ts +47 -0
- package/config/resolve.js +192 -0
- package/config/schema.d.ts +54 -0
- package/config/schema.js +269 -0
- package/config/types.d.ts +562 -0
- package/config/validate.d.ts +14 -0
- package/config/validate.js +125 -0
- package/corpus/README.md +83 -0
- package/corpus/bootstrap.sql +14 -0
- package/corpus/cases/01-anon-write-grant/case.json +26 -0
- package/corpus/cases/01-anon-write-grant/schema.sql +19 -0
- package/corpus/cases/02-grant-to-public/case.json +25 -0
- package/corpus/cases/02-grant-to-public/schema.sql +18 -0
- package/corpus/cases/03-rls-off-reachable-by-anon/case.json +30 -0
- package/corpus/cases/03-rls-off-reachable-by-anon/schema.sql +13 -0
- package/corpus/cases/04-permissive-write-policy/case.json +30 -0
- package/corpus/cases/04-permissive-write-policy/schema.sql +21 -0
- package/corpus/cases/05-public-read-policy/case.json +25 -0
- package/corpus/cases/05-public-read-policy/schema.sql +16 -0
- package/corpus/cases/06-update-without-with-check/case.json +25 -0
- package/corpus/cases/06-update-without-with-check/schema.sql +19 -0
- package/corpus/cases/07-rls-enabled-no-policies/case.json +29 -0
- package/corpus/cases/07-rls-enabled-no-policies/schema.sql +14 -0
- package/corpus/cases/08-select-grant-no-policy/case.json +25 -0
- package/corpus/cases/08-select-grant-no-policy/schema.sql +20 -0
- package/corpus/cases/09-write-grant-no-policy/case.json +24 -0
- package/corpus/cases/09-write-grant-no-policy/schema.sql +18 -0
- package/corpus/cases/10-rls-not-forced/case.json +24 -0
- package/corpus/cases/10-rls-not-forced/schema.sql +18 -0
- package/corpus/cases/11-session-user-in-policy/case.json +24 -0
- package/corpus/cases/11-session-user-in-policy/schema.sql +18 -0
- package/corpus/cases/12-volatile-function-in-policy/case.json +24 -0
- package/corpus/cases/12-volatile-function-in-policy/schema.sql +21 -0
- package/corpus/cases/13-security-definer-policy-wrapper/case.json +24 -0
- package/corpus/cases/13-security-definer-policy-wrapper/schema.sql +21 -0
- package/corpus/cases/14-dead-policy/case.json +24 -0
- package/corpus/cases/14-dead-policy/schema.sql +18 -0
- package/corpus/cases/15-unreachable-grant/case.json +24 -0
- package/corpus/cases/15-unreachable-grant/schema.sql +18 -0
- package/corpus/cases/16-anon-permissive-write-policy/case.json +34 -0
- package/corpus/cases/16-anon-permissive-write-policy/schema.sql +17 -0
- package/corpus/cases/17-foreign-key-without-index/case.json +24 -0
- package/corpus/cases/17-foreign-key-without-index/schema.sql +18 -0
- package/corpus/cases/18-policy-column-unindexed/case.json +24 -0
- package/corpus/cases/18-policy-column-unindexed/schema.sql +23 -0
- package/corpus/cases/19-policy-column-cast/case.json +24 -0
- package/corpus/cases/19-policy-column-cast/schema.sql +19 -0
- package/corpus/cases/20-stable-function-per-row/case.json +24 -0
- package/corpus/cases/20-stable-function-per-row/schema.sql +25 -0
- package/corpus/cases/21-table-without-primary-key/case.json +24 -0
- package/corpus/cases/21-table-without-primary-key/schema.sql +14 -0
- package/corpus/cases/22-redundant-index/case.json +24 -0
- package/corpus/cases/22-redundant-index/schema.sql +16 -0
- package/corpus/cases/23-tsvector-without-index/case.json +24 -0
- package/corpus/cases/23-tsvector-without-index/schema.sql +13 -0
- package/corpus/cases/24-anon-set-role-escalation/case.json +29 -0
- package/corpus/cases/24-anon-set-role-escalation/schema.sql +30 -0
- package/corpus/cases/25-definer-view-bypass/case.json +31 -0
- package/corpus/cases/25-definer-view-bypass/schema.sql +36 -0
- package/corpus/cases/26-invoker-view-no-bypass/case.json +31 -0
- package/corpus/cases/26-invoker-view-no-bypass/schema.sql +44 -0
- package/corpus/cases/27-definer-view-write/case.json +36 -0
- package/corpus/cases/27-definer-view-write/schema.sql +40 -0
- package/corpus/cases/28-invoker-view-no-write/case.json +31 -0
- package/corpus/cases/28-invoker-view-no-write/schema.sql +37 -0
- package/corpus/cases/29-rewrite-rule-bypass/case.json +36 -0
- package/corpus/cases/29-rewrite-rule-bypass/schema.sql +52 -0
- package/corpus/cases/30-instead-nothing-rule/case.json +31 -0
- package/corpus/cases/30-instead-nothing-rule/schema.sql +44 -0
- package/corpus/cases/31-matview-snapshot/case.json +32 -0
- package/corpus/cases/31-matview-snapshot/schema.sql +36 -0
- package/corpus/cases/32-matview-no-rls-no-bypass/case.json +30 -0
- package/corpus/cases/32-matview-no-rls-no-bypass/schema.sql +18 -0
- package/corpus/cases/33-leaky-filter-view/case.json +36 -0
- package/corpus/cases/33-leaky-filter-view/schema.sql +29 -0
- package/corpus/cases/34-barrier-view-no-leak/case.json +32 -0
- package/corpus/cases/34-barrier-view-no-leak/schema.sql +29 -0
- package/corpus/cases/35-column-grant-projection/case.json +27 -0
- package/corpus/cases/35-column-grant-projection/schema.sql +21 -0
- package/corpus/cases/35-policy-function-not-leakproof/case.json +24 -0
- package/corpus/cases/35-policy-function-not-leakproof/schema.sql +27 -0
- package/corpus/cases/36-column-grant-rls-mediated/case.json +27 -0
- package/corpus/cases/36-column-grant-rls-mediated/schema.sql +32 -0
- package/corpus/cases/36-policy-function-leakproof/case.json +24 -0
- package/corpus/cases/36-policy-function-leakproof/schema.sql +22 -0
- package/corpus/cases/37-sort-column-unindexed/case.json +24 -0
- package/corpus/cases/37-sort-column-unindexed/schema.sql +14 -0
- package/corpus/cases/37-unaudited-base-relation/case.json +30 -0
- package/corpus/cases/37-unaudited-base-relation/schema.sql +45 -0
- package/corpus/cases/38-foreign-key-with-index/case.json +22 -0
- package/corpus/cases/38-foreign-key-with-index/schema.sql +17 -0
- package/corpus/cases/38-invoker-view-unaudited-base/case.json +30 -0
- package/corpus/cases/38-invoker-view-unaudited-base/schema.sql +39 -0
- package/corpus/cases/39-definer-view-column-subset/case.json +30 -0
- package/corpus/cases/39-definer-view-column-subset/schema.sql +29 -0
- package/corpus/cases/39-policy-cast-with-expression-index/case.json +23 -0
- package/corpus/cases/39-policy-cast-with-expression-index/schema.sql +19 -0
- package/corpus/cases/40-definer-view-column-overreach/case.json +27 -0
- package/corpus/cases/40-definer-view-column-overreach/schema.sql +29 -0
- package/corpus/cases/40-stable-function-hoisted/case.json +23 -0
- package/corpus/cases/40-stable-function-hoisted/schema.sql +25 -0
- package/corpus/cases/41-index-prefix-not-redundant/case.json +21 -0
- package/corpus/cases/41-index-prefix-not-redundant/schema.sql +15 -0
- package/corpus/cases/41-unreadable-view-body/case.json +33 -0
- package/corpus/cases/41-unreadable-view-body/schema.sql +28 -0
- package/corpus/cases/42-invoker-view-unreadable-body/case.json +30 -0
- package/corpus/cases/42-invoker-view-unreadable-body/schema.sql +27 -0
- package/corpus/cases/42-search-column-indexed/case.json +21 -0
- package/corpus/cases/42-search-column-indexed/schema.sql +12 -0
- package/corpus/cases/43-anon-sequence-grant/case.json +27 -0
- package/corpus/cases/43-anon-sequence-grant/schema.sql +23 -0
- package/corpus/cases/44-serial-sequence-usage/case.json +27 -0
- package/corpus/cases/44-serial-sequence-usage/schema.sql +25 -0
- package/corpus/cases/45-anon-foreign-table/case.json +27 -0
- package/corpus/cases/45-anon-foreign-table/schema.sql +23 -0
- package/corpus/cases/46-unchecked-writable-view/case.json +41 -0
- package/corpus/cases/46-unchecked-writable-view/schema.sql +37 -0
- package/corpus/cases/47-checked-writable-view/case.json +37 -0
- package/corpus/cases/47-checked-writable-view/schema.sql +38 -0
- package/corpus/cases/48-definer-function-reach/case.json +38 -0
- package/corpus/cases/48-definer-function-reach/schema.sql +41 -0
- package/corpus/cases/49-invoker-function-no-reach/case.json +32 -0
- package/corpus/cases/49-invoker-function-no-reach/schema.sql +38 -0
- package/corpus/cases/50-instead-of-trigger-write/case.json +38 -0
- package/corpus/cases/50-instead-of-trigger-write/schema.sql +58 -0
- package/corpus/cases/51-invoker-trigger-no-write/case.json +33 -0
- package/corpus/cases/51-invoker-trigger-no-write/schema.sql +54 -0
- package/corpus/cases/52-revocable-execute-grant/case.json +30 -0
- package/corpus/cases/52-revocable-execute-grant/schema.sql +45 -0
- package/corpus/cases/53-policy-predicate-retains-execute/case.json +23 -0
- package/corpus/cases/53-policy-predicate-retains-execute/schema.sql +49 -0
- package/corpus/index.d.ts +79 -0
- package/corpus/index.js +125 -0
- package/esm/adapters.d.ts +8 -0
- package/esm/adapters.js +6 -0
- package/esm/ast/helpers.d.ts +16 -0
- package/esm/ast/helpers.js +36 -0
- package/esm/ast/parse.d.ts +31 -0
- package/esm/ast/parse.js +82 -0
- package/esm/ast/walk.d.ts +27 -0
- package/esm/ast/walk.js +72 -0
- package/esm/callgraph/baseline.d.ts +28 -0
- package/esm/callgraph/baseline.js +48 -0
- package/esm/callgraph/extract.d.ts +93 -0
- package/esm/callgraph/extract.js +333 -0
- package/esm/callgraph/graph.d.ts +82 -0
- package/esm/callgraph/graph.js +273 -0
- package/esm/checks/anti-patterns.d.ts +53 -0
- package/esm/checks/anti-patterns.js +240 -0
- package/esm/checks/column-grants.d.ts +20 -0
- package/esm/checks/column-grants.js +71 -0
- package/esm/checks/coverage.d.ts +43 -0
- package/esm/checks/coverage.js +104 -0
- package/esm/checks/definer-function.d.ts +91 -0
- package/esm/checks/definer-function.js +452 -0
- package/esm/checks/definer-view.d.ts +167 -0
- package/esm/checks/definer-view.js +382 -0
- package/esm/checks/indexes.d.ts +82 -0
- package/esm/checks/indexes.js +256 -0
- package/esm/checks/lattice.d.ts +179 -0
- package/esm/checks/lattice.js +475 -0
- package/esm/checks/object-acls.d.ts +32 -0
- package/esm/checks/object-acls.js +125 -0
- package/esm/checks/policy-index.d.ts +78 -0
- package/esm/checks/policy-index.js +342 -0
- package/esm/checks/revocable-grants.d.ts +140 -0
- package/esm/checks/revocable-grants.js +373 -0
- package/esm/checks/rls-flags.d.ts +25 -0
- package/esm/checks/rls-flags.js +77 -0
- package/esm/checks/role-reach.d.ts +329 -0
- package/esm/checks/role-reach.js +270 -0
- package/esm/checks/role-trust.d.ts +45 -0
- package/esm/checks/role-trust.js +86 -0
- package/esm/checks/set-role.d.ts +21 -0
- package/esm/checks/set-role.js +63 -0
- package/esm/checks/stats.d.ts +52 -0
- package/esm/checks/stats.js +184 -0
- package/esm/checks/view-exposure.d.ts +68 -0
- package/esm/checks/view-exposure.js +231 -0
- package/esm/checks/view-writes.d.ts +88 -0
- package/esm/checks/view-writes.js +344 -0
- package/esm/ci/baseline.d.ts +104 -0
- package/esm/ci/baseline.js +107 -0
- package/esm/ci/github.d.ts +47 -0
- package/esm/ci/github.js +161 -0
- package/esm/cli/audit.d.ts +3 -0
- package/esm/cli/audit.js +525 -0
- package/esm/cli/baseline.d.ts +3 -0
- package/esm/cli/baseline.js +102 -0
- package/esm/cli/commands.d.ts +2 -0
- package/esm/cli/commands.js +53 -0
- package/esm/cli/doctor.d.ts +3 -0
- package/esm/cli/doctor.js +67 -0
- package/esm/cli/eval.d.ts +3 -0
- package/esm/cli/eval.js +115 -0
- package/esm/cli/print-config.d.ts +3 -0
- package/esm/cli/print-config.js +52 -0
- package/esm/cli/shared.d.ts +43 -0
- package/esm/cli/shared.js +112 -0
- package/esm/cli.d.ts +2 -0
- package/esm/cli.js +67 -0
- package/esm/commands/audit.d.ts +65 -0
- package/esm/commands/audit.js +776 -0
- package/esm/commands/doctor.d.ts +19 -0
- package/esm/commands/doctor.js +178 -0
- package/esm/commands/eval.d.ts +67 -0
- package/esm/commands/eval.js +90 -0
- package/esm/config/fingerprint.d.ts +40 -0
- package/esm/config/fingerprint.js +68 -0
- package/esm/config/loader.d.ts +44 -0
- package/esm/config/loader.js +172 -0
- package/esm/config/presets.d.ts +95 -0
- package/esm/config/presets.js +324 -0
- package/esm/config/resolve.d.ts +47 -0
- package/esm/config/resolve.js +180 -0
- package/esm/config/schema.d.ts +54 -0
- package/esm/config/schema.js +265 -0
- package/esm/config/types.d.ts +562 -0
- package/esm/config/validate.d.ts +14 -0
- package/esm/config/validate.js +122 -0
- package/esm/corpus/index.d.ts +79 -0
- package/esm/corpus/index.js +85 -0
- package/esm/exposure/adapters.d.ts +152 -0
- package/esm/exposure/adapters.js +463 -0
- package/esm/exposure/planes.d.ts +59 -0
- package/esm/exposure/planes.js +144 -0
- package/esm/exposure/reach.d.ts +78 -0
- package/esm/exposure/reach.js +105 -0
- package/esm/exposure/routines.d.ts +36 -0
- package/esm/exposure/routines.js +54 -0
- package/esm/index.d.ts +96 -0
- package/esm/index.js +57 -6
- package/esm/lint/index.d.ts +11 -0
- package/esm/lint/index.js +10 -0
- package/esm/perf/baseline.d.ts +40 -0
- package/esm/perf/baseline.js +83 -0
- package/esm/perf/explain.d.ts +46 -0
- package/esm/perf/explain.js +201 -0
- package/esm/pg/acl.d.ts +72 -0
- package/esm/pg/acl.js +186 -0
- package/esm/pg/behaviors.d.ts +114 -0
- package/esm/pg/behaviors.js +206 -0
- package/esm/pg/exposure.d.ts +74 -0
- package/esm/pg/exposure.js +198 -0
- package/esm/pg/functions.d.ts +52 -0
- package/esm/pg/functions.js +128 -0
- package/esm/pg/indexes.d.ts +192 -0
- package/esm/pg/indexes.js +395 -0
- package/esm/pg/introspect.d.ts +134 -0
- package/esm/pg/introspect.js +274 -0
- package/esm/pg/objects.d.ts +37 -0
- package/esm/pg/objects.js +108 -0
- package/esm/pg/paths.d.ts +114 -0
- package/esm/pg/paths.js +159 -0
- package/esm/pg/proc.d.ts +23 -0
- package/esm/pg/proc.js +55 -0
- package/esm/pg/reach-inputs.d.ts +47 -0
- package/esm/pg/reach-inputs.js +133 -0
- package/esm/pg/roles.d.ts +28 -0
- package/esm/pg/roles.js +50 -0
- package/esm/pg/stats.d.ts +65 -0
- package/esm/pg/stats.js +129 -0
- package/esm/pgpm-test.d.ts +22 -0
- package/esm/pgpm-test.js +39 -0
- package/esm/report/callgraph.d.ts +9 -0
- package/esm/report/callgraph.js +74 -0
- package/esm/report/compare.d.ts +143 -0
- package/esm/report/compare.js +195 -0
- package/esm/report/github.d.ts +74 -0
- package/esm/report/github.js +290 -0
- package/esm/report/json.d.ts +4 -0
- package/esm/report/json.js +3 -0
- package/esm/report/markdown.d.ts +25 -0
- package/esm/report/markdown.js +352 -0
- package/esm/report/pretty.d.ts +14 -0
- package/esm/report/pretty.js +318 -0
- package/esm/report/sarif.d.ts +40 -0
- package/esm/report/sarif.js +189 -0
- package/esm/report/view.d.ts +82 -0
- package/esm/report/view.js +126 -0
- package/esm/rules/registry.d.ts +66 -0
- package/esm/rules/registry.js +562 -0
- package/esm/score/score.d.ts +84 -0
- package/esm/score/score.js +233 -0
- package/esm/score/scorecards.d.ts +68 -0
- package/esm/score/scorecards.js +157 -0
- package/esm/types.d.ts +292 -0
- package/esm/types.js +26 -0
- package/esm/version.d.ts +8 -0
- package/esm/version.js +9 -0
- package/exposure/adapters.d.ts +152 -0
- package/exposure/adapters.js +468 -0
- package/exposure/planes.d.ts +59 -0
- package/exposure/planes.js +151 -0
- package/exposure/reach.d.ts +78 -0
- package/exposure/reach.js +109 -0
- package/exposure/routines.d.ts +36 -0
- package/exposure/routines.js +57 -0
- package/index.d.ts +96 -6
- package/index.js +229 -6
- package/lint/index.d.ts +11 -0
- package/lint/index.js +19 -0
- package/package.json +54 -48
- package/perf/baseline.d.ts +40 -0
- package/perf/baseline.js +92 -0
- package/perf/explain.d.ts +46 -0
- package/perf/explain.js +204 -0
- package/pg/acl.d.ts +72 -0
- package/pg/acl.js +191 -0
- package/pg/behaviors.d.ts +114 -0
- package/pg/behaviors.js +217 -0
- package/pg/exposure.d.ts +74 -0
- package/pg/exposure.js +205 -0
- package/pg/functions.d.ts +52 -0
- package/pg/functions.js +131 -0
- package/pg/indexes.d.ts +192 -0
- package/pg/indexes.js +400 -0
- package/pg/introspect.d.ts +134 -0
- package/pg/introspect.js +280 -0
- package/pg/objects.d.ts +37 -0
- package/pg/objects.js +111 -0
- package/pg/paths.d.ts +114 -0
- package/pg/paths.js +166 -0
- package/pg/proc.d.ts +23 -0
- package/pg/proc.js +58 -0
- package/pg/reach-inputs.d.ts +47 -0
- package/pg/reach-inputs.js +136 -0
- package/pg/roles.d.ts +28 -0
- package/pg/roles.js +54 -0
- package/pg/stats.d.ts +65 -0
- package/pg/stats.js +133 -0
- package/pgpm-test.d.ts +22 -0
- package/pgpm-test.js +42 -0
- package/report/callgraph.d.ts +9 -0
- package/report/callgraph.js +81 -0
- package/report/compare.d.ts +143 -0
- package/report/compare.js +205 -0
- package/report/github.d.ts +74 -0
- package/report/github.js +333 -0
- package/report/json.d.ts +4 -0
- package/report/json.js +6 -0
- package/report/markdown.d.ts +25 -0
- package/report/markdown.js +355 -0
- package/report/pretty.d.ts +14 -0
- package/report/pretty.js +324 -0
- package/report/sarif.d.ts +40 -0
- package/report/sarif.js +193 -0
- package/report/view.d.ts +82 -0
- package/report/view.js +132 -0
- package/rules/registry.d.ts +66 -0
- package/rules/registry.js +569 -0
- package/schema/safegres.schema.json +1119 -0
- package/score/score.d.ts +84 -0
- package/score/score.js +238 -0
- package/score/scorecards.d.ts +68 -0
- package/score/scorecards.js +162 -0
- package/types.d.ts +292 -0
- package/types.js +32 -0
- package/version.d.ts +8 -0
- package/version.js +12 -0
- package/LICENSE +0 -23
- package/admin.d.ts +0 -26
- package/admin.js +0 -182
- package/connect.d.ts +0 -19
- package/connect.js +0 -83
- package/dist/README.md +0 -557
- package/dist/package.json +0 -72
- package/esm/admin.js +0 -178
- package/esm/connect.js +0 -78
- package/esm/manager.js +0 -136
- package/esm/roles.js +0 -32
- package/esm/seed/adapters.js +0 -23
- package/esm/seed/csv.js +0 -44
- package/esm/seed/index.js +0 -16
- package/esm/seed/json.js +0 -18
- package/esm/seed/launchql.js +0 -19
- package/esm/seed/sqitch.js +0 -17
- package/esm/stream.js +0 -43
- package/esm/test-client.js +0 -150
- package/manager.d.ts +0 -25
- package/manager.js +0 -140
- package/roles.d.ts +0 -17
- package/roles.js +0 -38
- package/seed/adapters.d.ts +0 -4
- package/seed/adapters.js +0 -28
- package/seed/csv.d.ts +0 -9
- package/seed/csv.js +0 -49
- package/seed/index.d.ts +0 -16
- package/seed/index.js +0 -33
- package/seed/json.d.ts +0 -6
- package/seed/json.js +0 -21
- package/seed/launchql.d.ts +0 -2
- package/seed/launchql.js +0 -22
- package/seed/sqitch.d.ts +0 -2
- package/seed/sqitch.js +0 -20
- package/seed/types.d.ts +0 -13
- package/stream.d.ts +0 -2
- package/stream.js +0 -46
- package/test-client.d.ts +0 -49
- package/test-client.js +0 -154
- /package/{seed → config}/types.js +0 -0
- /package/esm/{seed → config}/types.js +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Logger } from '@pgpmjs/logger';
|
|
2
|
+
import { extractFirst } from 'inquirerer';
|
|
3
|
+
import audit from './audit';
|
|
4
|
+
import baseline from './baseline';
|
|
5
|
+
import doctor from './doctor';
|
|
6
|
+
import evalCommand from './eval';
|
|
7
|
+
import printConfig from './print-config';
|
|
8
|
+
const log = new Logger('safegres');
|
|
9
|
+
const usage = `
|
|
10
|
+
safegres — Postgres security and performance auditor
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
safegres <command> [OPTIONS]
|
|
14
|
+
|
|
15
|
+
Commands:
|
|
16
|
+
audit Audit grants, RLS flags, policy coverage, and anti-patterns
|
|
17
|
+
lint Alias for audit, for a package.json script
|
|
18
|
+
perf Audit index hygiene and policy cost (audit --perf)
|
|
19
|
+
baseline Choose the report a delta is measured against (CI)
|
|
20
|
+
doctor Diagnose environment, connection, and configuration
|
|
21
|
+
eval Grade the auditor against a corpus with known answers
|
|
22
|
+
print-config Show the resolved effective configuration
|
|
23
|
+
help Show this help message
|
|
24
|
+
|
|
25
|
+
Run \`safegres <command> --help\` for command-specific options.
|
|
26
|
+
`;
|
|
27
|
+
const commandMap = {
|
|
28
|
+
audit,
|
|
29
|
+
lint: audit,
|
|
30
|
+
perf: (argv, prompter, options) => audit({ ...argv, perf: true }, prompter, options),
|
|
31
|
+
baseline,
|
|
32
|
+
doctor,
|
|
33
|
+
eval: evalCommand,
|
|
34
|
+
'print-config': printConfig
|
|
35
|
+
};
|
|
36
|
+
export const commands = async (argv, prompter, options) => {
|
|
37
|
+
const { first: command, newArgv } = extractFirst(argv);
|
|
38
|
+
if (!command && (argv.help || argv.h)) {
|
|
39
|
+
process.stdout.write(usage);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!command || command === 'help') {
|
|
43
|
+
process.stdout.write(usage);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const handler = commandMap[command];
|
|
47
|
+
if (!handler) {
|
|
48
|
+
log.error(`Unknown command: ${command}`);
|
|
49
|
+
process.stdout.write(usage);
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
await handler(newArgv, prompter, options);
|
|
53
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import yanse from 'yanse';
|
|
2
|
+
import { doctor } from '../commands/doctor';
|
|
3
|
+
import { buildClient, configParamsFromArgv } from './shared';
|
|
4
|
+
const usage = `
|
|
5
|
+
safegres doctor — diagnose the environment, connection, and configuration
|
|
6
|
+
|
|
7
|
+
safegres doctor [OPTIONS]
|
|
8
|
+
|
|
9
|
+
Connection (same flags as \`safegres audit\`):
|
|
10
|
+
--connection <url> Full PostgreSQL connection string
|
|
11
|
+
--host / --port / --user / --password / --database
|
|
12
|
+
|
|
13
|
+
Configuration:
|
|
14
|
+
--config <path> Explicit config file
|
|
15
|
+
--preset <name> Apply a built-in preset
|
|
16
|
+
--rule <CODE=SETTING> Retune a rule (repeatable)
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--no-color Disable ANSI colors
|
|
20
|
+
--help, -h Show this help message
|
|
21
|
+
|
|
22
|
+
Checks: config discovery & rule validation, pgsql-parser availability,
|
|
23
|
+
connection, catalog access (pg_policy), audit blind spots (BYPASSRLS),
|
|
24
|
+
and whether any tables actually have RLS enabled.
|
|
25
|
+
`;
|
|
26
|
+
const STATUS_LABEL = {
|
|
27
|
+
ok: 'OK ',
|
|
28
|
+
warn: 'WARN',
|
|
29
|
+
fail: 'FAIL'
|
|
30
|
+
};
|
|
31
|
+
export default async (argv, _prompter, _options) => {
|
|
32
|
+
if (argv.help || argv.h) {
|
|
33
|
+
process.stdout.write(usage);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const colorEnabled = argv.color !== false;
|
|
37
|
+
const paint = (status, s) => {
|
|
38
|
+
if (!colorEnabled)
|
|
39
|
+
return s;
|
|
40
|
+
if (status === 'ok')
|
|
41
|
+
return yanse.green(s);
|
|
42
|
+
if (status === 'warn')
|
|
43
|
+
return yanse.yellow(s);
|
|
44
|
+
return yanse.bold(yanse.red(s));
|
|
45
|
+
};
|
|
46
|
+
let client = buildClient(argv);
|
|
47
|
+
try {
|
|
48
|
+
await client.connect();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
client = null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const report = await doctor(client, configParamsFromArgv(argv));
|
|
55
|
+
for (const check of report.checks) {
|
|
56
|
+
process.stdout.write(`[${paint(check.status, STATUS_LABEL[check.status])}] ${check.name.padEnd(12)} ${check.detail}\n`);
|
|
57
|
+
}
|
|
58
|
+
process.stdout.write('\n');
|
|
59
|
+
process.stdout.write(report.ok ? 'doctor: all checks passed\n' : 'doctor: some checks failed\n');
|
|
60
|
+
if (!report.ok)
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
if (client)
|
|
65
|
+
await client.end();
|
|
66
|
+
}
|
|
67
|
+
};
|
package/esm/cli/eval.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import yanse from 'yanse';
|
|
3
|
+
import { runEval } from '../commands/eval';
|
|
4
|
+
import { configPathBase, loadConfig } from '../config/loader';
|
|
5
|
+
import { loadCorpus } from '../corpus';
|
|
6
|
+
import { buildClient, csvList } from './shared';
|
|
7
|
+
const usage = `
|
|
8
|
+
safegres eval — grade the auditor against a corpus with known answers
|
|
9
|
+
|
|
10
|
+
safegres eval [OPTIONS]
|
|
11
|
+
|
|
12
|
+
Deploys each corpus case into the connected database, audits it under a sealed
|
|
13
|
+
preset, and grades the report against the case's answer key: every expected
|
|
14
|
+
finding present (recall), nothing the case forbids (precision). Exits non-zero
|
|
15
|
+
if any case fails. Each case's schemas are dropped afterwards.
|
|
16
|
+
|
|
17
|
+
Connection (same flags as \`safegres audit\`):
|
|
18
|
+
--connection <url> Full PostgreSQL connection string
|
|
19
|
+
--host / --port / --user / --password / --database
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--preset <name> Preset every case is graded under (default: recommended)
|
|
23
|
+
--corpus <dir> Corpus directory of <id>/{case.json,schema.sql}
|
|
24
|
+
--case <id> Run only these cases; id or id prefix, comma-separated
|
|
25
|
+
--list List the corpus without connecting to a database
|
|
26
|
+
--keep Leave the case schemas in the database
|
|
27
|
+
--json Emit the machine-readable result instead of a table
|
|
28
|
+
--no-color Disable ANSI colors
|
|
29
|
+
--help, -h Show this help message
|
|
30
|
+
|
|
31
|
+
Config: a discovered safegres config may set \`eval.corpus\`, \`eval.preset\` and
|
|
32
|
+
\`eval.cases\` — what to run. It cannot retune the rules: cases are always
|
|
33
|
+
graded by the named preset alone, or the corpus would be measuring itself.
|
|
34
|
+
`;
|
|
35
|
+
/**
|
|
36
|
+
* Discovery is used for *selection only* (which corpus, which preset, which
|
|
37
|
+
* cases). Grading always loads the preset sealed, so nothing in the working
|
|
38
|
+
* tree can change the answers.
|
|
39
|
+
*/
|
|
40
|
+
function evalDefaults(configFile) {
|
|
41
|
+
try {
|
|
42
|
+
const loaded = loadConfig({ configFile });
|
|
43
|
+
const config = loaded.config.eval ?? {};
|
|
44
|
+
if (config.corpus === undefined)
|
|
45
|
+
return config;
|
|
46
|
+
// Like every other configured path: relative to the file that declared it,
|
|
47
|
+
// so a corpus committed next to the config is found from any cwd.
|
|
48
|
+
return { ...config, corpus: path.resolve(configPathBase(loaded).dirFor('eval.corpus'), config.corpus) };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function line(r, color) {
|
|
55
|
+
const paint = (s) => (!color ? s : r.passed ? yanse.green(s) : yanse.bold(yanse.red(s)));
|
|
56
|
+
const detail = r.passed
|
|
57
|
+
? r.found.map((e) => e.code).join(' ')
|
|
58
|
+
: [
|
|
59
|
+
...r.missed.map((e) => `missed ${e.code}${e.relation ? `@${e.relation}` : ''}`),
|
|
60
|
+
...r.falsePositives.map((code) => `false positive ${code}`)
|
|
61
|
+
].join(', ');
|
|
62
|
+
return ` ${paint(r.passed ? 'PASS' : 'FAIL')} ${r.id.padEnd(32)} ${r.dimension.padEnd(8)} `
|
|
63
|
+
+ `${String(r.score).padStart(3)} (${r.grade.padEnd(2)}) ${detail}\n`;
|
|
64
|
+
}
|
|
65
|
+
function summary(report, color) {
|
|
66
|
+
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
67
|
+
const ok = report.passed === report.total;
|
|
68
|
+
const headline = `${report.passed}/${report.total} cases passed`;
|
|
69
|
+
return `\n${color ? (ok ? yanse.green(headline) : yanse.bold(yanse.red(headline))) : headline}`
|
|
70
|
+
+ ` · recall ${pct(report.recall)} · precision ${pct(report.precision)}\n`;
|
|
71
|
+
}
|
|
72
|
+
export default async (argv, _prompter, _options) => {
|
|
73
|
+
if (argv.help || argv.h) {
|
|
74
|
+
process.stdout.write(usage);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const defaults = evalDefaults(typeof argv.config === 'string' ? argv.config : undefined);
|
|
78
|
+
const corpus = typeof argv.corpus === 'string' ? argv.corpus : defaults.corpus;
|
|
79
|
+
const preset = typeof argv.preset === 'string' ? argv.preset : (defaults.preset ?? 'recommended');
|
|
80
|
+
const cases = csvList(argv.case) ?? defaults.cases;
|
|
81
|
+
const color = argv.color !== false && argv.json !== true;
|
|
82
|
+
if (argv.list === true) {
|
|
83
|
+
for (const c of loadCorpus(corpus)) {
|
|
84
|
+
process.stdout.write(`${c.id.padEnd(32)} ${c.dimension.padEnd(8)} ${c.title}\n`);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const { config } = loadConfig({ sealed: true, preset });
|
|
89
|
+
const client = buildClient(argv);
|
|
90
|
+
await client.connect();
|
|
91
|
+
try {
|
|
92
|
+
const report = await runEval(client, {
|
|
93
|
+
config,
|
|
94
|
+
preset,
|
|
95
|
+
corpus,
|
|
96
|
+
cases,
|
|
97
|
+
keep: argv.keep === true,
|
|
98
|
+
onResult: argv.json === true ? undefined : (r) => process.stdout.write(line(r, color))
|
|
99
|
+
});
|
|
100
|
+
if (argv.json === true) {
|
|
101
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
process.stdout.write(summary(report, color));
|
|
105
|
+
for (const r of report.results.filter((x) => !x.passed)) {
|
|
106
|
+
process.stdout.write(`\n${r.id}: ${r.title}\n fix: ${r.fix}\n`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (report.passed !== report.total)
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await client.end();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { loadConfig, safegresConfigLoader } from '../config/loader';
|
|
2
|
+
import { resolveRules } from '../config/resolve';
|
|
3
|
+
import { toJsonSchema } from '../config/schema';
|
|
4
|
+
import { RULES } from '../rules/registry';
|
|
5
|
+
import { configParamsFromArgv } from './shared';
|
|
6
|
+
const usage = `
|
|
7
|
+
safegres print-config — show the resolved effective configuration
|
|
8
|
+
|
|
9
|
+
safegres print-config [OPTIONS]
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--config <path> Explicit config file
|
|
13
|
+
--preset <name> Apply a built-in preset
|
|
14
|
+
--rule <CODE=SETTING> Retune a rule (repeatable)
|
|
15
|
+
--explain Show per-key provenance (which layer set each value)
|
|
16
|
+
--schema Print the config JSON Schema instead, for an editor
|
|
17
|
+
--help, -h Show this help message
|
|
18
|
+
`;
|
|
19
|
+
export default async (argv, _prompter, _options) => {
|
|
20
|
+
if (argv.help || argv.h) {
|
|
21
|
+
process.stdout.write(usage);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (argv.schema === true) {
|
|
25
|
+
process.stdout.write(`${JSON.stringify(toJsonSchema(), null, 2)}\n`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const params = configParamsFromArgv(argv);
|
|
29
|
+
if (argv.explain === true) {
|
|
30
|
+
const loader = safegresConfigLoader();
|
|
31
|
+
const explained = loader.explainSync({
|
|
32
|
+
configFile: params.configFile,
|
|
33
|
+
overrides: params.overrides
|
|
34
|
+
});
|
|
35
|
+
for (const e of explained) {
|
|
36
|
+
process.stdout.write(`${e.path} = ${JSON.stringify(e.value)} (${e.source}: ${e.origin})\n`);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const { config, filepath, isEmpty } = loadConfig(params);
|
|
41
|
+
const resolved = resolveRules(config);
|
|
42
|
+
const effectiveRules = {};
|
|
43
|
+
for (const rule of RULES) {
|
|
44
|
+
const r = resolved.rules.get(rule.code);
|
|
45
|
+
effectiveRules[rule.code] = r.enabled ? r.severity : 'off';
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write(`${JSON.stringify({
|
|
48
|
+
source: filepath ?? (isEmpty ? '(no config file — defaults)' : undefined),
|
|
49
|
+
config,
|
|
50
|
+
effectiveRules
|
|
51
|
+
}, null, 2)}\n`);
|
|
52
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ParsedArgs } from 'inquirerer';
|
|
2
|
+
import { Client } from 'pg';
|
|
3
|
+
import type { ConfigPathBase, LoadConfigParams } from '../config/loader';
|
|
4
|
+
import type { ExtensionsConfig, RulesConfig, SafegresConfig } from '../config/types';
|
|
5
|
+
export declare function csvList(value: unknown): string[] | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Merge `--ignore-extensions` / `--audit-extension-owned` over the config's
|
|
8
|
+
* `extensions` block. Returns `undefined` when neither side says anything, so
|
|
9
|
+
* the introspection defaults apply.
|
|
10
|
+
*/
|
|
11
|
+
export declare function extensionScopeFromArgv(argv: ParsedArgs, config: SafegresConfig): ExtensionsConfig | undefined;
|
|
12
|
+
export declare function buildClient(argv: ParsedArgs): Client;
|
|
13
|
+
/**
|
|
14
|
+
* Parse repeatable `--rule CODE=SETTING` flags into a RulesConfig, e.g.
|
|
15
|
+
* `--rule A3=off --rule A5=high --rule 'P*'=off`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseRuleFlags(value: unknown): RulesConfig | undefined;
|
|
18
|
+
/** Every file a run reads or writes, after flags and config have been merged. */
|
|
19
|
+
export interface RunPaths {
|
|
20
|
+
/** The pgpm workspace to deploy, when `usePgpm`. Undefined means "nearest". */
|
|
21
|
+
pgpm?: string;
|
|
22
|
+
usePgpm: boolean;
|
|
23
|
+
perfBaseline?: string;
|
|
24
|
+
callGraphBaseline?: string;
|
|
25
|
+
outputs: {
|
|
26
|
+
json?: string;
|
|
27
|
+
markdown?: string;
|
|
28
|
+
sarif?: string;
|
|
29
|
+
sarifSources?: string;
|
|
30
|
+
snapshot?: string;
|
|
31
|
+
githubComment?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Flags win over the config file. A path from a config file is resolved
|
|
36
|
+
* against *the file that declared it* — which is not always the discovered
|
|
37
|
+
* one, since a file can extend another — so a CI job can run from any
|
|
38
|
+
* directory; a path on the command line stays relative to cwd, like every
|
|
39
|
+
* other command's arguments.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveRunPaths(argv: ParsedArgs, config: SafegresConfig, base: ConfigPathBase | string): RunPaths;
|
|
42
|
+
/** Build config-loading params from shared CLI flags. */
|
|
43
|
+
export declare function configParamsFromArgv(argv: ParsedArgs): LoadConfigParams;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { Client } from 'pg';
|
|
3
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
4
|
+
export function csvList(value) {
|
|
5
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
6
|
+
return undefined;
|
|
7
|
+
return value
|
|
8
|
+
.split(',')
|
|
9
|
+
.map((p) => p.trim())
|
|
10
|
+
.filter(Boolean);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Merge `--ignore-extensions` / `--audit-extension-owned` over the config's
|
|
14
|
+
* `extensions` block. Returns `undefined` when neither side says anything, so
|
|
15
|
+
* the introspection defaults apply.
|
|
16
|
+
*/
|
|
17
|
+
export function extensionScopeFromArgv(argv, config) {
|
|
18
|
+
const ignore = csvList(argv['ignore-extensions']) ?? config.extensions?.ignore;
|
|
19
|
+
const skipOwned = argv['audit-extension-owned'] === true ? false : config.extensions?.skipOwned;
|
|
20
|
+
if (ignore === undefined && skipOwned === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
return { ...(ignore !== undefined && { ignore }), ...(skipOwned !== undefined && { skipOwned }) };
|
|
23
|
+
}
|
|
24
|
+
export function buildClient(argv) {
|
|
25
|
+
if (typeof argv.connection === 'string' && argv.connection.length > 0) {
|
|
26
|
+
return new Client({ connectionString: argv.connection });
|
|
27
|
+
}
|
|
28
|
+
const overrides = {};
|
|
29
|
+
if (typeof argv.host === 'string')
|
|
30
|
+
overrides.host = argv.host;
|
|
31
|
+
if (typeof argv.port === 'number')
|
|
32
|
+
overrides.port = argv.port;
|
|
33
|
+
if (typeof argv.user === 'string')
|
|
34
|
+
overrides.user = argv.user;
|
|
35
|
+
if (typeof argv.password === 'string')
|
|
36
|
+
overrides.password = argv.password;
|
|
37
|
+
if (typeof argv.database === 'string')
|
|
38
|
+
overrides.database = argv.database;
|
|
39
|
+
return new Client(getPgEnvOptions(overrides));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse repeatable `--rule CODE=SETTING` flags into a RulesConfig, e.g.
|
|
43
|
+
* `--rule A3=off --rule A5=high --rule 'P*'=off`.
|
|
44
|
+
*/
|
|
45
|
+
export function parseRuleFlags(value) {
|
|
46
|
+
if (value == null)
|
|
47
|
+
return undefined;
|
|
48
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
49
|
+
const rules = {};
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (typeof entry !== 'string')
|
|
52
|
+
continue;
|
|
53
|
+
const eq = entry.indexOf('=');
|
|
54
|
+
if (eq <= 0) {
|
|
55
|
+
throw new Error(`Invalid --rule "${entry}": expected CODE=off|<severity>.`);
|
|
56
|
+
}
|
|
57
|
+
const code = entry.slice(0, eq).trim();
|
|
58
|
+
const setting = entry.slice(eq + 1).trim();
|
|
59
|
+
rules[code] = setting;
|
|
60
|
+
}
|
|
61
|
+
return Object.keys(rules).length > 0 ? rules : undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Did the caller name a database on the command line, rather than in the environment? */
|
|
64
|
+
function hasConnectionFlag(argv) {
|
|
65
|
+
return ['connection', 'database', 'host', 'port'].some((flag) => typeof argv[flag] === 'string' || typeof argv[flag] === 'number');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Flags win over the config file. A path from a config file is resolved
|
|
69
|
+
* against *the file that declared it* — which is not always the discovered
|
|
70
|
+
* one, since a file can extend another — so a CI job can run from any
|
|
71
|
+
* directory; a path on the command line stays relative to cwd, like every
|
|
72
|
+
* other command's arguments.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveRunPaths(argv, config, base) {
|
|
75
|
+
const dirFor = typeof base === 'string' ? () => base : (key) => base.dirFor(key);
|
|
76
|
+
const pick = (flag, key, configured) => typeof flag === 'string' ? flag : configured && path.resolve(dirFor(key), configured);
|
|
77
|
+
// A directory is the common case: one path, conventional names, no remembering
|
|
78
|
+
// which extension goes with which renderer. A named file still wins.
|
|
79
|
+
const dir = pick(argv.out, 'outputs.dir', config.outputs?.dir);
|
|
80
|
+
const inDir = (name) => dir && path.join(dir, name);
|
|
81
|
+
return {
|
|
82
|
+
pgpm: pick(argv.pgpm, 'source.pgpm', config.source?.pgpm),
|
|
83
|
+
// An explicit connection on the command line beats a configured source:
|
|
84
|
+
// pointing the same config at a database you already have is the whole
|
|
85
|
+
// reason to type one, and deploying a throwaway copy instead would ignore it.
|
|
86
|
+
usePgpm: argv.pgpm !== undefined || (config.source?.pgpm !== undefined && !hasConnectionFlag(argv)),
|
|
87
|
+
perfBaseline: pick(argv['perf-baseline'], 'perf.baseline', config.perf?.baseline),
|
|
88
|
+
callGraphBaseline: pick(argv.baseline, 'callGraph.baseline', config.callGraph?.baseline),
|
|
89
|
+
outputs: {
|
|
90
|
+
json: pick(argv['write-json'], 'outputs.json', config.outputs?.json) ?? inDir('safegres.json'),
|
|
91
|
+
markdown: pick(argv['write-markdown'], 'outputs.markdown', config.outputs?.markdown)
|
|
92
|
+
?? inDir('safegres.md'),
|
|
93
|
+
sarif: pick(argv['write-sarif'], 'outputs.sarif', config.outputs?.sarif) ?? inDir('safegres.sarif'),
|
|
94
|
+
sarifSources: pick(argv['sarif-sources'], 'outputs.sarifSources', config.outputs?.sarifSources),
|
|
95
|
+
snapshot: pick(argv['write-snapshot'], 'outputs.snapshot', config.outputs?.snapshot),
|
|
96
|
+
githubComment: pick(argv['write-github-comment'], 'outputs.githubComment', config.outputs?.githubComment)
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Build config-loading params from shared CLI flags. */
|
|
101
|
+
export function configParamsFromArgv(argv) {
|
|
102
|
+
const cliRules = parseRuleFlags(argv.rule);
|
|
103
|
+
const overrides = {};
|
|
104
|
+
if (cliRules)
|
|
105
|
+
overrides.rules = cliRules;
|
|
106
|
+
return {
|
|
107
|
+
configFile: typeof argv.config === 'string' ? argv.config : undefined,
|
|
108
|
+
preset: typeof argv.preset === 'string' ? argv.preset : undefined,
|
|
109
|
+
overrides: Object.keys(overrides).length > 0 ? overrides : undefined,
|
|
110
|
+
sealed: argv.sealed === true
|
|
111
|
+
};
|
|
112
|
+
}
|
package/esm/cli.d.ts
ADDED
package/esm/cli.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Logger } from '@pgpmjs/logger';
|
|
3
|
+
import { CLI, getPackageJson } from 'inquirerer';
|
|
4
|
+
import { commands } from './cli/commands';
|
|
5
|
+
const log = new Logger('safegres');
|
|
6
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
7
|
+
const pkg = getPackageJson(__dirname);
|
|
8
|
+
process.stdout.write(`${pkg.version}\n`);
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
const options = {
|
|
12
|
+
minimistOpts: {
|
|
13
|
+
alias: { v: 'version', h: 'help', q: 'summary' },
|
|
14
|
+
boolean: [
|
|
15
|
+
'skip-ast',
|
|
16
|
+
'color',
|
|
17
|
+
'help',
|
|
18
|
+
'version',
|
|
19
|
+
'summary',
|
|
20
|
+
'verbose',
|
|
21
|
+
'call-graph',
|
|
22
|
+
'fail-on-new-boundaries',
|
|
23
|
+
'keep',
|
|
24
|
+
'list',
|
|
25
|
+
'json'
|
|
26
|
+
],
|
|
27
|
+
string: [
|
|
28
|
+
'baseline',
|
|
29
|
+
'write-baseline',
|
|
30
|
+
'compare',
|
|
31
|
+
'compare-ref',
|
|
32
|
+
'compare-sha',
|
|
33
|
+
'compare-run-id',
|
|
34
|
+
'compare-run-url',
|
|
35
|
+
'compare-age',
|
|
36
|
+
'compare-skipped',
|
|
37
|
+
'write-snapshot',
|
|
38
|
+
'connection',
|
|
39
|
+
'host',
|
|
40
|
+
'user',
|
|
41
|
+
'password',
|
|
42
|
+
'database',
|
|
43
|
+
'schemas',
|
|
44
|
+
'exclude-schemas',
|
|
45
|
+
'ignore-extensions',
|
|
46
|
+
'roles',
|
|
47
|
+
'exclude-roles',
|
|
48
|
+
'format',
|
|
49
|
+
'fail-on',
|
|
50
|
+
'out',
|
|
51
|
+
'preset',
|
|
52
|
+
'corpus',
|
|
53
|
+
'case',
|
|
54
|
+
'provider',
|
|
55
|
+
'repo',
|
|
56
|
+
'base',
|
|
57
|
+
'head',
|
|
58
|
+
'workflow',
|
|
59
|
+
'artifact',
|
|
60
|
+
'dir'
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
new CLI(commands, options).run().catch((error) => {
|
|
65
|
+
log.error(error);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script A driver: pure-Postgres RLS audit.
|
|
3
|
+
*
|
|
4
|
+
* Ingests a catalog snapshot, runs every check, and returns a structured report.
|
|
5
|
+
*/
|
|
6
|
+
import type { ExposureConfig, SafegresConfig } from '../config/types';
|
|
7
|
+
import { type IntrospectOptions, type QueryExecutor } from '../pg/introspect';
|
|
8
|
+
import type { Report } from '../types';
|
|
9
|
+
export interface AuditOptions extends IntrospectOptions {
|
|
10
|
+
/** If provided, bypass `pg_roles` enumeration. Otherwise enumerate roles dynamically. */
|
|
11
|
+
includeRoles?: string[];
|
|
12
|
+
/** Roles to drop after enumeration. */
|
|
13
|
+
excludeRoles?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Skip AST-level anti-pattern checks (P1, P5). Useful for very fast audits
|
|
16
|
+
* that only want grants + RLS-flag + coverage findings.
|
|
17
|
+
*/
|
|
18
|
+
skipAstChecks?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The exposed API surface. Overrides `config.exposure` when provided.
|
|
21
|
+
* Findings on non-exposed schemas contribute nothing to the score.
|
|
22
|
+
*/
|
|
23
|
+
exposure?: ExposureConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Build the unscored call-graph audit: trust boundaries (SECURITY DEFINER
|
|
26
|
+
* hops, RLS-bypass paths, auth-context mutations) reachable from the
|
|
27
|
+
* exposed entry points. Adds `report.callGraph`.
|
|
28
|
+
*/
|
|
29
|
+
callGraph?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Collect and score the performance dimension: index-hygiene rules (`X*`)
|
|
32
|
+
* on top of the policy-cost rules (P1/P1b) the audit already runs. Adds
|
|
33
|
+
* `report.perf` with its own score. Overrides `config.perf.enabled`.
|
|
34
|
+
*/
|
|
35
|
+
perf?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Collect runtime statistics (`pg_stat_user_tables`, `pg_stat_statements`)
|
|
38
|
+
* and run the `S*` rules. Implies `perf`. Overrides `config.perf.stats.enabled`.
|
|
39
|
+
*/
|
|
40
|
+
stats?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Probe perf findings with `EXPLAIN (GENERIC_PLAN)` and attach the plan as
|
|
43
|
+
* evidence, acknowledging findings the planner refutes. Implies `perf`.
|
|
44
|
+
* Overrides `config.perf.explain.enabled`.
|
|
45
|
+
*/
|
|
46
|
+
explain?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Merged safegres configuration (rules, overrides, scoring). Rule settings
|
|
49
|
+
* filter and retune findings; scoring settings drive the report score.
|
|
50
|
+
* Connection-independent option fields (`schemas`, `roles`, …) present on
|
|
51
|
+
* the config are used as fallbacks for the corresponding AuditOptions.
|
|
52
|
+
*/
|
|
53
|
+
config?: SafegresConfig;
|
|
54
|
+
/**
|
|
55
|
+
* Record the run as sealed: produced under configuration the caller
|
|
56
|
+
* controls, with no local config file, rule overrides or baselines. The
|
|
57
|
+
* audit behaves identically — sealing is enforced by whoever assembles the
|
|
58
|
+
* config (the CLI's `--sealed`) — but the claim lands in
|
|
59
|
+
* `report.provenance` so a harness can require it.
|
|
60
|
+
*/
|
|
61
|
+
sealed?: boolean;
|
|
62
|
+
/** The preset a sealed run was graded under, recorded in the provenance. */
|
|
63
|
+
preset?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function audit(client: QueryExecutor, options?: AuditOptions): Promise<Report>;
|