@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
package/score/score.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Grade, ScoringConfig } from '../config/types';
|
|
2
|
+
import type { Finding, Severity } from '../types';
|
|
3
|
+
export interface ScoreDeduction {
|
|
4
|
+
code: string;
|
|
5
|
+
count: number;
|
|
6
|
+
points: number;
|
|
7
|
+
/**
|
|
8
|
+
* What the audit would score if this rule were its only problem — the same
|
|
9
|
+
* curve applied to this rule's points alone. Grades the rule rather than
|
|
10
|
+
* the database.
|
|
11
|
+
*/
|
|
12
|
+
score: number;
|
|
13
|
+
grade: Grade;
|
|
14
|
+
/**
|
|
15
|
+
* Score the audit would *gain* by taking this rule to zero. Not
|
|
16
|
+
* interchangeable with `score`: because the curve is exponential a rule's
|
|
17
|
+
* payoff depends on how much other debt exists, so the same fix is worth
|
|
18
|
+
* more once the rest is clean.
|
|
19
|
+
*/
|
|
20
|
+
potential: number;
|
|
21
|
+
/**
|
|
22
|
+
* The rule contributes no points by construction — every finding is
|
|
23
|
+
* `info`-weighted or fail-closed. Reported so a run cannot imply that
|
|
24
|
+
* fixing them would move the score; it cannot.
|
|
25
|
+
*/
|
|
26
|
+
unscored?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The rule's raw points exceeded `maxRuleDensity` and were cut to it. The
|
|
29
|
+
* finding count is unchanged: what is capped is the rule's influence on the
|
|
30
|
+
* score, not the size of the problem it reports.
|
|
31
|
+
*/
|
|
32
|
+
capped?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Distinct units of repair behind `count`, when a rule declares one
|
|
35
|
+
* (`RuleMeta.dedupBy`). Points are charged per unit, so a rule that emits
|
|
36
|
+
* one finding per (relation × function) pair costs what fixing it costs.
|
|
37
|
+
*/
|
|
38
|
+
units?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface Score {
|
|
41
|
+
/** 0-100. */
|
|
42
|
+
value: number;
|
|
43
|
+
grade: Grade;
|
|
44
|
+
/** Scoring model identifier ('density' or 'weighted'). */
|
|
45
|
+
model: string;
|
|
46
|
+
/**
|
|
47
|
+
* Every rule that produced a scorable finding, largest deduction first,
|
|
48
|
+
* with its own score, grade and payoff-to-fix. Rules that contribute no
|
|
49
|
+
* points (info-weighted, fail-closed) are listed last and flagged
|
|
50
|
+
* `unscored` rather than omitted — a report that silently drops them
|
|
51
|
+
* implies that fixing them would help, and it would not.
|
|
52
|
+
*/
|
|
53
|
+
deductions: ScoreDeduction[];
|
|
54
|
+
/** Density model: risk points per exposed table. */
|
|
55
|
+
density?: number;
|
|
56
|
+
/** Density model: the exposed-table denominator. */
|
|
57
|
+
exposedTables?: number;
|
|
58
|
+
/** True when the score was capped because no exposure surface was known. */
|
|
59
|
+
cappedByUnknownExposure?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** Context the audit supplies for exposure-aware scoring. */
|
|
62
|
+
export interface ScoreContext {
|
|
63
|
+
/** Number of tables on the exposed surface (the density denominator). */
|
|
64
|
+
exposedTables?: number;
|
|
65
|
+
/** Whether an exposure surface was configured/resolved. */
|
|
66
|
+
exposureKnown?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The caller has already decided which findings count (a scorecard's
|
|
69
|
+
* selector). Skips the built-in exposure/acknowledgement/meta filtering,
|
|
70
|
+
* which would otherwise silently overrule a selector that asked for
|
|
71
|
+
* unexposed findings.
|
|
72
|
+
*/
|
|
73
|
+
prefiltered?: boolean;
|
|
74
|
+
}
|
|
75
|
+
export declare const DEFAULT_WEIGHTS: Record<Severity, number>;
|
|
76
|
+
export declare const DEFAULT_GRADE_BANDS: Record<Exclude<Grade, 'F'>, number>;
|
|
77
|
+
/**
|
|
78
|
+
* Compute the audit score. The score is config-driven by design: disabling
|
|
79
|
+
* or retuning rules changes the findings and therefore the score, and only
|
|
80
|
+
* findings on the exposed surface count (see `Finding.exposed`).
|
|
81
|
+
*/
|
|
82
|
+
export declare function computeScore(findings: Finding[], config?: ScoringConfig, context?: ScoreContext): Score;
|
|
83
|
+
/** True when `grade` is at least as good as `minimum`. */
|
|
84
|
+
export declare function meetsGrade(grade: Grade, minimum: Grade): boolean;
|
package/score/score.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_GRADE_BANDS = exports.DEFAULT_WEIGHTS = void 0;
|
|
4
|
+
exports.computeScore = computeScore;
|
|
5
|
+
exports.meetsGrade = meetsGrade;
|
|
6
|
+
const registry_1 = require("../rules/registry");
|
|
7
|
+
const types_1 = require("../types");
|
|
8
|
+
exports.DEFAULT_WEIGHTS = {
|
|
9
|
+
critical: 25,
|
|
10
|
+
high: 10,
|
|
11
|
+
medium: 4,
|
|
12
|
+
low: 1,
|
|
13
|
+
info: 0
|
|
14
|
+
};
|
|
15
|
+
exports.DEFAULT_GRADE_BANDS = {
|
|
16
|
+
'A+': 97,
|
|
17
|
+
A: 90,
|
|
18
|
+
B: 80,
|
|
19
|
+
C: 65,
|
|
20
|
+
D: 50
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_MAX_DEDUCTION_PER_RULE = 40;
|
|
23
|
+
const DEFAULT_MAX_RULE_DENSITY = 0.5;
|
|
24
|
+
const DEFAULT_DENSITY_K = 0.17;
|
|
25
|
+
const DEFAULT_UNKNOWN_EXPOSURE_CAP = 80;
|
|
26
|
+
const DEFAULT_FAIL_CLOSED_WEIGHT = 0;
|
|
27
|
+
/**
|
|
28
|
+
* Compute the audit score. The score is config-driven by design: disabling
|
|
29
|
+
* or retuning rules changes the findings and therefore the score, and only
|
|
30
|
+
* findings on the exposed surface count (see `Finding.exposed`).
|
|
31
|
+
*/
|
|
32
|
+
function computeScore(findings, config = {}, context = {}) {
|
|
33
|
+
const model = config.model ?? 'density';
|
|
34
|
+
return model === 'weighted'
|
|
35
|
+
? computeWeightedScore(findings, config, context)
|
|
36
|
+
: computeDensityScore(findings, config, context);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Density scoring: severity-weighted *scorable* findings per exposed table
|
|
40
|
+
* with exponential falloff — does not saturate on large schemas.
|
|
41
|
+
*
|
|
42
|
+
* score = 100 · exp(−k · riskPoints / exposedTables)
|
|
43
|
+
*
|
|
44
|
+
* Scorable findings are those on the exposed surface (`exposed !== false`)
|
|
45
|
+
* that are not fail-closed (fail-closed weights are multiplied by
|
|
46
|
+
* `failClosedWeight`, default 0 — denied-at-runtime hygiene doesn't reduce
|
|
47
|
+
* the score). With the default k, one critical per ~10 exposed tables lands
|
|
48
|
+
* around a C; one critical per table is an F.
|
|
49
|
+
*
|
|
50
|
+
* Two things keep a rule's *shape* out of the arithmetic. Points are charged
|
|
51
|
+
* per unit of repair, not per finding, for rules that declare one — a rule
|
|
52
|
+
* emitting a row per (relation × function) pair costs what fixing it costs.
|
|
53
|
+
* And each rule's total is capped at a fraction of the points that would fail
|
|
54
|
+
* an audit alone (`maxRuleDensity`), so no single rule can decide the grade.
|
|
55
|
+
*/
|
|
56
|
+
function computeDensityScore(findings, config, context) {
|
|
57
|
+
const weights = { ...exports.DEFAULT_WEIGHTS, ...(config.weights ?? {}) };
|
|
58
|
+
const perRule = config.perRuleWeights ?? {};
|
|
59
|
+
const bands = { ...exports.DEFAULT_GRADE_BANDS, ...(config.gradeBands ?? {}) };
|
|
60
|
+
const floor = config.floorOnCritical === undefined ? 'C' : config.floorOnCritical;
|
|
61
|
+
const k = config.densityK ?? DEFAULT_DENSITY_K;
|
|
62
|
+
const failClosedWeight = config.failClosedWeight ?? DEFAULT_FAIL_CLOSED_WEIGHT;
|
|
63
|
+
// Meta findings (e.g. W1) are advisories about the audit itself — the
|
|
64
|
+
// unknown-exposure cap is their penalty, not weighted points.
|
|
65
|
+
const scorable = context.prefiltered
|
|
66
|
+
? findings
|
|
67
|
+
: findings.filter((f) => f.exposed !== false && !f.acknowledged && f.category !== 'meta');
|
|
68
|
+
const exposedTables = Math.max(1, context.exposedTables ?? 1);
|
|
69
|
+
const byRule = new Map();
|
|
70
|
+
for (const f of scorable) {
|
|
71
|
+
let weight = perRule[f.code] ?? weights[f.severity] ?? 0;
|
|
72
|
+
if (f.direction === 'fail-closed')
|
|
73
|
+
weight *= failClosedWeight;
|
|
74
|
+
const entry = byRule.get(f.code) ?? { count: 0, points: 0, units: new Set() };
|
|
75
|
+
entry.count += 1;
|
|
76
|
+
// A rule with a declared unit of repair is charged once per unit: the
|
|
77
|
+
// second finding against the same function is the same fix, and letting
|
|
78
|
+
// it charge again grades fan-out rather than risk.
|
|
79
|
+
const unit = repairUnit(f);
|
|
80
|
+
if (unit === undefined || !entry.units.has(unit)) {
|
|
81
|
+
entry.points += Math.max(0, weight);
|
|
82
|
+
if (unit !== undefined)
|
|
83
|
+
entry.units.add(unit);
|
|
84
|
+
}
|
|
85
|
+
byRule.set(f.code, entry);
|
|
86
|
+
}
|
|
87
|
+
// No single rule may contribute more than `maxRuleDensity` of the points
|
|
88
|
+
// that would take the score to F alone — at the default half, one rule can
|
|
89
|
+
// cost two grade bands but an F still takes breadth. Solved from the curve
|
|
90
|
+
// rather than fixed, so retuning `k` or the bands moves the cap with them.
|
|
91
|
+
const maxRuleDensity = config.maxRuleDensity ?? DEFAULT_MAX_RULE_DENSITY;
|
|
92
|
+
const pointsToF = (Math.log(100 / (bands.D || 50)) / k) * exposedTables;
|
|
93
|
+
const ruleCap = maxRuleDensity === false ? Infinity : maxRuleDensity * pointsToF;
|
|
94
|
+
const capped = new Map();
|
|
95
|
+
for (const [code, tally] of byRule) {
|
|
96
|
+
capped.set(code, {
|
|
97
|
+
count: tally.count,
|
|
98
|
+
points: Math.min(tally.points, ruleCap),
|
|
99
|
+
raw: tally.points,
|
|
100
|
+
...(tally.units.size > 0 ? { units: tally.units.size } : {})
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const riskPoints = [...capped.values()].reduce((sum, r) => sum + r.points, 0);
|
|
104
|
+
const density = riskPoints / exposedTables;
|
|
105
|
+
const curve = (points) => round1(100 * Math.exp((-k * points) / exposedTables));
|
|
106
|
+
let value = round1(100 * Math.exp(-k * density));
|
|
107
|
+
const deductions = [...capped.entries()]
|
|
108
|
+
.map(([code, { count, points, raw, units }]) => ({
|
|
109
|
+
code,
|
|
110
|
+
count,
|
|
111
|
+
points,
|
|
112
|
+
score: curve(points),
|
|
113
|
+
grade: gradeFor(curve(points), bands),
|
|
114
|
+
potential: round1(curve(riskPoints - points) - value),
|
|
115
|
+
...(units !== undefined && units !== count ? { units } : {}),
|
|
116
|
+
...(raw > points ? { capped: true } : {}),
|
|
117
|
+
...(points === 0 ? { unscored: true } : {})
|
|
118
|
+
}))
|
|
119
|
+
.sort(byPayoff);
|
|
120
|
+
const unknownCap = config.unknownExposureCap ?? DEFAULT_UNKNOWN_EXPOSURE_CAP;
|
|
121
|
+
const cappedByUnknown = context.exposureKnown === false && unknownCap !== false && value > unknownCap;
|
|
122
|
+
if (cappedByUnknown)
|
|
123
|
+
value = unknownCap;
|
|
124
|
+
let grade = gradeFor(value, bands);
|
|
125
|
+
if (floor
|
|
126
|
+
&& scorable.some((f) => f.direction !== 'fail-closed' && (0, types_1.meetsThreshold)(f.severity, 'critical'))) {
|
|
127
|
+
grade = worseOf(grade, floor);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
value,
|
|
131
|
+
grade,
|
|
132
|
+
model: 'density',
|
|
133
|
+
deductions,
|
|
134
|
+
density: Math.round(density * 1000) / 1000,
|
|
135
|
+
exposedTables,
|
|
136
|
+
...(cappedByUnknown ? { cappedByUnknownExposure: true } : {})
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The finding's unit of repair, from the rule's declared `dedupBy` path.
|
|
141
|
+
* `undefined` when the rule declares none — one finding, one unit.
|
|
142
|
+
*/
|
|
143
|
+
function repairUnit(finding) {
|
|
144
|
+
const path = registry_1.RULES_BY_CODE.get(finding.code)?.dedupBy;
|
|
145
|
+
if (!path)
|
|
146
|
+
return undefined;
|
|
147
|
+
let value = finding;
|
|
148
|
+
for (const key of path.split('.')) {
|
|
149
|
+
if (value === null || typeof value !== 'object')
|
|
150
|
+
return undefined;
|
|
151
|
+
value = value[key];
|
|
152
|
+
}
|
|
153
|
+
return typeof value === 'string' ? value : undefined;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Legacy weighted-deduction scoring (Lighthouse-style): each finding deducts
|
|
157
|
+
* points by severity (or per-rule override), capped per rule so one noisy
|
|
158
|
+
* rule can't zero the score alone. Any critical finding floors the grade
|
|
159
|
+
* (default 'C'). Saturates on large schemas — prefer the density model.
|
|
160
|
+
*/
|
|
161
|
+
function computeWeightedScore(findings, config, context) {
|
|
162
|
+
const weights = { ...exports.DEFAULT_WEIGHTS, ...(config.weights ?? {}) };
|
|
163
|
+
const perRule = config.perRuleWeights ?? {};
|
|
164
|
+
const cap = config.maxDeductionPerRule ?? DEFAULT_MAX_DEDUCTION_PER_RULE;
|
|
165
|
+
const bands = { ...exports.DEFAULT_GRADE_BANDS, ...(config.gradeBands ?? {}) };
|
|
166
|
+
const floor = config.floorOnCritical === undefined ? 'C' : config.floorOnCritical;
|
|
167
|
+
const scorable = context.prefiltered
|
|
168
|
+
? findings
|
|
169
|
+
: findings.filter((f) => f.exposed !== false && !f.acknowledged && f.category !== 'meta');
|
|
170
|
+
const byRule = new Map();
|
|
171
|
+
for (const f of scorable) {
|
|
172
|
+
const weight = perRule[f.code] ?? weights[f.severity] ?? 0;
|
|
173
|
+
const entry = byRule.get(f.code) ?? { count: 0, points: 0 };
|
|
174
|
+
entry.count += 1;
|
|
175
|
+
entry.points += weight;
|
|
176
|
+
byRule.set(f.code, entry);
|
|
177
|
+
}
|
|
178
|
+
const byRuleCapped = [...byRule.entries()].map(([code, { count, points }]) => [code, { count, points: Math.min(points, cap) }]);
|
|
179
|
+
const total = byRuleCapped.reduce((sum, [, r]) => sum + r.points, 0);
|
|
180
|
+
let value = Math.max(0, round1(100 - total));
|
|
181
|
+
const remainder = (points) => Math.max(0, round1(100 - (total - points)));
|
|
182
|
+
const deductions = byRuleCapped
|
|
183
|
+
.map(([code, { count, points }]) => ({
|
|
184
|
+
code,
|
|
185
|
+
count,
|
|
186
|
+
points,
|
|
187
|
+
score: Math.max(0, round1(100 - points)),
|
|
188
|
+
grade: gradeFor(Math.max(0, round1(100 - points)), bands),
|
|
189
|
+
potential: round1(remainder(points) - value),
|
|
190
|
+
...(points === 0 ? { unscored: true } : {})
|
|
191
|
+
}))
|
|
192
|
+
.sort(byPayoff);
|
|
193
|
+
const unknownCap = config.unknownExposureCap ?? DEFAULT_UNKNOWN_EXPOSURE_CAP;
|
|
194
|
+
const capped = context.exposureKnown === false && unknownCap !== false && value > unknownCap;
|
|
195
|
+
if (capped)
|
|
196
|
+
value = unknownCap;
|
|
197
|
+
let grade = gradeFor(value, bands);
|
|
198
|
+
if (floor && scorable.some((f) => (0, types_1.meetsThreshold)(f.severity, 'critical'))) {
|
|
199
|
+
grade = worseOf(grade, floor);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
value,
|
|
203
|
+
grade,
|
|
204
|
+
model: 'weighted',
|
|
205
|
+
deductions,
|
|
206
|
+
...(capped ? { cappedByUnknownExposure: true } : {})
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function round1(n) {
|
|
210
|
+
return Math.round(n * 10) / 10;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Scored rules first, largest deduction at the top; unscored rules last, in
|
|
214
|
+
* code order. Points and payoff rank identically under a monotone curve, so
|
|
215
|
+
* this keeps the existing "top deductions" output stable.
|
|
216
|
+
*/
|
|
217
|
+
function byPayoff(a, b) {
|
|
218
|
+
if (!!a.unscored !== !!b.unscored)
|
|
219
|
+
return a.unscored ? 1 : -1;
|
|
220
|
+
return b.points - a.points || a.code.localeCompare(b.code);
|
|
221
|
+
}
|
|
222
|
+
const GRADE_ORDER = ['A+', 'A', 'B', 'C', 'D', 'F'];
|
|
223
|
+
function gradeFor(value, bands) {
|
|
224
|
+
for (const grade of GRADE_ORDER) {
|
|
225
|
+
if (grade === 'F')
|
|
226
|
+
break;
|
|
227
|
+
if (value >= bands[grade])
|
|
228
|
+
return grade;
|
|
229
|
+
}
|
|
230
|
+
return 'F';
|
|
231
|
+
}
|
|
232
|
+
function worseOf(a, b) {
|
|
233
|
+
return GRADE_ORDER.indexOf(a) >= GRADE_ORDER.indexOf(b) ? a : b;
|
|
234
|
+
}
|
|
235
|
+
/** True when `grade` is at least as good as `minimum`. */
|
|
236
|
+
function meetsGrade(grade, minimum) {
|
|
237
|
+
return GRADE_ORDER.indexOf(grade) <= GRADE_ORDER.indexOf(minimum);
|
|
238
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ScorecardConfig, ScorecardSelector } from '../config/types';
|
|
2
|
+
import { type Finding, type Summary } from '../types';
|
|
3
|
+
import { type Score, type ScoreContext } from './score';
|
|
4
|
+
/**
|
|
5
|
+
* One named score over a selected slice of the findings.
|
|
6
|
+
*
|
|
7
|
+
* The headline score answers one question well and every other question
|
|
8
|
+
* badly: a platform team gating on what `anonymous` reaches and an
|
|
9
|
+
* application team gating on house style are not arguing about the same
|
|
10
|
+
* number, and forcing them to is how a single number becomes something
|
|
11
|
+
* nobody trusts. A scorecard is that question, written down — a selector
|
|
12
|
+
* over the findings plus the weighting to grade them with.
|
|
13
|
+
*/
|
|
14
|
+
export interface ScorecardReport {
|
|
15
|
+
/** Config key, e.g. `anon-surface`. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Human-readable heading for reports. */
|
|
18
|
+
title?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Why this scorecard exists — what decision it informs. Rendered with the
|
|
21
|
+
* score, because a number whose question is unstated invites the reader to
|
|
22
|
+
* supply their own.
|
|
23
|
+
*/
|
|
24
|
+
description?: string;
|
|
25
|
+
/** The findings it selected, graded by its own weighting. */
|
|
26
|
+
score: Score;
|
|
27
|
+
/** How many findings the selector matched. */
|
|
28
|
+
findings: number;
|
|
29
|
+
/** Severity counts over the selected findings. */
|
|
30
|
+
summary: Summary;
|
|
31
|
+
/** The resolved selector, so a report explains its own arithmetic. */
|
|
32
|
+
select: ScorecardSelector;
|
|
33
|
+
/**
|
|
34
|
+
* Reserved scorecards safegres computes itself: `default` is the headline
|
|
35
|
+
* (`report.score`, verbatim) and `raw` is every finding at its declared
|
|
36
|
+
* severity with no exposure filter. Neither can be redefined into
|
|
37
|
+
* something flattering.
|
|
38
|
+
*/
|
|
39
|
+
reserved?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ScorecardContext extends ScoreContext {
|
|
42
|
+
/** Relations on the exposed surface — the default density denominator. */
|
|
43
|
+
exposedTables?: number;
|
|
44
|
+
/** Every relation in the audited schemas — the `all` denominator. */
|
|
45
|
+
totalTables?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The two scorecards every run carries, whatever the config says.
|
|
49
|
+
*
|
|
50
|
+
* `raw` is the honest one: no exposure filter, no acknowledgements, no
|
|
51
|
+
* preset severity retuning, fail-closed findings weighted like anything
|
|
52
|
+
* else. It is not flattering and it is not meant to be — it is the number
|
|
53
|
+
* that is comparable between two databases, and the one a preset cannot
|
|
54
|
+
* talk down.
|
|
55
|
+
*/
|
|
56
|
+
export declare const RESERVED_SCORECARDS: Record<string, ScorecardConfig>;
|
|
57
|
+
/**
|
|
58
|
+
* Grade each configured scorecard. `default` is not recomputed — it is the
|
|
59
|
+
* headline score passed in, so a scorecard block can never move the number
|
|
60
|
+
* on the badge by redefining what `default` means.
|
|
61
|
+
*/
|
|
62
|
+
export declare function computeScorecards(findings: Finding[], headline: Score, cards: Record<string, ScorecardConfig>, context: ScorecardContext): ScorecardReport[];
|
|
63
|
+
/**
|
|
64
|
+
* The findings a scorecard grades. Every clause is a narrowing filter, so an
|
|
65
|
+
* empty selector selects what the headline does: exposed, non-acknowledged,
|
|
66
|
+
* security findings.
|
|
67
|
+
*/
|
|
68
|
+
export declare function selectFindings(findings: Finding[], select: ScorecardSelector): Finding[];
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RESERVED_SCORECARDS = void 0;
|
|
4
|
+
exports.computeScorecards = computeScorecards;
|
|
5
|
+
exports.selectFindings = selectFindings;
|
|
6
|
+
const registry_1 = require("../rules/registry");
|
|
7
|
+
const types_1 = require("../types");
|
|
8
|
+
const score_1 = require("./score");
|
|
9
|
+
/**
|
|
10
|
+
* The two scorecards every run carries, whatever the config says.
|
|
11
|
+
*
|
|
12
|
+
* `raw` is the honest one: no exposure filter, no acknowledgements, no
|
|
13
|
+
* preset severity retuning, fail-closed findings weighted like anything
|
|
14
|
+
* else. It is not flattering and it is not meant to be — it is the number
|
|
15
|
+
* that is comparable between two databases, and the one a preset cannot
|
|
16
|
+
* talk down.
|
|
17
|
+
*/
|
|
18
|
+
exports.RESERVED_SCORECARDS = {
|
|
19
|
+
default: {
|
|
20
|
+
title: 'Safegres score',
|
|
21
|
+
description: 'The headline: exposed, fail-open findings at their configured severities.'
|
|
22
|
+
},
|
|
23
|
+
raw: {
|
|
24
|
+
title: 'Raw score',
|
|
25
|
+
description: 'Every finding at its declared severity, exposure ignored — comparable across databases.',
|
|
26
|
+
select: {
|
|
27
|
+
severities: 'declared',
|
|
28
|
+
exposure: 'all',
|
|
29
|
+
acknowledged: 'include',
|
|
30
|
+
denominator: 'all'
|
|
31
|
+
},
|
|
32
|
+
failClosedWeight: 1,
|
|
33
|
+
maxRuleDensity: false
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Grade each configured scorecard. `default` is not recomputed — it is the
|
|
38
|
+
* headline score passed in, so a scorecard block can never move the number
|
|
39
|
+
* on the badge by redefining what `default` means.
|
|
40
|
+
*/
|
|
41
|
+
function computeScorecards(findings, headline, cards, context) {
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const [name, card] of Object.entries(cards)) {
|
|
44
|
+
const reserved = name in exports.RESERVED_SCORECARDS;
|
|
45
|
+
const select = { ...(exports.RESERVED_SCORECARDS[name]?.select ?? {}), ...(card.select ?? {}) };
|
|
46
|
+
if (name === 'default') {
|
|
47
|
+
out.push({
|
|
48
|
+
name,
|
|
49
|
+
...titleOf(name, card),
|
|
50
|
+
score: headline,
|
|
51
|
+
findings: findings.filter((f) => f.exposed !== false && !f.acknowledged).length,
|
|
52
|
+
summary: (0, types_1.summarize)(findings.filter((f) => f.exposed !== false && !f.acknowledged)),
|
|
53
|
+
select,
|
|
54
|
+
reserved: true
|
|
55
|
+
});
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const selected = selectFindings(findings, select);
|
|
59
|
+
const denominator = select.denominator === 'all'
|
|
60
|
+
? (context.totalTables ?? context.exposedTables)
|
|
61
|
+
: context.exposedTables;
|
|
62
|
+
out.push({
|
|
63
|
+
name,
|
|
64
|
+
...titleOf(name, card),
|
|
65
|
+
score: (0, score_1.computeScore)(selected, card, {
|
|
66
|
+
...context,
|
|
67
|
+
exposedTables: denominator,
|
|
68
|
+
// The selector has already decided what counts; scoring must not
|
|
69
|
+
// filter again, or `exposure: 'all'` would be silently overruled.
|
|
70
|
+
prefiltered: true
|
|
71
|
+
}),
|
|
72
|
+
findings: selected.length,
|
|
73
|
+
summary: (0, types_1.summarize)(selected),
|
|
74
|
+
select,
|
|
75
|
+
...(reserved ? { reserved: true } : {})
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function titleOf(name, card) {
|
|
81
|
+
const base = exports.RESERVED_SCORECARDS[name] ?? {};
|
|
82
|
+
const title = card.title ?? base.title;
|
|
83
|
+
const description = card.description ?? base.description;
|
|
84
|
+
return { ...(title ? { title } : {}), ...(description ? { description } : {}) };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The findings a scorecard grades. Every clause is a narrowing filter, so an
|
|
88
|
+
* empty selector selects what the headline does: exposed, non-acknowledged,
|
|
89
|
+
* security findings.
|
|
90
|
+
*/
|
|
91
|
+
function selectFindings(findings, select) {
|
|
92
|
+
const dimension = select.dimension ?? 'security';
|
|
93
|
+
const rules = select.rules?.map(matcher);
|
|
94
|
+
const exclude = select.exclude?.map(matcher);
|
|
95
|
+
const roles = select.roles ? new Set(select.roles) : undefined;
|
|
96
|
+
const planes = select.planes ? new Set(select.planes) : undefined;
|
|
97
|
+
const schemas = select.schemas ? new Set(select.schemas) : undefined;
|
|
98
|
+
const severity = select.minSeverity ? SEVERITY_RANK[select.minSeverity] : undefined;
|
|
99
|
+
const selected = findings.filter((f) => {
|
|
100
|
+
if (f.category === 'meta')
|
|
101
|
+
return false;
|
|
102
|
+
if (dimension !== 'all' && (f.dimension ?? 'security') !== dimension)
|
|
103
|
+
return false;
|
|
104
|
+
if (select.exposure !== 'all' && f.exposed === false)
|
|
105
|
+
return false;
|
|
106
|
+
if (select.acknowledged !== 'include' && f.acknowledged)
|
|
107
|
+
return false;
|
|
108
|
+
if (select.direction && select.direction !== 'any' && f.direction !== select.direction) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (rules && !rules.some((m) => m(f.code)))
|
|
112
|
+
return false;
|
|
113
|
+
if (exclude?.some((m) => m(f.code)))
|
|
114
|
+
return false;
|
|
115
|
+
if (schemas && (f.schema === undefined || !schemas.has(f.schema)))
|
|
116
|
+
return false;
|
|
117
|
+
if (roles && !touchesRole(f, roles))
|
|
118
|
+
return false;
|
|
119
|
+
if (planes && !f.planes?.some((p) => planes.has(p)))
|
|
120
|
+
return false;
|
|
121
|
+
if (severity !== undefined && SEVERITY_RANK[f.severity] < severity)
|
|
122
|
+
return false;
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
125
|
+
// Declared severities answer "how bad is this really", independent of what
|
|
126
|
+
// a preset chose to quiet down. Copies, never mutation: the findings in
|
|
127
|
+
// the report keep the severity the config resolved.
|
|
128
|
+
return select.severities === 'declared'
|
|
129
|
+
? selected.map((f) => {
|
|
130
|
+
const declared = registry_1.RULES_BY_CODE.get(f.code)?.defaultSeverity;
|
|
131
|
+
return declared === undefined || declared === f.severity ? f : { ...f, severity: declared };
|
|
132
|
+
})
|
|
133
|
+
: selected;
|
|
134
|
+
}
|
|
135
|
+
const SEVERITY_RANK = {
|
|
136
|
+
info: 0,
|
|
137
|
+
low: 1,
|
|
138
|
+
medium: 2,
|
|
139
|
+
high: 3,
|
|
140
|
+
critical: 4
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* A role clause asks "is this finding about one of these roles" — which the
|
|
144
|
+
* finding answers in one of three places: the graded `role` column, a
|
|
145
|
+
* grantee list, or the reachability context a lattice rule attached.
|
|
146
|
+
*/
|
|
147
|
+
function touchesRole(finding, roles) {
|
|
148
|
+
if (finding.role && roles.has(finding.role))
|
|
149
|
+
return true;
|
|
150
|
+
const context = finding.context;
|
|
151
|
+
if (context?.role && roles.has(context.role))
|
|
152
|
+
return true;
|
|
153
|
+
return Array.isArray(context?.roles)
|
|
154
|
+
&& context.roles.some((r) => typeof r === 'string' && roles.has(r));
|
|
155
|
+
}
|
|
156
|
+
/** Rule code matcher: exact, or a `C*` prefix wildcard. */
|
|
157
|
+
function matcher(pattern) {
|
|
158
|
+
if (!pattern.endsWith('*'))
|
|
159
|
+
return (code) => code === pattern;
|
|
160
|
+
const prefix = pattern.slice(0, -1);
|
|
161
|
+
return (code) => code.startsWith(prefix);
|
|
162
|
+
}
|