@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,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API reach: which relations a generated API can actually address.
|
|
3
|
+
*
|
|
4
|
+
* A plane made of schemas answers "is this relation in the API's schemas?",
|
|
5
|
+
* which is as much precision as a schema list can carry. It over-counts in a
|
|
6
|
+
* way that matters: a generated API exposes types and fields, not schemas, and
|
|
7
|
+
* a schema routinely contains relations the API deliberately does not surface
|
|
8
|
+
* — join tables, denormalised shadows, machine-only back-pointers.
|
|
9
|
+
*
|
|
10
|
+
* Where the author has *declared* that, the declaration is better evidence
|
|
11
|
+
* than anything else safegres can collect. `reltuples` and `idx_scan` are
|
|
12
|
+
* measurements, and safegres grades ephemeral CI databases that have never
|
|
13
|
+
* held data, so both read zero at exactly the moment they would have to mean
|
|
14
|
+
* something. A behavior tag reads the same in CI as in production.
|
|
15
|
+
*
|
|
16
|
+
* Two properties keep this honest:
|
|
17
|
+
*
|
|
18
|
+
* - **Only an explicit denial counts.** Presets grant most behaviors by
|
|
19
|
+
* default, so the absence of `+list` says nothing whatsoever. Silence is
|
|
20
|
+
* never read as denial.
|
|
21
|
+
* - **Unreachable means unreachable by *every* route.** A relation the API
|
|
22
|
+
* cannot address at the root can still be addressed by traversing a relation
|
|
23
|
+
* field from one that can, so this is a graph reachability problem and not a
|
|
24
|
+
* per-table test. Subtracting a relation that is in fact addressable would
|
|
25
|
+
* silently drop real findings out of the score, which is the one failure
|
|
26
|
+
* mode worth designing against.
|
|
27
|
+
*/
|
|
28
|
+
import type { BehaviorSnapshot } from '../pg/behaviors';
|
|
29
|
+
/**
|
|
30
|
+
* Abilities that between them cover every root entry point to a relation.
|
|
31
|
+
* All four must be denied before the relation is absent from the API root: a
|
|
32
|
+
* table that cannot be read but can still be inserted into is present.
|
|
33
|
+
*/
|
|
34
|
+
export declare const ROOT_ABILITIES: string[];
|
|
35
|
+
/** The reverse (child-listing) field exists under any one of these. */
|
|
36
|
+
export declare const BACKWARD_ABILITIES: string[];
|
|
37
|
+
/** The forward (parent) field is a single record. */
|
|
38
|
+
export declare const FORWARD_ABILITIES: string[];
|
|
39
|
+
/** One foreign key, as reach cares about it. */
|
|
40
|
+
export interface ReachEdge {
|
|
41
|
+
/** Referencing relation, `schema.table`. */
|
|
42
|
+
from: string;
|
|
43
|
+
/** Referenced relation, `schema.table`. */
|
|
44
|
+
to: string;
|
|
45
|
+
constraint: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ReachInputs {
|
|
48
|
+
/** Every relation in scope, `schema.table`. */
|
|
49
|
+
relations: string[];
|
|
50
|
+
edges: ReachEdge[];
|
|
51
|
+
behaviors: BehaviorSnapshot;
|
|
52
|
+
}
|
|
53
|
+
export interface UnreachableRelation {
|
|
54
|
+
schema: string;
|
|
55
|
+
table: string;
|
|
56
|
+
/** Why it is unreachable, in one human-readable clause. */
|
|
57
|
+
reason: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ApiReach {
|
|
60
|
+
/** Relations no field of the generated API can address. */
|
|
61
|
+
unreachable: UnreachableRelation[];
|
|
62
|
+
/**
|
|
63
|
+
* `schema.table.constraint` for every foreign key whose reverse relation is
|
|
64
|
+
* declared absent. A hidden reverse relation is not the same claim as an
|
|
65
|
+
* unreachable table — it is one path, not all of them — so it is reported
|
|
66
|
+
* separately and consumed by the X1 access-path signals.
|
|
67
|
+
*/
|
|
68
|
+
hiddenBackwardRelations: string[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Compute what the generated API can address.
|
|
72
|
+
*
|
|
73
|
+
* The traversal starts from every relation the API roots — anything not
|
|
74
|
+
* explicitly denied all of {@link ROOT_ABILITIES} — and walks relation fields
|
|
75
|
+
* in both directions. What it never visits is a relation that is denied at the
|
|
76
|
+
* root *and* has no surviving field pointing at it from anywhere reachable.
|
|
77
|
+
*/
|
|
78
|
+
export declare function computeApiReach(inputs: ReachInputs): ApiReach;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API reach: which relations a generated API can actually address.
|
|
3
|
+
*
|
|
4
|
+
* A plane made of schemas answers "is this relation in the API's schemas?",
|
|
5
|
+
* which is as much precision as a schema list can carry. It over-counts in a
|
|
6
|
+
* way that matters: a generated API exposes types and fields, not schemas, and
|
|
7
|
+
* a schema routinely contains relations the API deliberately does not surface
|
|
8
|
+
* — join tables, denormalised shadows, machine-only back-pointers.
|
|
9
|
+
*
|
|
10
|
+
* Where the author has *declared* that, the declaration is better evidence
|
|
11
|
+
* than anything else safegres can collect. `reltuples` and `idx_scan` are
|
|
12
|
+
* measurements, and safegres grades ephemeral CI databases that have never
|
|
13
|
+
* held data, so both read zero at exactly the moment they would have to mean
|
|
14
|
+
* something. A behavior tag reads the same in CI as in production.
|
|
15
|
+
*
|
|
16
|
+
* Two properties keep this honest:
|
|
17
|
+
*
|
|
18
|
+
* - **Only an explicit denial counts.** Presets grant most behaviors by
|
|
19
|
+
* default, so the absence of `+list` says nothing whatsoever. Silence is
|
|
20
|
+
* never read as denial.
|
|
21
|
+
* - **Unreachable means unreachable by *every* route.** A relation the API
|
|
22
|
+
* cannot address at the root can still be addressed by traversing a relation
|
|
23
|
+
* field from one that can, so this is a graph reachability problem and not a
|
|
24
|
+
* per-table test. Subtracting a relation that is in fact addressable would
|
|
25
|
+
* silently drop real findings out of the score, which is the one failure
|
|
26
|
+
* mode worth designing against.
|
|
27
|
+
*/
|
|
28
|
+
import { deniesAll, directionalBehavior } from '../pg/behaviors';
|
|
29
|
+
/**
|
|
30
|
+
* Abilities that between them cover every root entry point to a relation.
|
|
31
|
+
* All four must be denied before the relation is absent from the API root: a
|
|
32
|
+
* table that cannot be read but can still be inserted into is present.
|
|
33
|
+
*/
|
|
34
|
+
export const ROOT_ABILITIES = ['select', 'insert', 'update', 'delete'];
|
|
35
|
+
/** The reverse (child-listing) field exists under any one of these. */
|
|
36
|
+
export const BACKWARD_ABILITIES = ['list', 'connection', 'single'];
|
|
37
|
+
/** The forward (parent) field is a single record. */
|
|
38
|
+
export const FORWARD_ABILITIES = ['single'];
|
|
39
|
+
function splitRelation(relation) {
|
|
40
|
+
const dot = relation.indexOf('.');
|
|
41
|
+
return { schema: relation.slice(0, dot), table: relation.slice(dot + 1) };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compute what the generated API can address.
|
|
45
|
+
*
|
|
46
|
+
* The traversal starts from every relation the API roots — anything not
|
|
47
|
+
* explicitly denied all of {@link ROOT_ABILITIES} — and walks relation fields
|
|
48
|
+
* in both directions. What it never visits is a relation that is denied at the
|
|
49
|
+
* root *and* has no surviving field pointing at it from anywhere reachable.
|
|
50
|
+
*/
|
|
51
|
+
export function computeApiReach(inputs) {
|
|
52
|
+
const { relations, edges, behaviors } = inputs;
|
|
53
|
+
const inScope = new Set(relations);
|
|
54
|
+
const rooted = relations.filter((relation) => !deniesAll(behaviors.tables.get(relation), ROOT_ABILITIES));
|
|
55
|
+
const hiddenBackwardRelations = [];
|
|
56
|
+
const adjacency = new Map();
|
|
57
|
+
const addEdge = (from, to) => {
|
|
58
|
+
const list = adjacency.get(from);
|
|
59
|
+
if (list)
|
|
60
|
+
list.push(to);
|
|
61
|
+
else
|
|
62
|
+
adjacency.set(from, [to]);
|
|
63
|
+
};
|
|
64
|
+
for (const edge of edges) {
|
|
65
|
+
const key = `${edge.from}.${edge.constraint}`;
|
|
66
|
+
const directions = behaviors.constraintDirections.get(key);
|
|
67
|
+
const backwardHidden = deniesAll(directionalBehavior(directions, 'backward'), BACKWARD_ABILITIES);
|
|
68
|
+
if (backwardHidden)
|
|
69
|
+
hiddenBackwardRelations.push(key);
|
|
70
|
+
// The reverse field lives on the referenced table and reaches the
|
|
71
|
+
// referencing one.
|
|
72
|
+
if (!backwardHidden && inScope.has(edge.to))
|
|
73
|
+
addEdge(edge.to, edge.from);
|
|
74
|
+
const forwardHidden = deniesAll(directionalBehavior(directions, 'forward'), FORWARD_ABILITIES);
|
|
75
|
+
if (!forwardHidden && inScope.has(edge.from))
|
|
76
|
+
addEdge(edge.from, edge.to);
|
|
77
|
+
}
|
|
78
|
+
const reached = new Set();
|
|
79
|
+
const queue = [...rooted];
|
|
80
|
+
for (const relation of rooted)
|
|
81
|
+
reached.add(relation);
|
|
82
|
+
while (queue.length > 0) {
|
|
83
|
+
const current = queue.pop();
|
|
84
|
+
for (const next of adjacency.get(current) ?? []) {
|
|
85
|
+
if (!inScope.has(next) || reached.has(next))
|
|
86
|
+
continue;
|
|
87
|
+
reached.add(next);
|
|
88
|
+
queue.push(next);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const unreachable = [];
|
|
92
|
+
for (const relation of relations) {
|
|
93
|
+
if (reached.has(relation))
|
|
94
|
+
continue;
|
|
95
|
+
const inbound = edges.filter((e) => e.to === relation || e.from === relation).length;
|
|
96
|
+
unreachable.push({
|
|
97
|
+
...splitRelation(relation),
|
|
98
|
+
reason: `a behavior denies ${ROOT_ABILITIES.join(', ')} on the relation`
|
|
99
|
+
+ (inbound > 0
|
|
100
|
+
? ', and every relation field that could reach it is denied too'
|
|
101
|
+
: ', and no relation field points at it')
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return { unreachable, hiddenBackwardRelations: hiddenBackwardRelations.sort() };
|
|
105
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routine exposure: which functions an untrusted role can actually call.
|
|
3
|
+
*
|
|
4
|
+
* The exposure surface answers "which relations can the API address?", and
|
|
5
|
+
* every finding was graded by that question — including the ones that are not
|
|
6
|
+
* about a relation at all. A definer function lives in a private schema, so by
|
|
7
|
+
* relation reach it is unexposed; but `anonymous` holds `EXECUTE` on it, so it
|
|
8
|
+
* is one call away from whatever its owner can touch. Grading it by its schema
|
|
9
|
+
* is not conservative, it is wrong in the dangerous direction.
|
|
10
|
+
*
|
|
11
|
+
* The question a routine finding is graded by is therefore the callability
|
|
12
|
+
* one: can an untrusted role reach the function itself — `USAGE` on its
|
|
13
|
+
* schema, and `EXECUTE` on the function, directly, by inheritance, or through
|
|
14
|
+
* Postgres's default `EXECUTE TO PUBLIC`.
|
|
15
|
+
*/
|
|
16
|
+
import type { RoleGraph } from '../checks/lattice';
|
|
17
|
+
import { type SchemaAclInfo } from '../pg/acl';
|
|
18
|
+
import type { FunctionSnapshot } from '../pg/functions';
|
|
19
|
+
export interface RoutineReachOptions {
|
|
20
|
+
/** Roles a request can arrive as — the API edge, plus its anonymous roles. */
|
|
21
|
+
roles: string[];
|
|
22
|
+
graph: RoleGraph;
|
|
23
|
+
schemaAcls: Map<string, SchemaAclInfo>;
|
|
24
|
+
/**
|
|
25
|
+
* Schemas on the API surface. A trigger function is not directly callable
|
|
26
|
+
* whatever its ACL says, so it is judged the way its firing is: reachable
|
|
27
|
+
* when a write that fires it is.
|
|
28
|
+
*/
|
|
29
|
+
exposedSchemas: Set<string>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Every `schema.name` an untrusted role can execute. Overloads collapse: the
|
|
33
|
+
* finding names a function, not a signature, and a role that can call one
|
|
34
|
+
* overload can reach the body the rule is talking about.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveRoutineReach(functions: FunctionSnapshot[], options: RoutineReachOptions): Set<string>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routine exposure: which functions an untrusted role can actually call.
|
|
3
|
+
*
|
|
4
|
+
* The exposure surface answers "which relations can the API address?", and
|
|
5
|
+
* every finding was graded by that question — including the ones that are not
|
|
6
|
+
* about a relation at all. A definer function lives in a private schema, so by
|
|
7
|
+
* relation reach it is unexposed; but `anonymous` holds `EXECUTE` on it, so it
|
|
8
|
+
* is one call away from whatever its owner can touch. Grading it by its schema
|
|
9
|
+
* is not conservative, it is wrong in the dangerous direction.
|
|
10
|
+
*
|
|
11
|
+
* The question a routine finding is graded by is therefore the callability
|
|
12
|
+
* one: can an untrusted role reach the function itself — `USAGE` on its
|
|
13
|
+
* schema, and `EXECUTE` on the function, directly, by inheritance, or through
|
|
14
|
+
* Postgres's default `EXECUTE TO PUBLIC`.
|
|
15
|
+
*/
|
|
16
|
+
import { canEnterSchema } from '../pg/acl';
|
|
17
|
+
/**
|
|
18
|
+
* Every `schema.name` an untrusted role can execute. Overloads collapse: the
|
|
19
|
+
* finding names a function, not a signature, and a role that can call one
|
|
20
|
+
* overload can reach the body the rule is talking about.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveRoutineReach(functions, options) {
|
|
23
|
+
const reachable = new Set();
|
|
24
|
+
if (options.roles.length === 0)
|
|
25
|
+
return reachable;
|
|
26
|
+
for (const fn of functions) {
|
|
27
|
+
const key = `${fn.schema}.${fn.name}`;
|
|
28
|
+
if (reachable.has(key))
|
|
29
|
+
continue;
|
|
30
|
+
if (fn.returnsTrigger) {
|
|
31
|
+
// Postgres refuses a direct call however wide the ACL is. What reaches
|
|
32
|
+
// the body is a write to the table the trigger is attached to, so the
|
|
33
|
+
// API surface is the right question after all.
|
|
34
|
+
if (options.exposedSchemas.has(fn.schema))
|
|
35
|
+
reachable.add(key);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (options.roles.some((role) => canExecute(fn, role, options)))
|
|
39
|
+
reachable.add(key);
|
|
40
|
+
}
|
|
41
|
+
return reachable;
|
|
42
|
+
}
|
|
43
|
+
function canExecute(fn, role, { graph, schemaAcls }) {
|
|
44
|
+
if (!canEnterSchema(fn.schema, role, graph, schemaAcls))
|
|
45
|
+
return false;
|
|
46
|
+
const attrs = graph.get(role);
|
|
47
|
+
if (attrs?.isSuper)
|
|
48
|
+
return true;
|
|
49
|
+
if (role === fn.owner)
|
|
50
|
+
return true;
|
|
51
|
+
return fn.grants.some((g) => g.role === 'PUBLIC'
|
|
52
|
+
|| g.role === role
|
|
53
|
+
|| (attrs?.inheritsFrom ?? []).includes(g.role));
|
|
54
|
+
}
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of `safegres` — a pure-PostgreSQL Row-Level-Security auditor.
|
|
3
|
+
*
|
|
4
|
+
* The auditor introspects pg_class / pg_policy / role grants and emits
|
|
5
|
+
* structured findings (A1–A7, P1, P5). It has no knowledge of any specific
|
|
6
|
+
* application schema or policy DSL.
|
|
7
|
+
*/
|
|
8
|
+
export { parsePolicyExpression, type PgAstNode, type PolicyExpression, PolicyParseError } from './ast/parse';
|
|
9
|
+
export type { BaselineBoundary, CallGraphBaseline, CallGraphDiff } from './callgraph/baseline';
|
|
10
|
+
export { boundaryKey, diffCallGraph, parseBaseline, serializeBaseline, toBaseline } from './callgraph/baseline';
|
|
11
|
+
export type { CallGraphEdge, CallGraphNode, CallGraphOptions, CallGraphReport, ChecklistCode, ChecklistItem } from './callgraph/graph';
|
|
12
|
+
export { buildCallGraph } from './callgraph/graph';
|
|
13
|
+
export { checkUntrustedColumnGrants } from './checks/column-grants';
|
|
14
|
+
export type { FunctionBodyAnalysis } from './checks/definer-function';
|
|
15
|
+
export { analyzeFunctionBodies, checkDefinerFunctionReach, checkInsteadOfTriggerWrite, checkUnreadableFunctionReach } from './checks/definer-function';
|
|
16
|
+
export type { SuppressedView, ViewBodyAnalysis } from './checks/definer-view';
|
|
17
|
+
export { analyzeViewBodies, checkDefinerViewBypass, checkUnauditedViewReach, checkUnreadableViewReach } from './checks/definer-view';
|
|
18
|
+
export { checkMissingPrimaryKey, checkRedundantIndexes, checkUnindexedForeignKeys, checkUnindexedSearchColumns, checkUnindexedSortColumns } from './checks/indexes';
|
|
19
|
+
export type { EffectiveColumnGrant, EffectiveGrant, GrantVia, LatticeRoleOptions, RoleAccessEntry, RoleAccessRelation, RoleGraph } from './checks/lattice';
|
|
20
|
+
export { checkDeadPolicies, checkDeadSchemaUsage, checkIndirectCoverageGaps, checkUnreachableGrants, checkUntrustedIndirectAccess, computeRoleAccess, effectiveColumnGrants, effectiveGrants } from './checks/lattice';
|
|
21
|
+
export { checkUntrustedForeignTableGrants, checkUntrustedSequenceGrants } from './checks/object-acls';
|
|
22
|
+
export type { PolicyClause, PredicateColumn } from './checks/policy-index';
|
|
23
|
+
export { checkNonLeakproofPolicyFunctions, checkPolicyColumnCasts, checkUnhoistedPolicyFunctions, checkUnindexedPolicyColumns, collectPredicateColumns } from './checks/policy-index';
|
|
24
|
+
export type { RoleTrustOptions } from './checks/role-trust';
|
|
25
|
+
export { checkPublicGrants, checkUntrustedRolePolicies, checkUntrustedRoleWrites } from './checks/role-trust';
|
|
26
|
+
export type { StatsThresholds } from './checks/stats';
|
|
27
|
+
export { checkDeadTuples, checkSeqScanDominant, checkStats, checkTopStatements, checkUnusedIndexes, DEFAULT_STATS_THRESHOLDS } from './checks/stats';
|
|
28
|
+
export type { ViewExposureAnalysis } from './checks/view-exposure';
|
|
29
|
+
export { analyzeViewExposure, checkLeakyFilterView, checkMatviewSnapshot } from './checks/view-exposure';
|
|
30
|
+
export type { ViewWriteAnalysis } from './checks/view-writes';
|
|
31
|
+
export { analyzeViewWrites, checkDefinerViewWrite, checkUncheckedViewWrite, checkViewRuleBypass } from './checks/view-writes';
|
|
32
|
+
export type { BaselineCandidate, BaselineSelection, ChosenBaseline, RejectedCandidate, SelectBaselineOptions } from './ci/baseline';
|
|
33
|
+
export { CANDIDATE_LIMIT, MAX_BASELINE_AGE_MS, selectBaseline } from './ci/baseline';
|
|
34
|
+
export type { GithubBaselineOptions, GithubBaselineResult } from './ci/github';
|
|
35
|
+
export { selectGithubBaseline } from './ci/github';
|
|
36
|
+
export type { AuditOptions } from './commands/audit';
|
|
37
|
+
export { audit } from './commands/audit';
|
|
38
|
+
export type { DoctorCheck, DoctorOptions, DoctorReport, DoctorStatus } from './commands/doctor';
|
|
39
|
+
export { doctor } from './commands/doctor';
|
|
40
|
+
export type { EvalCaseResult, EvalOptions, EvalReport } from './commands/eval';
|
|
41
|
+
export { runEval } from './commands/eval';
|
|
42
|
+
export type { Provenance } from './config/fingerprint';
|
|
43
|
+
export { configFingerprint } from './config/fingerprint';
|
|
44
|
+
export type { LoadConfigParams } from './config/loader';
|
|
45
|
+
export { loadConfig, safegresConfigLoader } from './config/loader';
|
|
46
|
+
export { constructive, graphile, hasura, minimal, multiTenant, oltp, postgrest, PRESETS, recommended, strict, supabase } from './config/presets';
|
|
47
|
+
export type { ResolvedRule, ResolvedRules } from './config/resolve';
|
|
48
|
+
export { allAstRulesDisabled, applyRulesToFindings, ConfigValidationError, defaultRuleMap, matchTablePattern, resolveRules, ruleCodesForDimension, rulesForTable } from './config/resolve';
|
|
49
|
+
export { CONFIG_SHAPE, SCHEMA_ID, toJsonSchema } from './config/schema';
|
|
50
|
+
export type { CallGraphConfig, EvalConfig, ExposureConfig, FailOnConfig, GithubCommentConfig, GithubReportConfig, Grade, OutputsConfig, OverrideEntry, PerfConfig, PlaneConfig, PlaneFailOnConfig, PlaneKind, ReportConfig, RulesConfig, RuleSetting, SafegresConfig, ScoringConfig, SourceConfig } from './config/types';
|
|
51
|
+
export { validateConfigShape } from './config/validate';
|
|
52
|
+
export type { CaseResult, CorpusCase, ExpectedFinding } from './corpus';
|
|
53
|
+
export { corpusBootstrap, corpusDir, gradeCase, loadCase, loadCorpus } from './corpus';
|
|
54
|
+
export type { ExposureAdapter, PlaneInput, ReachContext } from './exposure/adapters';
|
|
55
|
+
export { BUILTIN_ADAPTERS, constructiveAdapter, definePlanes, graphileAdapter, hasuraAdapter, postgraphileAdapter, postgrestAdapter, resolveAdapters, supabaseAdapter } from './exposure/adapters';
|
|
56
|
+
export type { PlaneReach } from './exposure/planes';
|
|
57
|
+
export { onPlane, relationKey, resolvePlaneReach, scorePlane, stampPlanes } from './exposure/planes';
|
|
58
|
+
export type { ApiReach, ReachEdge, ReachInputs, UnreachableRelation } from './exposure/reach';
|
|
59
|
+
export { BACKWARD_ABILITIES, computeApiReach, FORWARD_ABILITIES, ROOT_ABILITIES } from './exposure/reach';
|
|
60
|
+
export type { BaselineFinding, PerfBaseline, PerfDiff } from './perf/baseline';
|
|
61
|
+
export { diffPerf, findingKey, parsePerfBaseline, serializePerfBaseline, subjectOf, toBaselineFinding, toPerfBaseline } from './perf/baseline';
|
|
62
|
+
export type { ExplainOptions, ExplainReport } from './perf/explain';
|
|
63
|
+
export { proveFindings } from './perf/explain';
|
|
64
|
+
export type { RoleAttributes, SchemaAclGrant, SchemaAclInfo, SchemaAclOptions } from './pg/acl';
|
|
65
|
+
export { introspectRoleGraph, introspectSchemaAcls } from './pg/acl';
|
|
66
|
+
export type { ResolvedExposure, ResolvedPlane } from './pg/exposure';
|
|
67
|
+
export { resolveConstructiveExposure, resolveExposure, resolvePlanes, resolveReach, UNKNOWN_EXPOSURE } from './pg/exposure';
|
|
68
|
+
export type { FunctionGrant, FunctionSnapshot, IntrospectFunctionOptions } from './pg/functions';
|
|
69
|
+
export { introspectFunctions } from './pg/functions';
|
|
70
|
+
export type { ColumnInfo, ForeignKeyInfo, IndexInfo, TableIndexSnapshot, ViewSnapshot } from './pg/indexes';
|
|
71
|
+
export { introspectIndexes, introspectViews } from './pg/indexes';
|
|
72
|
+
export { type ColumnGrantInfo, type GrantInfo, type IntrospectOptions, introspectTables, type PgPrivilege, type PolicyCmd, type PolicyInfo, type QueryExecutor, type TableSnapshot } from './pg/introspect';
|
|
73
|
+
export type { ObjectAclSnapshot, PgObjectKind } from './pg/objects';
|
|
74
|
+
export { introspectObjectAcls } from './pg/objects';
|
|
75
|
+
export { listAuditableRoles, resolveRoles } from './pg/roles';
|
|
76
|
+
export type { IndexUsage, StatementUsage, StatsSnapshot, TableUsage } from './pg/stats';
|
|
77
|
+
export { introspectStats } from './pg/stats';
|
|
78
|
+
export { renderCallGraph, renderCallGraphDiff } from './report/callgraph';
|
|
79
|
+
export type { BaselineProvenance, DimensionSnapshot, ReportComparison, ReportSnapshot, RuleDelta, ScoreDelta } from './report/compare';
|
|
80
|
+
export { baselineLabel, compareReports, describeBaseline, formatAge, formatDelta, parseSnapshot, serializeSnapshot, toSnapshot } from './report/compare';
|
|
81
|
+
export type { GithubRenderOptions } from './report/github';
|
|
82
|
+
export { COMMENT_MARKER, emitGithub, gradeBadge, postStickyComment, renderAnnotations, renderGithubComment, renderGithubSummary, scoreBadge } from './report/github';
|
|
83
|
+
export { renderJson } from './report/json';
|
|
84
|
+
export type { RenderMarkdownOptions } from './report/markdown';
|
|
85
|
+
export { renderMarkdown } from './report/markdown';
|
|
86
|
+
export type { RenderPrettyOptions } from './report/pretty';
|
|
87
|
+
export { renderPretty } from './report/pretty';
|
|
88
|
+
export type { BuildSourceIndexOptions, RenderSarifOptions, SourceIndex, SourceLocation } from './report/sarif';
|
|
89
|
+
export { buildSourceIndex, renderSarif } from './report/sarif';
|
|
90
|
+
export type { ReportView, ViewConfig, ViewDimension, ViewFindings, ViewScore, ViewSection } from './report/view';
|
|
91
|
+
export { ALL_SECTIONS, matchPlane, selectView, viewConfigFromReportConfig } from './report/view';
|
|
92
|
+
export type { RuleMeta } from './rules/registry';
|
|
93
|
+
export { dimensionOf, expandRuleSelector, isKnownRule, RULES, RULES_BY_CODE } from './rules/registry';
|
|
94
|
+
export type { Score, ScoreContext, ScoreDeduction } from './score/score';
|
|
95
|
+
export { computeScore, DEFAULT_GRADE_BANDS, DEFAULT_WEIGHTS, meetsGrade } from './score/score';
|
|
96
|
+
export * from './types';
|
package/esm/index.js
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of `safegres` — a pure-PostgreSQL Row-Level-Security auditor.
|
|
3
|
+
*
|
|
4
|
+
* The auditor introspects pg_class / pg_policy / role grants and emits
|
|
5
|
+
* structured findings (A1–A7, P1, P5). It has no knowledge of any specific
|
|
6
|
+
* application schema or policy DSL.
|
|
7
|
+
*/
|
|
8
|
+
export { parsePolicyExpression, PolicyParseError } from './ast/parse';
|
|
9
|
+
export { boundaryKey, diffCallGraph, parseBaseline, serializeBaseline, toBaseline } from './callgraph/baseline';
|
|
10
|
+
export { buildCallGraph } from './callgraph/graph';
|
|
11
|
+
export { checkUntrustedColumnGrants } from './checks/column-grants';
|
|
12
|
+
export { analyzeFunctionBodies, checkDefinerFunctionReach, checkInsteadOfTriggerWrite, checkUnreadableFunctionReach } from './checks/definer-function';
|
|
13
|
+
export { analyzeViewBodies, checkDefinerViewBypass, checkUnauditedViewReach, checkUnreadableViewReach } from './checks/definer-view';
|
|
14
|
+
export { checkMissingPrimaryKey, checkRedundantIndexes, checkUnindexedForeignKeys, checkUnindexedSearchColumns, checkUnindexedSortColumns } from './checks/indexes';
|
|
15
|
+
export { checkDeadPolicies, checkDeadSchemaUsage, checkIndirectCoverageGaps, checkUnreachableGrants, checkUntrustedIndirectAccess, computeRoleAccess, effectiveColumnGrants, effectiveGrants } from './checks/lattice';
|
|
16
|
+
export { checkUntrustedForeignTableGrants, checkUntrustedSequenceGrants } from './checks/object-acls';
|
|
17
|
+
export { checkNonLeakproofPolicyFunctions, checkPolicyColumnCasts, checkUnhoistedPolicyFunctions, checkUnindexedPolicyColumns, collectPredicateColumns } from './checks/policy-index';
|
|
18
|
+
export { checkPublicGrants, checkUntrustedRolePolicies, checkUntrustedRoleWrites } from './checks/role-trust';
|
|
19
|
+
export { checkDeadTuples, checkSeqScanDominant, checkStats, checkTopStatements, checkUnusedIndexes, DEFAULT_STATS_THRESHOLDS } from './checks/stats';
|
|
20
|
+
export { analyzeViewExposure, checkLeakyFilterView, checkMatviewSnapshot } from './checks/view-exposure';
|
|
21
|
+
export { analyzeViewWrites, checkDefinerViewWrite, checkUncheckedViewWrite, checkViewRuleBypass } from './checks/view-writes';
|
|
22
|
+
export { CANDIDATE_LIMIT, MAX_BASELINE_AGE_MS, selectBaseline } from './ci/baseline';
|
|
23
|
+
export { selectGithubBaseline } from './ci/github';
|
|
24
|
+
export { audit } from './commands/audit';
|
|
25
|
+
export { doctor } from './commands/doctor';
|
|
26
|
+
export { runEval } from './commands/eval';
|
|
27
|
+
export { configFingerprint } from './config/fingerprint';
|
|
28
|
+
export { loadConfig, safegresConfigLoader } from './config/loader';
|
|
29
|
+
export { constructive, graphile, hasura, minimal, multiTenant, oltp, postgrest, PRESETS, recommended, strict, supabase } from './config/presets';
|
|
30
|
+
export { allAstRulesDisabled, applyRulesToFindings, ConfigValidationError, defaultRuleMap, matchTablePattern, resolveRules, ruleCodesForDimension, rulesForTable } from './config/resolve';
|
|
31
|
+
export { CONFIG_SHAPE, SCHEMA_ID, toJsonSchema } from './config/schema';
|
|
32
|
+
export { validateConfigShape } from './config/validate';
|
|
33
|
+
export { corpusBootstrap, corpusDir, gradeCase, loadCase, loadCorpus } from './corpus';
|
|
34
|
+
export { BUILTIN_ADAPTERS, constructiveAdapter, definePlanes, graphileAdapter, hasuraAdapter, postgraphileAdapter, postgrestAdapter, resolveAdapters, supabaseAdapter } from './exposure/adapters';
|
|
35
|
+
export { onPlane, relationKey, resolvePlaneReach, scorePlane, stampPlanes } from './exposure/planes';
|
|
36
|
+
export { BACKWARD_ABILITIES, computeApiReach, FORWARD_ABILITIES, ROOT_ABILITIES } from './exposure/reach';
|
|
37
|
+
export { diffPerf, findingKey, parsePerfBaseline, serializePerfBaseline, subjectOf, toBaselineFinding, toPerfBaseline } from './perf/baseline';
|
|
38
|
+
export { proveFindings } from './perf/explain';
|
|
39
|
+
export { introspectRoleGraph, introspectSchemaAcls } from './pg/acl';
|
|
40
|
+
export { resolveConstructiveExposure, resolveExposure, resolvePlanes, resolveReach, UNKNOWN_EXPOSURE } from './pg/exposure';
|
|
41
|
+
export { introspectFunctions } from './pg/functions';
|
|
42
|
+
export { introspectIndexes, introspectViews } from './pg/indexes';
|
|
43
|
+
export { introspectTables } from './pg/introspect';
|
|
44
|
+
export { introspectObjectAcls } from './pg/objects';
|
|
45
|
+
export { listAuditableRoles, resolveRoles } from './pg/roles';
|
|
46
|
+
export { introspectStats } from './pg/stats';
|
|
47
|
+
export { renderCallGraph, renderCallGraphDiff } from './report/callgraph';
|
|
48
|
+
export { baselineLabel, compareReports, describeBaseline, formatAge, formatDelta, parseSnapshot, serializeSnapshot, toSnapshot } from './report/compare';
|
|
49
|
+
export { COMMENT_MARKER, emitGithub, gradeBadge, postStickyComment, renderAnnotations, renderGithubComment, renderGithubSummary, scoreBadge } from './report/github';
|
|
50
|
+
export { renderJson } from './report/json';
|
|
51
|
+
export { renderMarkdown } from './report/markdown';
|
|
52
|
+
export { renderPretty } from './report/pretty';
|
|
53
|
+
export { buildSourceIndex, renderSarif } from './report/sarif';
|
|
54
|
+
export { ALL_SECTIONS, matchPlane, selectView, viewConfigFromReportConfig } from './report/view';
|
|
55
|
+
export { dimensionOf, expandRuleSelector, isKnownRule, RULES, RULES_BY_CODE } from './rules/registry';
|
|
56
|
+
export { computeScore, DEFAULT_GRADE_BANDS, DEFAULT_WEIGHTS, meetsGrade } from './score/score';
|
|
57
|
+
export * from './types';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-level SQL/PL/pgSQL convention linter.
|
|
3
|
+
*
|
|
4
|
+
* This module used to carry safegres's own copy of the linter. It now lives in
|
|
5
|
+
* the standalone `@pgsql/lint` package (source text in → findings out, no `pg`
|
|
6
|
+
* dependency); safegres consumes it as "the catalog adapter", feeding it the
|
|
7
|
+
* `pg_get_functiondef` text it already reads. This file is a thin re-export so
|
|
8
|
+
* the rest of safegres keeps importing from `../lint` unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export type { DynamicSqlSite, LintOptions, LintProblem, LintResult, LintRule, LintRuleMeta, LintUnit, SqlFragment, SuppressedProblem, SuppressionScope } from '@pgsql/lint';
|
|
11
|
+
export { LINT_RULES, LINT_RULES_BY_CODE, LINT_RULES_BY_ID, lintDefinition, parseUnit, Suppressions } from '@pgsql/lint';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-level SQL/PL/pgSQL convention linter.
|
|
3
|
+
*
|
|
4
|
+
* This module used to carry safegres's own copy of the linter. It now lives in
|
|
5
|
+
* the standalone `@pgsql/lint` package (source text in → findings out, no `pg`
|
|
6
|
+
* dependency); safegres consumes it as "the catalog adapter", feeding it the
|
|
7
|
+
* `pg_get_functiondef` text it already reads. This file is a thin re-export so
|
|
8
|
+
* the rest of safegres keeps importing from `../lint` unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export { LINT_RULES, LINT_RULES_BY_CODE, LINT_RULES_BY_ID, lintDefinition, parseUnit, Suppressions } from '@pgsql/lint';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perf baseline: a committed snapshot of accepted performance debt.
|
|
3
|
+
*
|
|
4
|
+
* The point is the ratchet. A large schema will not reach a clean perf report
|
|
5
|
+
* in one pass, but it can refuse to get worse: commit the current findings,
|
|
6
|
+
* then fail CI only on findings that are *not* in the baseline.
|
|
7
|
+
*
|
|
8
|
+
* Only the identity of a finding is stored — code, relation, policy, and the
|
|
9
|
+
* one context field that distinguishes findings of the same code on the same
|
|
10
|
+
* relation. Messages, severities and hints may be reworded between safegres
|
|
11
|
+
* versions without invalidating a committed baseline.
|
|
12
|
+
*/
|
|
13
|
+
import type { Finding } from '../types';
|
|
14
|
+
export interface PerfBaseline {
|
|
15
|
+
version: 1;
|
|
16
|
+
findings: BaselineFinding[];
|
|
17
|
+
}
|
|
18
|
+
export interface BaselineFinding {
|
|
19
|
+
code: string;
|
|
20
|
+
schema?: string;
|
|
21
|
+
table?: string;
|
|
22
|
+
policy?: string;
|
|
23
|
+
/** The rule-specific subject: constraint, index, column, expression, or function. */
|
|
24
|
+
subject?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PerfDiff {
|
|
27
|
+
/** Findings present now but absent from the baseline — new debt. */
|
|
28
|
+
added: Finding[];
|
|
29
|
+
/** Baseline entries no longer produced — fixed, or the relation is gone. */
|
|
30
|
+
removed: BaselineFinding[];
|
|
31
|
+
/** Findings that matched the baseline — accepted debt, still reported. */
|
|
32
|
+
accepted: Finding[];
|
|
33
|
+
}
|
|
34
|
+
export declare function subjectOf(finding: Finding): string | undefined;
|
|
35
|
+
export declare function findingKey(entry: BaselineFinding): string;
|
|
36
|
+
export declare function toBaselineFinding(finding: Finding): BaselineFinding;
|
|
37
|
+
export declare function toPerfBaseline(findings: Finding[]): PerfBaseline;
|
|
38
|
+
export declare function diffPerf(findings: Finding[], baseline: PerfBaseline): PerfDiff;
|
|
39
|
+
export declare function parsePerfBaseline(raw: string): PerfBaseline;
|
|
40
|
+
export declare function serializePerfBaseline(baseline: PerfBaseline): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perf baseline: a committed snapshot of accepted performance debt.
|
|
3
|
+
*
|
|
4
|
+
* The point is the ratchet. A large schema will not reach a clean perf report
|
|
5
|
+
* in one pass, but it can refuse to get worse: commit the current findings,
|
|
6
|
+
* then fail CI only on findings that are *not* in the baseline.
|
|
7
|
+
*
|
|
8
|
+
* Only the identity of a finding is stored — code, relation, policy, and the
|
|
9
|
+
* one context field that distinguishes findings of the same code on the same
|
|
10
|
+
* relation. Messages, severities and hints may be reworded between safegres
|
|
11
|
+
* versions without invalidating a committed baseline.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Context keys that identify *which* object a finding is about, in priority
|
|
15
|
+
* order (X3 carries both `expression` and `column`; the expression is the
|
|
16
|
+
* narrower identity). Rules that can only fire once per relation — X6 — set
|
|
17
|
+
* none of these and are keyed by code + relation alone.
|
|
18
|
+
*/
|
|
19
|
+
const SUBJECT_KEYS = ['constraint', 'index', 'expression', 'column', 'function'];
|
|
20
|
+
export function subjectOf(finding) {
|
|
21
|
+
const context = finding.context;
|
|
22
|
+
if (!context)
|
|
23
|
+
return undefined;
|
|
24
|
+
for (const key of SUBJECT_KEYS) {
|
|
25
|
+
const value = context[key];
|
|
26
|
+
if (typeof value === 'string' && value.length > 0)
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
export function findingKey(entry) {
|
|
32
|
+
return [entry.code, entry.schema ?? '', entry.table ?? '', entry.policy ?? '', entry.subject ?? ''].join('|');
|
|
33
|
+
}
|
|
34
|
+
export function toBaselineFinding(finding) {
|
|
35
|
+
const subject = subjectOf(finding);
|
|
36
|
+
return {
|
|
37
|
+
code: finding.code,
|
|
38
|
+
...(finding.schema ? { schema: finding.schema } : {}),
|
|
39
|
+
...(finding.table ? { table: finding.table } : {}),
|
|
40
|
+
...(finding.policy ? { policy: finding.policy } : {}),
|
|
41
|
+
...(subject ? { subject } : {})
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function toPerfBaseline(findings) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const finding of findings) {
|
|
48
|
+
const entry = toBaselineFinding(finding);
|
|
49
|
+
const key = findingKey(entry);
|
|
50
|
+
if (seen.has(key))
|
|
51
|
+
continue;
|
|
52
|
+
seen.add(key);
|
|
53
|
+
out.push(entry);
|
|
54
|
+
}
|
|
55
|
+
out.sort((a, b) => findingKey(a).localeCompare(findingKey(b)));
|
|
56
|
+
return { version: 1, findings: out };
|
|
57
|
+
}
|
|
58
|
+
export function diffPerf(findings, baseline) {
|
|
59
|
+
const baseKeys = new Set(baseline.findings.map(findingKey));
|
|
60
|
+
const currentKeys = new Set();
|
|
61
|
+
const added = [];
|
|
62
|
+
const accepted = [];
|
|
63
|
+
for (const finding of findings) {
|
|
64
|
+
const key = findingKey(toBaselineFinding(finding));
|
|
65
|
+
currentKeys.add(key);
|
|
66
|
+
if (baseKeys.has(key))
|
|
67
|
+
accepted.push(finding);
|
|
68
|
+
else
|
|
69
|
+
added.push(finding);
|
|
70
|
+
}
|
|
71
|
+
const removed = baseline.findings.filter((entry) => !currentKeys.has(findingKey(entry)));
|
|
72
|
+
return { added, removed, accepted };
|
|
73
|
+
}
|
|
74
|
+
export function parsePerfBaseline(raw) {
|
|
75
|
+
const data = JSON.parse(raw);
|
|
76
|
+
if (data.version !== 1 || !Array.isArray(data.findings)) {
|
|
77
|
+
throw new Error('invalid perf baseline: expected { version: 1, findings: [...] }');
|
|
78
|
+
}
|
|
79
|
+
return { version: 1, findings: data.findings };
|
|
80
|
+
}
|
|
81
|
+
export function serializePerfBaseline(baseline) {
|
|
82
|
+
return JSON.stringify(baseline, null, 2) + '\n';
|
|
83
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof by EXPLAIN (`--explain`).
|
|
3
|
+
*
|
|
4
|
+
* The `X*` rules infer from the catalog that a query *shape* has no index to
|
|
5
|
+
* serve it. This turns the inference into evidence: for each finding that has
|
|
6
|
+
* a probe shape, plan the query the finding is about and read the plan back.
|
|
7
|
+
*
|
|
8
|
+
* The planner is the authority, so the interesting outcome is disagreement —
|
|
9
|
+
* a finding whose probe plans as an index scan is **refuted** (some index the
|
|
10
|
+
* catalog rules didn't credit does serve it) and stops counting against the
|
|
11
|
+
* score. The reverse is not symmetrical: an empty or unanalyzed table always
|
|
12
|
+
* seq-scans, so a seq scan only *confirms* a finding above the row floor;
|
|
13
|
+
* below it the probe is inconclusive and the finding is left exactly as it
|
|
14
|
+
* was.
|
|
15
|
+
*
|
|
16
|
+
* Probes use `EXPLAIN (GENERIC_PLAN)` — parameters stay parameters, nothing
|
|
17
|
+
* is executed, and no value has to be invented for a column whose domain we
|
|
18
|
+
* know nothing about. That requires PostgreSQL 16+.
|
|
19
|
+
*/
|
|
20
|
+
import type { TableIndexSnapshot } from '../pg/indexes';
|
|
21
|
+
import type { QueryExecutor } from '../pg/introspect';
|
|
22
|
+
import type { Finding } from '../types';
|
|
23
|
+
export interface ExplainOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Below this planner row estimate a sequential scan is the correct plan, so
|
|
26
|
+
* a seq-scan result proves nothing. Default 1000.
|
|
27
|
+
*/
|
|
28
|
+
minRows?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ExplainReport {
|
|
31
|
+
/** Findings that were probed. */
|
|
32
|
+
probed: number;
|
|
33
|
+
/** Probes whose plan showed the scan the finding predicted. */
|
|
34
|
+
confirmed: number;
|
|
35
|
+
/** Probes the planner served with an index — the finding was wrong. */
|
|
36
|
+
refuted: number;
|
|
37
|
+
/** Probes on tables too small for the plan to mean anything. */
|
|
38
|
+
inconclusive: number;
|
|
39
|
+
/** Why probing was skipped entirely, when it was. */
|
|
40
|
+
unavailable?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Plan a probe for every probeable perf finding and annotate it in place.
|
|
44
|
+
* Findings the planner refutes are acknowledged (reported, unscored).
|
|
45
|
+
*/
|
|
46
|
+
export declare function proveFindings(exec: QueryExecutor, findings: Finding[], tables: Map<string, TableIndexSnapshot>, options?: ExplainOptions): Promise<ExplainReport>;
|