@safegres/test 0.0.1 → 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,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigValidationError = void 0;
|
|
4
|
+
exports.applyRulesConfig = applyRulesConfig;
|
|
5
|
+
exports.defaultRuleMap = defaultRuleMap;
|
|
6
|
+
exports.resolveRules = resolveRules;
|
|
7
|
+
exports.ruleCodesForDimension = ruleCodesForDimension;
|
|
8
|
+
exports.matchTablePattern = matchTablePattern;
|
|
9
|
+
exports.rulesForTable = rulesForTable;
|
|
10
|
+
exports.applyRulesToFindings = applyRulesToFindings;
|
|
11
|
+
exports.allAstRulesDisabled = allAstRulesDisabled;
|
|
12
|
+
const registry_1 = require("../rules/registry");
|
|
13
|
+
const types_1 = require("../types");
|
|
14
|
+
class ConfigValidationError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'ConfigValidationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.ConfigValidationError = ConfigValidationError;
|
|
21
|
+
function parseSetting(code, setting) {
|
|
22
|
+
if (setting === 'off') {
|
|
23
|
+
return { enabled: false, severity: registry_1.RULES_BY_CODE.get(code).defaultSeverity };
|
|
24
|
+
}
|
|
25
|
+
if (typeof setting === 'string') {
|
|
26
|
+
assertSeverity(code, setting);
|
|
27
|
+
return { enabled: true, severity: setting };
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(setting) && setting.length >= 1) {
|
|
30
|
+
assertSeverity(code, setting[0]);
|
|
31
|
+
return { enabled: true, severity: setting[0], options: setting[1] };
|
|
32
|
+
}
|
|
33
|
+
throw new ConfigValidationError(`Invalid setting for rule "${code}": expected "off", a severity, or [severity, options].`);
|
|
34
|
+
}
|
|
35
|
+
function assertSeverity(code, sev) {
|
|
36
|
+
if (!(sev in types_1.SEVERITY_ORDER)) {
|
|
37
|
+
throw new ConfigValidationError(`Invalid severity "${sev}" for rule "${code}". Expected one of: ${Object.keys(types_1.SEVERITY_ORDER).join(', ')}, off.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Apply a RulesConfig on top of a base rule map. Wildcard selectors apply
|
|
42
|
+
* first, exact codes second — so `{ 'P*': 'off', P1: 'high' }` disables all
|
|
43
|
+
* P-rules except P1.
|
|
44
|
+
*/
|
|
45
|
+
function applyRulesConfig(base, rulesConfig, strictUnknown = true) {
|
|
46
|
+
const out = new Map(base);
|
|
47
|
+
const entries = Object.entries(rulesConfig);
|
|
48
|
+
const wildcards = entries.filter(([sel]) => sel.endsWith('*'));
|
|
49
|
+
const exacts = entries.filter(([sel]) => !sel.endsWith('*'));
|
|
50
|
+
for (const [selector, setting] of [...wildcards, ...exacts]) {
|
|
51
|
+
if (!selector.endsWith('*') && !(0, registry_1.isKnownRule)(selector)) {
|
|
52
|
+
if (strictUnknown) {
|
|
53
|
+
throw new ConfigValidationError(`Unknown rule code "${selector}". Known rules: ${registry_1.RULES.map((r) => r.code).join(', ')}.`);
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
for (const code of (0, registry_1.expandRuleSelector)(selector)) {
|
|
58
|
+
out.set(code, parseSetting(code, setting));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/** Base rule map: every registered rule enabled at its default severity. */
|
|
64
|
+
function defaultRuleMap() {
|
|
65
|
+
return new Map(registry_1.RULES.map((r) => [r.code, { enabled: true, severity: r.defaultSeverity }]));
|
|
66
|
+
}
|
|
67
|
+
/** Resolve the rule + override portion of a merged SafegresConfig. */
|
|
68
|
+
function resolveRules(config) {
|
|
69
|
+
let rules = defaultRuleMap();
|
|
70
|
+
if (config.rules) {
|
|
71
|
+
rules = applyRulesConfig(rules, config.rules);
|
|
72
|
+
}
|
|
73
|
+
if (config.perf?.rules) {
|
|
74
|
+
assertPerfSelectors(config.perf.rules);
|
|
75
|
+
rules = applyRulesConfig(rules, config.perf.rules);
|
|
76
|
+
}
|
|
77
|
+
const overrides = config.overrides ?? [];
|
|
78
|
+
for (const o of overrides) {
|
|
79
|
+
if (!Array.isArray(o.tables) || o.tables.length === 0) {
|
|
80
|
+
throw new ConfigValidationError('Every "overrides" entry needs a non-empty "tables" array.');
|
|
81
|
+
}
|
|
82
|
+
// Validate override rule settings eagerly.
|
|
83
|
+
applyRulesConfig(rules, o.rules ?? {});
|
|
84
|
+
}
|
|
85
|
+
const publicRead = config.public?.read;
|
|
86
|
+
if (publicRead !== undefined) {
|
|
87
|
+
if (!Array.isArray(publicRead) || publicRead.some((p) => typeof p !== 'string' || p.length === 0)) {
|
|
88
|
+
throw new ConfigValidationError('"public.read" must be an array of non-empty schema.table glob patterns.');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const perfIgnore = config.perf?.ignore;
|
|
92
|
+
if (perfIgnore !== undefined) {
|
|
93
|
+
if (!Array.isArray(perfIgnore) || perfIgnore.some((p) => typeof p !== 'string' || p.length === 0)) {
|
|
94
|
+
throw new ConfigValidationError('"perf.ignore" must be an array of non-empty schema.table glob patterns.');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const ignoredExtensions = config.extensions?.ignore;
|
|
98
|
+
if (ignoredExtensions !== undefined) {
|
|
99
|
+
if (!Array.isArray(ignoredExtensions)
|
|
100
|
+
|| ignoredExtensions.some((e) => typeof e !== 'string' || e.length === 0)) {
|
|
101
|
+
throw new ConfigValidationError('"extensions.ignore" must be an array of non-empty extension names.');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { rules, overrides };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* `perf.rules` may only retune perf-dimension codes — otherwise a security
|
|
108
|
+
* rule would silently change behind the perf flag.
|
|
109
|
+
*/
|
|
110
|
+
function assertPerfSelectors(rules) {
|
|
111
|
+
for (const selector of Object.keys(rules)) {
|
|
112
|
+
const codes = (0, registry_1.expandRuleSelector)(selector);
|
|
113
|
+
if (codes.length === 0) {
|
|
114
|
+
throw new ConfigValidationError(`Unknown rule code "${selector}" in "perf.rules". Perf rules: ${perfRuleCodes().join(', ')}.`);
|
|
115
|
+
}
|
|
116
|
+
const security = codes.filter((code) => (0, registry_1.dimensionOf)(registry_1.RULES_BY_CODE.get(code)) !== 'perf');
|
|
117
|
+
if (security.length > 0) {
|
|
118
|
+
throw new ConfigValidationError(`"perf.rules" may only configure perf-dimension rules; "${selector}" matches security rule(s) `
|
|
119
|
+
+ `${security.join(', ')}. Configure those under the top-level "rules".`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Rule codes belonging to a scoring dimension. */
|
|
124
|
+
function ruleCodesForDimension(dimension) {
|
|
125
|
+
return registry_1.RULES.filter((r) => (0, registry_1.dimensionOf)(r) === dimension).map((r) => r.code);
|
|
126
|
+
}
|
|
127
|
+
function perfRuleCodes() {
|
|
128
|
+
return ruleCodesForDimension('perf');
|
|
129
|
+
}
|
|
130
|
+
/** Simple `*` glob match against a qualified `schema.table` name. */
|
|
131
|
+
function matchTablePattern(pattern, qualified) {
|
|
132
|
+
const re = new RegExp(`^${pattern.split('*').map(escapeRegExp).join('.*')}$`);
|
|
133
|
+
return re.test(qualified);
|
|
134
|
+
}
|
|
135
|
+
function escapeRegExp(s) {
|
|
136
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The effective rule settings for one table: base rules plus any matching
|
|
140
|
+
* override entries (later entries win).
|
|
141
|
+
*/
|
|
142
|
+
function rulesForTable(resolved, schema, table) {
|
|
143
|
+
const qualified = `${schema}.${table}`;
|
|
144
|
+
let rules = resolved.rules;
|
|
145
|
+
for (const o of resolved.overrides) {
|
|
146
|
+
if (o.tables.some((p) => matchTablePattern(p, qualified))) {
|
|
147
|
+
rules = applyRulesConfig(rules, o.rules ?? {});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return rules;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Apply resolved rules to raw findings: drop findings for disabled rules and
|
|
154
|
+
* restamp severities from config (respecting per-table overrides).
|
|
155
|
+
*/
|
|
156
|
+
function applyRulesToFindings(resolved, findings) {
|
|
157
|
+
const out = [];
|
|
158
|
+
for (const f of findings) {
|
|
159
|
+
const rules = f.schema && f.table ? rulesForTable(resolved, f.schema, f.table) : resolved.rules;
|
|
160
|
+
const rule = rules.get(f.code);
|
|
161
|
+
if (!rule) {
|
|
162
|
+
out.push(f); // unknown/unregistered code — pass through untouched
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (!rule.enabled)
|
|
166
|
+
continue;
|
|
167
|
+
out.push(f.severity === rule.severity ? f : { ...f, severity: rule.severity });
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* True when every AST-scoped rule is disabled (lets the audit skip parsing).
|
|
173
|
+
*
|
|
174
|
+
* The policy-aware index rules (X2–X4) are AST-scoped but only run when the
|
|
175
|
+
* perf dimension is on, so they hold parsing open only in that mode.
|
|
176
|
+
*/
|
|
177
|
+
function allAstRulesDisabled(resolved, options = {}) {
|
|
178
|
+
const astCodes = registry_1.RULES
|
|
179
|
+
.filter((r) => r.scope === 'policy-ast')
|
|
180
|
+
.filter((r) => options.perf || r.category !== 'index')
|
|
181
|
+
.map((r) => r.code);
|
|
182
|
+
const base = astCodes.every((code) => resolved.rules.get(code)?.enabled === false);
|
|
183
|
+
if (!base)
|
|
184
|
+
return false;
|
|
185
|
+
// An override could re-enable an AST rule for some table.
|
|
186
|
+
for (const o of resolved.overrides) {
|
|
187
|
+
const merged = applyRulesConfig(resolved.rules, o.rules ?? {});
|
|
188
|
+
if (astCodes.some((code) => merged.get(code)?.enabled))
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SafegresConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The shape of the config file, as data: what an editor completes against
|
|
4
|
+
* (JSON Schema, via `toJsonSchema`) and what a load validates against, from
|
|
5
|
+
* one declaration.
|
|
6
|
+
*
|
|
7
|
+
* Drift is a compile error, not a stale file. `Shape<T>` maps over
|
|
8
|
+
* `Required<T>`, so a key added to `SafegresConfig` that nobody described
|
|
9
|
+
* here fails to typecheck — which is the only reason a hand-written schema
|
|
10
|
+
* for a growing interface is defensible at all.
|
|
11
|
+
*/
|
|
12
|
+
export type Shape<T> = {
|
|
13
|
+
[K in keyof Required<T>]: Node;
|
|
14
|
+
};
|
|
15
|
+
/** One described value. `any` is the honest answer for a live JS object. */
|
|
16
|
+
export type Node = {
|
|
17
|
+
type: 'any';
|
|
18
|
+
description: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'string';
|
|
21
|
+
description: string;
|
|
22
|
+
enum?: readonly string[];
|
|
23
|
+
} | {
|
|
24
|
+
type: 'number';
|
|
25
|
+
description: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'boolean';
|
|
28
|
+
description: string;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'array';
|
|
31
|
+
description: string;
|
|
32
|
+
items: Node;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'object';
|
|
35
|
+
description: string;
|
|
36
|
+
properties: Record<string, Node>;
|
|
37
|
+
}
|
|
38
|
+
/** A map with arbitrary keys — `rules`, `failOn.planes`, … */
|
|
39
|
+
| {
|
|
40
|
+
type: 'record';
|
|
41
|
+
description: string;
|
|
42
|
+
values: Node;
|
|
43
|
+
}
|
|
44
|
+
/** A key whose value may be more than one of the above. */
|
|
45
|
+
| {
|
|
46
|
+
type: 'union';
|
|
47
|
+
description: string;
|
|
48
|
+
of: Node[];
|
|
49
|
+
};
|
|
50
|
+
/** The config file, described. */
|
|
51
|
+
export declare const CONFIG_SHAPE: Shape<SafegresConfig>;
|
|
52
|
+
export declare const SCHEMA_ID = "https://raw.githubusercontent.com/constructive-io/constructive/main/packages/safegres/schema/safegres.schema.json";
|
|
53
|
+
/** The described shape as a draft-07 JSON Schema, for editors. */
|
|
54
|
+
export declare function toJsonSchema(): Record<string, unknown>;
|
package/config/schema.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCHEMA_ID = exports.CONFIG_SHAPE = void 0;
|
|
4
|
+
exports.toJsonSchema = toJsonSchema;
|
|
5
|
+
const str = (description, values) => values ? { type: 'string', description, enum: values } : { type: 'string', description };
|
|
6
|
+
const num = (description) => ({ type: 'number', description });
|
|
7
|
+
const bool = (description) => ({ type: 'boolean', description });
|
|
8
|
+
const list = (description, items) => ({ type: 'array', description, items });
|
|
9
|
+
const obj = (description, properties) => ({
|
|
10
|
+
type: 'object',
|
|
11
|
+
description,
|
|
12
|
+
properties: properties
|
|
13
|
+
});
|
|
14
|
+
const oneOf = (description, ...of) => ({ type: 'union', description, of });
|
|
15
|
+
const GRADES = ['A+', 'A', 'B', 'C', 'D', 'F'];
|
|
16
|
+
const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
|
|
17
|
+
/** `'off' | Severity | [Severity, options]`. */
|
|
18
|
+
const RULE_SETTING = oneOf('off, a severity, or [severity, options]', str('Rule setting', ['off', ...SEVERITIES]), list('Severity plus rule options', { type: 'any', description: 'Severity or options object' }));
|
|
19
|
+
const RULES = {
|
|
20
|
+
type: 'record',
|
|
21
|
+
description: 'Rule settings by code or prefix wildcard (A*, P*, *). Exact codes win.',
|
|
22
|
+
values: RULE_SETTING
|
|
23
|
+
};
|
|
24
|
+
const PLANE = {
|
|
25
|
+
name: str('Plane identifier, e.g. api, direct:app, internal'),
|
|
26
|
+
kind: str('What kind of access path this plane describes', ['api', 'role', 'schema']),
|
|
27
|
+
primary: bool('Make this plane the headline score. At most one plane may claim it.'),
|
|
28
|
+
schemas: list('Schemas this plane reaches', str('Schema name')),
|
|
29
|
+
roles: list('Roles this plane grants access as', str('Role name')),
|
|
30
|
+
anonRoles: list('The subset of roles reachable without authenticating', str('Role name'))
|
|
31
|
+
};
|
|
32
|
+
const EXPOSURE = {
|
|
33
|
+
resolver: str('How to resolve the exposed surface', ['static', 'constructive', 'postgraphile']),
|
|
34
|
+
adapters: list('Exposure adapters: built-in names, or ExposureAdapter objects in a JS/TS config', { type: 'any', description: 'Built-in name or adapter object' }),
|
|
35
|
+
schemas: list('Schemas reachable from the exposed APIs', str('Schema name')),
|
|
36
|
+
roles: list('Roles reachable at the API edge', str('Role name')),
|
|
37
|
+
anonRoles: list('The subset of roles an unauthenticated caller arrives as', str('Role name')),
|
|
38
|
+
name: str('Name of the primary plane. Default api.'),
|
|
39
|
+
reach: bool('Let adapters narrow a plane to the relations the API can address. Default true.'),
|
|
40
|
+
planes: list('Additional access planes to grade', obj('One graded access plane', PLANE))
|
|
41
|
+
};
|
|
42
|
+
const SCORING = {
|
|
43
|
+
model: str('Scoring model. Default density.', ['density', 'weighted']),
|
|
44
|
+
weights: { type: 'record', description: 'Points deducted per finding severity', values: num('Points') },
|
|
45
|
+
perRuleWeights: { type: 'record', description: 'Per-rule weight, beating the severity weight', values: num('Points') },
|
|
46
|
+
maxDeductionPerRule: num('Cap on one rule\u2019s total deduction (weighted model)'),
|
|
47
|
+
maxRuleDensity: oneOf('Cap on one rule\u2019s contribution, as a fraction of the points that fail an audit alone. Default 0.5; false disables the cap.', num('Fraction'), bool('false to disable')),
|
|
48
|
+
failClosedWeight: num('Multiplier for fail-closed findings. Default 0.'),
|
|
49
|
+
densityK: num('Density falloff constant. Default 0.17.'),
|
|
50
|
+
unknownExposureCap: oneOf('Maximum score when no exposure surface resolves. Default 80; false disables the cap.', num('Cap'), bool('false to disable')),
|
|
51
|
+
floorOnCritical: oneOf('Grade any critical finding floors the score at. Default C; false disables.', str('Grade', GRADES), bool('false to disable')),
|
|
52
|
+
gradeBands: { type: 'record', description: 'Minimum score for each grade', values: num('Score') }
|
|
53
|
+
};
|
|
54
|
+
const PERF_SCORING = {
|
|
55
|
+
...SCORING,
|
|
56
|
+
includeStats: bool('Whether S* runtime-statistics findings count toward the perf score. Default true.')
|
|
57
|
+
};
|
|
58
|
+
const PERF_STATS = {
|
|
59
|
+
enabled: bool('Collect runtime statistics without passing --stats'),
|
|
60
|
+
minRows: num('Ignore tables with fewer live rows than this. Default 1000.'),
|
|
61
|
+
seqScanRatio: num('S1 fires above this seq-scan/index-scan ratio. Default 10.'),
|
|
62
|
+
minIndexBytes: num('S2 ignores indexes smaller than this. Default 1048576.'),
|
|
63
|
+
deadTupleRatio: num('S3 fires above this dead/live tuple ratio. Default 0.2.'),
|
|
64
|
+
minTimeShare: num('S4 reports statements at or above this share of total time. Default 0.05.'),
|
|
65
|
+
topStatements: num('S4 reports at most this many statements. Default 5.')
|
|
66
|
+
};
|
|
67
|
+
const PERF_EXPLAIN = {
|
|
68
|
+
enabled: bool('Probe findings with EXPLAIN without passing --explain'),
|
|
69
|
+
minRows: num('Below this planner row estimate a seq scan is right. Default 1000.')
|
|
70
|
+
};
|
|
71
|
+
const PERF_PATHS = {
|
|
72
|
+
infer: bool('Collect access-path signals for every foreign key. Default true.'),
|
|
73
|
+
minPointers: num('Write-once pointers before the config-record signal fires. Default 2.'),
|
|
74
|
+
onWriteOncePointer: str('What X1 does with a shape-only write-once pointer', [
|
|
75
|
+
'report',
|
|
76
|
+
'demote',
|
|
77
|
+
'suppress'
|
|
78
|
+
])
|
|
79
|
+
};
|
|
80
|
+
const PERF = {
|
|
81
|
+
enabled: bool('Collect and score perf findings without passing --perf'),
|
|
82
|
+
rules: RULES,
|
|
83
|
+
ignore: list('Table globs whose perf findings are intentional', str('schema.table glob')),
|
|
84
|
+
scoring: obj('Scoring for the perf axis', PERF_SCORING),
|
|
85
|
+
stats: obj('Runtime statistics (S*)', PERF_STATS),
|
|
86
|
+
explain: obj('Planner proof (--explain)', PERF_EXPLAIN),
|
|
87
|
+
baseline: str('Baseline of accepted perf debt; its presence enables the diff'),
|
|
88
|
+
failOnNew: bool('Exit non-zero on a perf finding absent from the baseline'),
|
|
89
|
+
paths: obj('Access-path classification, which decides whether X1 applies', PERF_PATHS)
|
|
90
|
+
};
|
|
91
|
+
const SCORECARD_SELECT = {
|
|
92
|
+
rules: list('Rule codes or C* prefix wildcards to grade', str('Rule code or wildcard')),
|
|
93
|
+
exclude: list('Rule codes or wildcards to drop after rules selected', str('Rule code or wildcard')),
|
|
94
|
+
dimension: str('Which axis: security (default), perf, or all', ['security', 'perf', 'all']),
|
|
95
|
+
roles: list('Only findings about these roles', str('Role name')),
|
|
96
|
+
planes: list('Only findings reachable on these access planes', str('Plane name')),
|
|
97
|
+
schemas: list('Only findings in these schemas', str('Schema name')),
|
|
98
|
+
direction: str('fail-open leaks, fail-closed denials, or any', ['fail-open', 'fail-closed', 'none', 'any']),
|
|
99
|
+
minSeverity: str('Drop findings below this severity before scoring', SEVERITIES),
|
|
100
|
+
exposure: str('exposed (default) grades the API surface; all ignores exposure', ['exposed', 'all']),
|
|
101
|
+
acknowledged: str('include grades acknowledged findings too. Default skip.', ['skip', 'include']),
|
|
102
|
+
severities: str('configured (default) uses this config\u2019s severities; declared uses each rule\u2019s registry default', ['configured', 'declared']),
|
|
103
|
+
denominator: str('Density denominator: exposed relations (default) or all', ['exposed', 'all'])
|
|
104
|
+
};
|
|
105
|
+
const SCORECARD = {
|
|
106
|
+
...SCORING,
|
|
107
|
+
title: str('Heading for reports. Defaults to the config key.'),
|
|
108
|
+
description: str('What decision this score informs. Rendered with it.'),
|
|
109
|
+
select: obj('Which findings it grades', SCORECARD_SELECT)
|
|
110
|
+
};
|
|
111
|
+
const FAIL_ON_PLANE = {
|
|
112
|
+
score: num('Exit non-zero below this plane score'),
|
|
113
|
+
grade: str('Exit non-zero below this plane grade', GRADES)
|
|
114
|
+
};
|
|
115
|
+
const FAIL_ON = {
|
|
116
|
+
severity: str('Exit non-zero on any finding at or above this severity', SEVERITIES),
|
|
117
|
+
score: num('Exit non-zero below this security score'),
|
|
118
|
+
grade: str('Exit non-zero below this security grade', GRADES),
|
|
119
|
+
perfScore: num('Exit non-zero below this perf score'),
|
|
120
|
+
perfGrade: str('Exit non-zero below this perf grade', GRADES),
|
|
121
|
+
planes: {
|
|
122
|
+
type: 'record',
|
|
123
|
+
description: 'Per-plane gates, keyed by plane name. A floor is the useful shape.',
|
|
124
|
+
values: obj('One plane gate', FAIL_ON_PLANE)
|
|
125
|
+
},
|
|
126
|
+
scorecards: {
|
|
127
|
+
type: 'record',
|
|
128
|
+
description: 'Per-scorecard gates, keyed by scorecard name.',
|
|
129
|
+
values: obj('One scorecard gate', FAIL_ON_PLANE)
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const GITHUB_COMMENT = {
|
|
133
|
+
sticky: bool('Reuse one comment per PR instead of appending. Default true.'),
|
|
134
|
+
sections: list('Sections to include. Default scores, delta, new-findings. `report` adds the '
|
|
135
|
+
+ 'markdown report (`findings` is its old name).', str('Section', ['scores', 'delta', 'new-findings', 'report', 'findings', 'planes'])),
|
|
136
|
+
detail: str('How much of the report the report section carries. Default summary.', [
|
|
137
|
+
'summary',
|
|
138
|
+
'normal',
|
|
139
|
+
'verbose'
|
|
140
|
+
])
|
|
141
|
+
};
|
|
142
|
+
const GITHUB = {
|
|
143
|
+
summary: list('Scores in the job summary, in order (security, perf, planes:<glob>)', str('Score')),
|
|
144
|
+
comment: obj('Sticky PR comment', GITHUB_COMMENT),
|
|
145
|
+
annotations: str('Which findings become annotations. Default gate-failures.', [
|
|
146
|
+
'all',
|
|
147
|
+
'gate-failures',
|
|
148
|
+
'none'
|
|
149
|
+
]),
|
|
150
|
+
detail: str('How much of the report goes in the job summary. Default normal.', [
|
|
151
|
+
'summary',
|
|
152
|
+
'normal',
|
|
153
|
+
'verbose'
|
|
154
|
+
]),
|
|
155
|
+
badges: bool('Render scores as colored shields.io badges. Default true.')
|
|
156
|
+
};
|
|
157
|
+
const REPORT = {
|
|
158
|
+
planes: list('Planes to render, by name or glob', str('Plane name or glob')),
|
|
159
|
+
dimensions: list('Dimensions to render. Default both.', str('Dimension', ['security', 'perf'])),
|
|
160
|
+
github: obj('GitHub Actions output', GITHUB)
|
|
161
|
+
};
|
|
162
|
+
const OVERRIDE = {
|
|
163
|
+
tables: list('schema.table globs this entry applies to', str('schema.table glob')),
|
|
164
|
+
rules: RULES
|
|
165
|
+
};
|
|
166
|
+
const OUTPUTS = {
|
|
167
|
+
dir: str('Write safegres.{json,md,sarif} into this directory'),
|
|
168
|
+
json: str('Path for the JSON report'),
|
|
169
|
+
markdown: str('Path for the Markdown report'),
|
|
170
|
+
sarif: str('Path for the SARIF report'),
|
|
171
|
+
sarifSources: str('Root scanned to resolve SARIF findings to SQL source lines'),
|
|
172
|
+
snapshot: str('Path for the aggregate-only snapshot, for a later compare'),
|
|
173
|
+
githubComment: str('Path for the rendered sticky PR comment')
|
|
174
|
+
};
|
|
175
|
+
const SOURCE = {
|
|
176
|
+
pgpm: str('Deploy the pgpm workspace at this path into an ephemeral database and audit it')
|
|
177
|
+
};
|
|
178
|
+
const CALL_GRAPH = {
|
|
179
|
+
enabled: bool('Build the call-graph audit without passing --call-graph'),
|
|
180
|
+
baseline: str('Baseline of accepted trust boundaries; enables the diff'),
|
|
181
|
+
failOnNew: bool('Exit non-zero on a boundary absent from the baseline')
|
|
182
|
+
};
|
|
183
|
+
const EVAL = {
|
|
184
|
+
corpus: str('Corpus directory of <id>/{case.json,schema.sql}'),
|
|
185
|
+
preset: str('Preset every case is graded under. Default recommended.'),
|
|
186
|
+
cases: list('Case ids (or id prefixes) to run', str('Case id'))
|
|
187
|
+
};
|
|
188
|
+
const PUBLIC = {
|
|
189
|
+
read: list('Table globs whose open SELECT policies are by design', str('schema.table glob'))
|
|
190
|
+
};
|
|
191
|
+
const EXTENSIONS = {
|
|
192
|
+
skipOwned: bool('Skip relations an extension owns. Default true.'),
|
|
193
|
+
ignore: list('Extension names whose schemas are skipped wholesale', str('Extension name'))
|
|
194
|
+
};
|
|
195
|
+
/** The config file, described. */
|
|
196
|
+
exports.CONFIG_SHAPE = {
|
|
197
|
+
extends: oneOf('Presets (safegres:recommended, \u2026), relative file paths, or npm packages', str('Preset name, path, or package'), list('Several, lowest precedence first', str('Preset name, path, or package'))),
|
|
198
|
+
exposure: obj('The exposed API surface \u2014 what the score is computed against', EXPOSURE),
|
|
199
|
+
public: obj('Declared-public surface: open reads that are deliberate', PUBLIC),
|
|
200
|
+
extensions: obj('How to treat objects belonging to installed extensions', EXTENSIONS),
|
|
201
|
+
perf: obj('The optional performance dimension', PERF),
|
|
202
|
+
schemas: list('Only audit these schemas', str('Schema name')),
|
|
203
|
+
excludeSchemas: list('Never audit these schemas', str('Schema name')),
|
|
204
|
+
roles: list('Only audit these roles', str('Role name')),
|
|
205
|
+
excludeRoles: list('Never audit these roles', str('Role name')),
|
|
206
|
+
rules: RULES,
|
|
207
|
+
overrides: list('Per-scope retuning, ESLint overrides-style', obj('One override', OVERRIDE)),
|
|
208
|
+
scoring: obj('The security scoring model', SCORING),
|
|
209
|
+
scorecards: {
|
|
210
|
+
type: 'record',
|
|
211
|
+
description: 'Named scores over slices of the same findings, keyed by name',
|
|
212
|
+
values: obj('One scorecard', SCORECARD)
|
|
213
|
+
},
|
|
214
|
+
failOn: obj('Gates \u2014 what makes the run exit non-zero', FAIL_ON),
|
|
215
|
+
source: obj('Where the database to audit comes from', SOURCE),
|
|
216
|
+
outputs: obj('Files a single run writes', OUTPUTS),
|
|
217
|
+
callGraph: obj('The unscored call-graph audit and its baseline', CALL_GRAPH),
|
|
218
|
+
report: obj('What a rendered report shows \u2014 selection, not analysis', REPORT),
|
|
219
|
+
eval: obj('Defaults for safegres eval', EVAL)
|
|
220
|
+
};
|
|
221
|
+
exports.SCHEMA_ID = 'https://raw.githubusercontent.com/constructive-io/constructive/main/packages/safegres/schema/safegres.schema.json';
|
|
222
|
+
/** The described shape as a draft-07 JSON Schema, for editors. */
|
|
223
|
+
function toJsonSchema() {
|
|
224
|
+
return {
|
|
225
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
226
|
+
$id: exports.SCHEMA_ID,
|
|
227
|
+
title: 'safegres configuration',
|
|
228
|
+
description: 'Configuration for safegres, the PostgreSQL security and performance auditor. '
|
|
229
|
+
+ 'Discovered as safegres.config.{ts,js,mjs,cjs}, .safegresrc{,.json,.yaml,.yml,.js}, '
|
|
230
|
+
+ 'safegres.json, or the "safegres" key in package.json.',
|
|
231
|
+
type: 'object',
|
|
232
|
+
additionalProperties: false,
|
|
233
|
+
properties: {
|
|
234
|
+
$schema: { type: 'string', description: 'JSON Schema reference, for editor completion.' },
|
|
235
|
+
...objectProperties(exports.CONFIG_SHAPE)
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function objectProperties(shape) {
|
|
240
|
+
const out = {};
|
|
241
|
+
for (const [key, node] of Object.entries(shape))
|
|
242
|
+
out[key] = jsonNode(node);
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
function jsonNode(node) {
|
|
246
|
+
const description = node.description;
|
|
247
|
+
switch (node.type) {
|
|
248
|
+
case 'any':
|
|
249
|
+
return { description };
|
|
250
|
+
case 'string':
|
|
251
|
+
return node.enum ? { type: 'string', enum: [...node.enum], description } : { type: 'string', description };
|
|
252
|
+
case 'number':
|
|
253
|
+
case 'boolean':
|
|
254
|
+
return { type: node.type, description };
|
|
255
|
+
case 'array':
|
|
256
|
+
return { type: 'array', items: jsonNode(node.items), description };
|
|
257
|
+
case 'object':
|
|
258
|
+
return {
|
|
259
|
+
type: 'object',
|
|
260
|
+
additionalProperties: false,
|
|
261
|
+
properties: objectProperties(node.properties),
|
|
262
|
+
description
|
|
263
|
+
};
|
|
264
|
+
case 'record':
|
|
265
|
+
return { type: 'object', additionalProperties: jsonNode(node.values), description };
|
|
266
|
+
case 'union':
|
|
267
|
+
return { anyOf: node.of.map(jsonNode), description };
|
|
268
|
+
}
|
|
269
|
+
}
|