@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.
Files changed (483) hide show
  1. package/README.md +825 -412
  2. package/adapters.d.ts +8 -0
  3. package/adapters.js +18 -0
  4. package/ast/helpers.d.ts +16 -0
  5. package/ast/helpers.js +40 -0
  6. package/ast/parse.d.ts +31 -0
  7. package/ast/parse.js +87 -0
  8. package/ast/walk.d.ts +27 -0
  9. package/ast/walk.js +79 -0
  10. package/callgraph/baseline.d.ts +28 -0
  11. package/callgraph/baseline.js +55 -0
  12. package/callgraph/extract.d.ts +93 -0
  13. package/callgraph/extract.js +340 -0
  14. package/callgraph/graph.d.ts +82 -0
  15. package/callgraph/graph.js +276 -0
  16. package/checks/anti-patterns.d.ts +53 -0
  17. package/checks/anti-patterns.js +247 -0
  18. package/checks/column-grants.d.ts +20 -0
  19. package/checks/column-grants.js +74 -0
  20. package/checks/coverage.d.ts +43 -0
  21. package/checks/coverage.js +110 -0
  22. package/checks/definer-function.d.ts +91 -0
  23. package/checks/definer-function.js +458 -0
  24. package/checks/definer-view.d.ts +167 -0
  25. package/checks/definer-view.js +392 -0
  26. package/checks/indexes.d.ts +82 -0
  27. package/checks/indexes.js +263 -0
  28. package/checks/lattice.d.ts +179 -0
  29. package/checks/lattice.js +487 -0
  30. package/checks/object-acls.d.ts +32 -0
  31. package/checks/object-acls.js +129 -0
  32. package/checks/policy-index.d.ts +78 -0
  33. package/checks/policy-index.js +349 -0
  34. package/checks/revocable-grants.d.ts +140 -0
  35. package/checks/revocable-grants.js +377 -0
  36. package/checks/rls-flags.d.ts +25 -0
  37. package/checks/rls-flags.js +82 -0
  38. package/checks/role-reach.d.ts +329 -0
  39. package/checks/role-reach.js +277 -0
  40. package/checks/role-trust.d.ts +45 -0
  41. package/checks/role-trust.js +91 -0
  42. package/checks/set-role.d.ts +21 -0
  43. package/checks/set-role.js +66 -0
  44. package/checks/stats.d.ts +52 -0
  45. package/checks/stats.js +192 -0
  46. package/checks/view-exposure.d.ts +68 -0
  47. package/checks/view-exposure.js +236 -0
  48. package/checks/view-writes.d.ts +88 -0
  49. package/checks/view-writes.js +350 -0
  50. package/ci/baseline.d.ts +104 -0
  51. package/ci/baseline.js +111 -0
  52. package/ci/github.d.ts +47 -0
  53. package/ci/github.js +197 -0
  54. package/cli/audit.d.ts +3 -0
  55. package/cli/audit.js +560 -0
  56. package/cli/baseline.d.ts +3 -0
  57. package/cli/baseline.js +104 -0
  58. package/cli/commands.d.ts +2 -0
  59. package/cli/commands.js +60 -0
  60. package/cli/doctor.d.ts +3 -0
  61. package/cli/doctor.js +72 -0
  62. package/cli/eval.d.ts +3 -0
  63. package/cli/eval.js +153 -0
  64. package/cli/print-config.d.ts +3 -0
  65. package/cli/print-config.js +54 -0
  66. package/cli/shared.d.ts +43 -0
  67. package/cli/shared.js +153 -0
  68. package/cli.d.ts +2 -0
  69. package/cli.js +69 -0
  70. package/commands/audit.d.ts +65 -0
  71. package/commands/audit.js +779 -0
  72. package/commands/doctor.d.ts +19 -0
  73. package/commands/doctor.js +181 -0
  74. package/commands/eval.d.ts +67 -0
  75. package/commands/eval.js +93 -0
  76. package/config/fingerprint.d.ts +40 -0
  77. package/config/fingerprint.js +71 -0
  78. package/config/loader.d.ts +44 -0
  79. package/config/loader.js +210 -0
  80. package/config/presets.d.ts +95 -0
  81. package/config/presets.js +327 -0
  82. package/config/resolve.d.ts +47 -0
  83. package/config/resolve.js +192 -0
  84. package/config/schema.d.ts +54 -0
  85. package/config/schema.js +269 -0
  86. package/config/types.d.ts +562 -0
  87. package/config/validate.d.ts +14 -0
  88. package/config/validate.js +125 -0
  89. package/corpus/README.md +83 -0
  90. package/corpus/bootstrap.sql +14 -0
  91. package/corpus/cases/01-anon-write-grant/case.json +26 -0
  92. package/corpus/cases/01-anon-write-grant/schema.sql +19 -0
  93. package/corpus/cases/02-grant-to-public/case.json +25 -0
  94. package/corpus/cases/02-grant-to-public/schema.sql +18 -0
  95. package/corpus/cases/03-rls-off-reachable-by-anon/case.json +30 -0
  96. package/corpus/cases/03-rls-off-reachable-by-anon/schema.sql +13 -0
  97. package/corpus/cases/04-permissive-write-policy/case.json +30 -0
  98. package/corpus/cases/04-permissive-write-policy/schema.sql +21 -0
  99. package/corpus/cases/05-public-read-policy/case.json +25 -0
  100. package/corpus/cases/05-public-read-policy/schema.sql +16 -0
  101. package/corpus/cases/06-update-without-with-check/case.json +25 -0
  102. package/corpus/cases/06-update-without-with-check/schema.sql +19 -0
  103. package/corpus/cases/07-rls-enabled-no-policies/case.json +29 -0
  104. package/corpus/cases/07-rls-enabled-no-policies/schema.sql +14 -0
  105. package/corpus/cases/08-select-grant-no-policy/case.json +25 -0
  106. package/corpus/cases/08-select-grant-no-policy/schema.sql +20 -0
  107. package/corpus/cases/09-write-grant-no-policy/case.json +24 -0
  108. package/corpus/cases/09-write-grant-no-policy/schema.sql +18 -0
  109. package/corpus/cases/10-rls-not-forced/case.json +24 -0
  110. package/corpus/cases/10-rls-not-forced/schema.sql +18 -0
  111. package/corpus/cases/11-session-user-in-policy/case.json +24 -0
  112. package/corpus/cases/11-session-user-in-policy/schema.sql +18 -0
  113. package/corpus/cases/12-volatile-function-in-policy/case.json +24 -0
  114. package/corpus/cases/12-volatile-function-in-policy/schema.sql +21 -0
  115. package/corpus/cases/13-security-definer-policy-wrapper/case.json +24 -0
  116. package/corpus/cases/13-security-definer-policy-wrapper/schema.sql +21 -0
  117. package/corpus/cases/14-dead-policy/case.json +24 -0
  118. package/corpus/cases/14-dead-policy/schema.sql +18 -0
  119. package/corpus/cases/15-unreachable-grant/case.json +24 -0
  120. package/corpus/cases/15-unreachable-grant/schema.sql +18 -0
  121. package/corpus/cases/16-anon-permissive-write-policy/case.json +34 -0
  122. package/corpus/cases/16-anon-permissive-write-policy/schema.sql +17 -0
  123. package/corpus/cases/17-foreign-key-without-index/case.json +24 -0
  124. package/corpus/cases/17-foreign-key-without-index/schema.sql +18 -0
  125. package/corpus/cases/18-policy-column-unindexed/case.json +24 -0
  126. package/corpus/cases/18-policy-column-unindexed/schema.sql +23 -0
  127. package/corpus/cases/19-policy-column-cast/case.json +24 -0
  128. package/corpus/cases/19-policy-column-cast/schema.sql +19 -0
  129. package/corpus/cases/20-stable-function-per-row/case.json +24 -0
  130. package/corpus/cases/20-stable-function-per-row/schema.sql +25 -0
  131. package/corpus/cases/21-table-without-primary-key/case.json +24 -0
  132. package/corpus/cases/21-table-without-primary-key/schema.sql +14 -0
  133. package/corpus/cases/22-redundant-index/case.json +24 -0
  134. package/corpus/cases/22-redundant-index/schema.sql +16 -0
  135. package/corpus/cases/23-tsvector-without-index/case.json +24 -0
  136. package/corpus/cases/23-tsvector-without-index/schema.sql +13 -0
  137. package/corpus/cases/24-anon-set-role-escalation/case.json +29 -0
  138. package/corpus/cases/24-anon-set-role-escalation/schema.sql +30 -0
  139. package/corpus/cases/25-definer-view-bypass/case.json +31 -0
  140. package/corpus/cases/25-definer-view-bypass/schema.sql +36 -0
  141. package/corpus/cases/26-invoker-view-no-bypass/case.json +31 -0
  142. package/corpus/cases/26-invoker-view-no-bypass/schema.sql +44 -0
  143. package/corpus/cases/27-definer-view-write/case.json +36 -0
  144. package/corpus/cases/27-definer-view-write/schema.sql +40 -0
  145. package/corpus/cases/28-invoker-view-no-write/case.json +31 -0
  146. package/corpus/cases/28-invoker-view-no-write/schema.sql +37 -0
  147. package/corpus/cases/29-rewrite-rule-bypass/case.json +36 -0
  148. package/corpus/cases/29-rewrite-rule-bypass/schema.sql +52 -0
  149. package/corpus/cases/30-instead-nothing-rule/case.json +31 -0
  150. package/corpus/cases/30-instead-nothing-rule/schema.sql +44 -0
  151. package/corpus/cases/31-matview-snapshot/case.json +32 -0
  152. package/corpus/cases/31-matview-snapshot/schema.sql +36 -0
  153. package/corpus/cases/32-matview-no-rls-no-bypass/case.json +30 -0
  154. package/corpus/cases/32-matview-no-rls-no-bypass/schema.sql +18 -0
  155. package/corpus/cases/33-leaky-filter-view/case.json +36 -0
  156. package/corpus/cases/33-leaky-filter-view/schema.sql +29 -0
  157. package/corpus/cases/34-barrier-view-no-leak/case.json +32 -0
  158. package/corpus/cases/34-barrier-view-no-leak/schema.sql +29 -0
  159. package/corpus/cases/35-column-grant-projection/case.json +27 -0
  160. package/corpus/cases/35-column-grant-projection/schema.sql +21 -0
  161. package/corpus/cases/35-policy-function-not-leakproof/case.json +24 -0
  162. package/corpus/cases/35-policy-function-not-leakproof/schema.sql +27 -0
  163. package/corpus/cases/36-column-grant-rls-mediated/case.json +27 -0
  164. package/corpus/cases/36-column-grant-rls-mediated/schema.sql +32 -0
  165. package/corpus/cases/36-policy-function-leakproof/case.json +24 -0
  166. package/corpus/cases/36-policy-function-leakproof/schema.sql +22 -0
  167. package/corpus/cases/37-sort-column-unindexed/case.json +24 -0
  168. package/corpus/cases/37-sort-column-unindexed/schema.sql +14 -0
  169. package/corpus/cases/37-unaudited-base-relation/case.json +30 -0
  170. package/corpus/cases/37-unaudited-base-relation/schema.sql +45 -0
  171. package/corpus/cases/38-foreign-key-with-index/case.json +22 -0
  172. package/corpus/cases/38-foreign-key-with-index/schema.sql +17 -0
  173. package/corpus/cases/38-invoker-view-unaudited-base/case.json +30 -0
  174. package/corpus/cases/38-invoker-view-unaudited-base/schema.sql +39 -0
  175. package/corpus/cases/39-definer-view-column-subset/case.json +30 -0
  176. package/corpus/cases/39-definer-view-column-subset/schema.sql +29 -0
  177. package/corpus/cases/39-policy-cast-with-expression-index/case.json +23 -0
  178. package/corpus/cases/39-policy-cast-with-expression-index/schema.sql +19 -0
  179. package/corpus/cases/40-definer-view-column-overreach/case.json +27 -0
  180. package/corpus/cases/40-definer-view-column-overreach/schema.sql +29 -0
  181. package/corpus/cases/40-stable-function-hoisted/case.json +23 -0
  182. package/corpus/cases/40-stable-function-hoisted/schema.sql +25 -0
  183. package/corpus/cases/41-index-prefix-not-redundant/case.json +21 -0
  184. package/corpus/cases/41-index-prefix-not-redundant/schema.sql +15 -0
  185. package/corpus/cases/41-unreadable-view-body/case.json +33 -0
  186. package/corpus/cases/41-unreadable-view-body/schema.sql +28 -0
  187. package/corpus/cases/42-invoker-view-unreadable-body/case.json +30 -0
  188. package/corpus/cases/42-invoker-view-unreadable-body/schema.sql +27 -0
  189. package/corpus/cases/42-search-column-indexed/case.json +21 -0
  190. package/corpus/cases/42-search-column-indexed/schema.sql +12 -0
  191. package/corpus/cases/43-anon-sequence-grant/case.json +27 -0
  192. package/corpus/cases/43-anon-sequence-grant/schema.sql +23 -0
  193. package/corpus/cases/44-serial-sequence-usage/case.json +27 -0
  194. package/corpus/cases/44-serial-sequence-usage/schema.sql +25 -0
  195. package/corpus/cases/45-anon-foreign-table/case.json +27 -0
  196. package/corpus/cases/45-anon-foreign-table/schema.sql +23 -0
  197. package/corpus/cases/46-unchecked-writable-view/case.json +41 -0
  198. package/corpus/cases/46-unchecked-writable-view/schema.sql +37 -0
  199. package/corpus/cases/47-checked-writable-view/case.json +37 -0
  200. package/corpus/cases/47-checked-writable-view/schema.sql +38 -0
  201. package/corpus/cases/48-definer-function-reach/case.json +38 -0
  202. package/corpus/cases/48-definer-function-reach/schema.sql +41 -0
  203. package/corpus/cases/49-invoker-function-no-reach/case.json +32 -0
  204. package/corpus/cases/49-invoker-function-no-reach/schema.sql +38 -0
  205. package/corpus/cases/50-instead-of-trigger-write/case.json +38 -0
  206. package/corpus/cases/50-instead-of-trigger-write/schema.sql +58 -0
  207. package/corpus/cases/51-invoker-trigger-no-write/case.json +33 -0
  208. package/corpus/cases/51-invoker-trigger-no-write/schema.sql +54 -0
  209. package/corpus/cases/52-revocable-execute-grant/case.json +30 -0
  210. package/corpus/cases/52-revocable-execute-grant/schema.sql +45 -0
  211. package/corpus/cases/53-policy-predicate-retains-execute/case.json +23 -0
  212. package/corpus/cases/53-policy-predicate-retains-execute/schema.sql +49 -0
  213. package/corpus/index.d.ts +79 -0
  214. package/corpus/index.js +125 -0
  215. package/esm/adapters.d.ts +8 -0
  216. package/esm/adapters.js +6 -0
  217. package/esm/ast/helpers.d.ts +16 -0
  218. package/esm/ast/helpers.js +36 -0
  219. package/esm/ast/parse.d.ts +31 -0
  220. package/esm/ast/parse.js +82 -0
  221. package/esm/ast/walk.d.ts +27 -0
  222. package/esm/ast/walk.js +72 -0
  223. package/esm/callgraph/baseline.d.ts +28 -0
  224. package/esm/callgraph/baseline.js +48 -0
  225. package/esm/callgraph/extract.d.ts +93 -0
  226. package/esm/callgraph/extract.js +333 -0
  227. package/esm/callgraph/graph.d.ts +82 -0
  228. package/esm/callgraph/graph.js +273 -0
  229. package/esm/checks/anti-patterns.d.ts +53 -0
  230. package/esm/checks/anti-patterns.js +240 -0
  231. package/esm/checks/column-grants.d.ts +20 -0
  232. package/esm/checks/column-grants.js +71 -0
  233. package/esm/checks/coverage.d.ts +43 -0
  234. package/esm/checks/coverage.js +104 -0
  235. package/esm/checks/definer-function.d.ts +91 -0
  236. package/esm/checks/definer-function.js +452 -0
  237. package/esm/checks/definer-view.d.ts +167 -0
  238. package/esm/checks/definer-view.js +382 -0
  239. package/esm/checks/indexes.d.ts +82 -0
  240. package/esm/checks/indexes.js +256 -0
  241. package/esm/checks/lattice.d.ts +179 -0
  242. package/esm/checks/lattice.js +475 -0
  243. package/esm/checks/object-acls.d.ts +32 -0
  244. package/esm/checks/object-acls.js +125 -0
  245. package/esm/checks/policy-index.d.ts +78 -0
  246. package/esm/checks/policy-index.js +342 -0
  247. package/esm/checks/revocable-grants.d.ts +140 -0
  248. package/esm/checks/revocable-grants.js +373 -0
  249. package/esm/checks/rls-flags.d.ts +25 -0
  250. package/esm/checks/rls-flags.js +77 -0
  251. package/esm/checks/role-reach.d.ts +329 -0
  252. package/esm/checks/role-reach.js +270 -0
  253. package/esm/checks/role-trust.d.ts +45 -0
  254. package/esm/checks/role-trust.js +86 -0
  255. package/esm/checks/set-role.d.ts +21 -0
  256. package/esm/checks/set-role.js +63 -0
  257. package/esm/checks/stats.d.ts +52 -0
  258. package/esm/checks/stats.js +184 -0
  259. package/esm/checks/view-exposure.d.ts +68 -0
  260. package/esm/checks/view-exposure.js +231 -0
  261. package/esm/checks/view-writes.d.ts +88 -0
  262. package/esm/checks/view-writes.js +344 -0
  263. package/esm/ci/baseline.d.ts +104 -0
  264. package/esm/ci/baseline.js +107 -0
  265. package/esm/ci/github.d.ts +47 -0
  266. package/esm/ci/github.js +161 -0
  267. package/esm/cli/audit.d.ts +3 -0
  268. package/esm/cli/audit.js +525 -0
  269. package/esm/cli/baseline.d.ts +3 -0
  270. package/esm/cli/baseline.js +102 -0
  271. package/esm/cli/commands.d.ts +2 -0
  272. package/esm/cli/commands.js +53 -0
  273. package/esm/cli/doctor.d.ts +3 -0
  274. package/esm/cli/doctor.js +67 -0
  275. package/esm/cli/eval.d.ts +3 -0
  276. package/esm/cli/eval.js +115 -0
  277. package/esm/cli/print-config.d.ts +3 -0
  278. package/esm/cli/print-config.js +52 -0
  279. package/esm/cli/shared.d.ts +43 -0
  280. package/esm/cli/shared.js +112 -0
  281. package/esm/cli.d.ts +2 -0
  282. package/esm/cli.js +67 -0
  283. package/esm/commands/audit.d.ts +65 -0
  284. package/esm/commands/audit.js +776 -0
  285. package/esm/commands/doctor.d.ts +19 -0
  286. package/esm/commands/doctor.js +178 -0
  287. package/esm/commands/eval.d.ts +67 -0
  288. package/esm/commands/eval.js +90 -0
  289. package/esm/config/fingerprint.d.ts +40 -0
  290. package/esm/config/fingerprint.js +68 -0
  291. package/esm/config/loader.d.ts +44 -0
  292. package/esm/config/loader.js +172 -0
  293. package/esm/config/presets.d.ts +95 -0
  294. package/esm/config/presets.js +324 -0
  295. package/esm/config/resolve.d.ts +47 -0
  296. package/esm/config/resolve.js +180 -0
  297. package/esm/config/schema.d.ts +54 -0
  298. package/esm/config/schema.js +265 -0
  299. package/esm/config/types.d.ts +562 -0
  300. package/esm/config/validate.d.ts +14 -0
  301. package/esm/config/validate.js +122 -0
  302. package/esm/corpus/index.d.ts +79 -0
  303. package/esm/corpus/index.js +85 -0
  304. package/esm/exposure/adapters.d.ts +152 -0
  305. package/esm/exposure/adapters.js +463 -0
  306. package/esm/exposure/planes.d.ts +59 -0
  307. package/esm/exposure/planes.js +144 -0
  308. package/esm/exposure/reach.d.ts +78 -0
  309. package/esm/exposure/reach.js +105 -0
  310. package/esm/exposure/routines.d.ts +36 -0
  311. package/esm/exposure/routines.js +54 -0
  312. package/esm/index.d.ts +96 -0
  313. package/esm/index.js +57 -6
  314. package/esm/lint/index.d.ts +11 -0
  315. package/esm/lint/index.js +10 -0
  316. package/esm/perf/baseline.d.ts +40 -0
  317. package/esm/perf/baseline.js +83 -0
  318. package/esm/perf/explain.d.ts +46 -0
  319. package/esm/perf/explain.js +201 -0
  320. package/esm/pg/acl.d.ts +72 -0
  321. package/esm/pg/acl.js +186 -0
  322. package/esm/pg/behaviors.d.ts +114 -0
  323. package/esm/pg/behaviors.js +206 -0
  324. package/esm/pg/exposure.d.ts +74 -0
  325. package/esm/pg/exposure.js +198 -0
  326. package/esm/pg/functions.d.ts +52 -0
  327. package/esm/pg/functions.js +128 -0
  328. package/esm/pg/indexes.d.ts +192 -0
  329. package/esm/pg/indexes.js +395 -0
  330. package/esm/pg/introspect.d.ts +134 -0
  331. package/esm/pg/introspect.js +274 -0
  332. package/esm/pg/objects.d.ts +37 -0
  333. package/esm/pg/objects.js +108 -0
  334. package/esm/pg/paths.d.ts +114 -0
  335. package/esm/pg/paths.js +159 -0
  336. package/esm/pg/proc.d.ts +23 -0
  337. package/esm/pg/proc.js +55 -0
  338. package/esm/pg/reach-inputs.d.ts +47 -0
  339. package/esm/pg/reach-inputs.js +133 -0
  340. package/esm/pg/roles.d.ts +28 -0
  341. package/esm/pg/roles.js +50 -0
  342. package/esm/pg/stats.d.ts +65 -0
  343. package/esm/pg/stats.js +129 -0
  344. package/esm/pgpm-test.d.ts +22 -0
  345. package/esm/pgpm-test.js +39 -0
  346. package/esm/report/callgraph.d.ts +9 -0
  347. package/esm/report/callgraph.js +74 -0
  348. package/esm/report/compare.d.ts +143 -0
  349. package/esm/report/compare.js +195 -0
  350. package/esm/report/github.d.ts +74 -0
  351. package/esm/report/github.js +290 -0
  352. package/esm/report/json.d.ts +4 -0
  353. package/esm/report/json.js +3 -0
  354. package/esm/report/markdown.d.ts +25 -0
  355. package/esm/report/markdown.js +352 -0
  356. package/esm/report/pretty.d.ts +14 -0
  357. package/esm/report/pretty.js +318 -0
  358. package/esm/report/sarif.d.ts +40 -0
  359. package/esm/report/sarif.js +189 -0
  360. package/esm/report/view.d.ts +82 -0
  361. package/esm/report/view.js +126 -0
  362. package/esm/rules/registry.d.ts +66 -0
  363. package/esm/rules/registry.js +562 -0
  364. package/esm/score/score.d.ts +84 -0
  365. package/esm/score/score.js +233 -0
  366. package/esm/score/scorecards.d.ts +68 -0
  367. package/esm/score/scorecards.js +157 -0
  368. package/esm/types.d.ts +292 -0
  369. package/esm/types.js +26 -0
  370. package/esm/version.d.ts +8 -0
  371. package/esm/version.js +9 -0
  372. package/exposure/adapters.d.ts +152 -0
  373. package/exposure/adapters.js +468 -0
  374. package/exposure/planes.d.ts +59 -0
  375. package/exposure/planes.js +151 -0
  376. package/exposure/reach.d.ts +78 -0
  377. package/exposure/reach.js +109 -0
  378. package/exposure/routines.d.ts +36 -0
  379. package/exposure/routines.js +57 -0
  380. package/index.d.ts +96 -6
  381. package/index.js +229 -6
  382. package/lint/index.d.ts +11 -0
  383. package/lint/index.js +19 -0
  384. package/package.json +54 -48
  385. package/perf/baseline.d.ts +40 -0
  386. package/perf/baseline.js +92 -0
  387. package/perf/explain.d.ts +46 -0
  388. package/perf/explain.js +204 -0
  389. package/pg/acl.d.ts +72 -0
  390. package/pg/acl.js +191 -0
  391. package/pg/behaviors.d.ts +114 -0
  392. package/pg/behaviors.js +217 -0
  393. package/pg/exposure.d.ts +74 -0
  394. package/pg/exposure.js +205 -0
  395. package/pg/functions.d.ts +52 -0
  396. package/pg/functions.js +131 -0
  397. package/pg/indexes.d.ts +192 -0
  398. package/pg/indexes.js +400 -0
  399. package/pg/introspect.d.ts +134 -0
  400. package/pg/introspect.js +280 -0
  401. package/pg/objects.d.ts +37 -0
  402. package/pg/objects.js +111 -0
  403. package/pg/paths.d.ts +114 -0
  404. package/pg/paths.js +166 -0
  405. package/pg/proc.d.ts +23 -0
  406. package/pg/proc.js +58 -0
  407. package/pg/reach-inputs.d.ts +47 -0
  408. package/pg/reach-inputs.js +136 -0
  409. package/pg/roles.d.ts +28 -0
  410. package/pg/roles.js +54 -0
  411. package/pg/stats.d.ts +65 -0
  412. package/pg/stats.js +133 -0
  413. package/pgpm-test.d.ts +22 -0
  414. package/pgpm-test.js +42 -0
  415. package/report/callgraph.d.ts +9 -0
  416. package/report/callgraph.js +81 -0
  417. package/report/compare.d.ts +143 -0
  418. package/report/compare.js +205 -0
  419. package/report/github.d.ts +74 -0
  420. package/report/github.js +333 -0
  421. package/report/json.d.ts +4 -0
  422. package/report/json.js +6 -0
  423. package/report/markdown.d.ts +25 -0
  424. package/report/markdown.js +355 -0
  425. package/report/pretty.d.ts +14 -0
  426. package/report/pretty.js +324 -0
  427. package/report/sarif.d.ts +40 -0
  428. package/report/sarif.js +193 -0
  429. package/report/view.d.ts +82 -0
  430. package/report/view.js +132 -0
  431. package/rules/registry.d.ts +66 -0
  432. package/rules/registry.js +569 -0
  433. package/schema/safegres.schema.json +1119 -0
  434. package/score/score.d.ts +84 -0
  435. package/score/score.js +238 -0
  436. package/score/scorecards.d.ts +68 -0
  437. package/score/scorecards.js +162 -0
  438. package/types.d.ts +292 -0
  439. package/types.js +32 -0
  440. package/version.d.ts +8 -0
  441. package/version.js +12 -0
  442. package/LICENSE +0 -23
  443. package/admin.d.ts +0 -26
  444. package/admin.js +0 -182
  445. package/connect.d.ts +0 -19
  446. package/connect.js +0 -83
  447. package/dist/README.md +0 -557
  448. package/dist/package.json +0 -72
  449. package/esm/admin.js +0 -178
  450. package/esm/connect.js +0 -78
  451. package/esm/manager.js +0 -136
  452. package/esm/roles.js +0 -32
  453. package/esm/seed/adapters.js +0 -23
  454. package/esm/seed/csv.js +0 -44
  455. package/esm/seed/index.js +0 -16
  456. package/esm/seed/json.js +0 -18
  457. package/esm/seed/launchql.js +0 -19
  458. package/esm/seed/sqitch.js +0 -17
  459. package/esm/stream.js +0 -43
  460. package/esm/test-client.js +0 -150
  461. package/manager.d.ts +0 -25
  462. package/manager.js +0 -140
  463. package/roles.d.ts +0 -17
  464. package/roles.js +0 -38
  465. package/seed/adapters.d.ts +0 -4
  466. package/seed/adapters.js +0 -28
  467. package/seed/csv.d.ts +0 -9
  468. package/seed/csv.js +0 -49
  469. package/seed/index.d.ts +0 -16
  470. package/seed/index.js +0 -33
  471. package/seed/json.d.ts +0 -6
  472. package/seed/json.js +0 -21
  473. package/seed/launchql.d.ts +0 -2
  474. package/seed/launchql.js +0 -22
  475. package/seed/sqitch.d.ts +0 -2
  476. package/seed/sqitch.js +0 -20
  477. package/seed/types.d.ts +0 -13
  478. package/stream.d.ts +0 -2
  479. package/stream.js +0 -46
  480. package/test-client.d.ts +0 -49
  481. package/test-client.js +0 -154
  482. /package/{seed → config}/types.js +0 -0
  483. /package/esm/{seed → config}/types.js +0 -0
@@ -0,0 +1,95 @@
1
+ import type { SafegresConfig } from './types';
2
+ /**
3
+ * Built-in presets, resolvable via `extends: 'safegres:<name>'`.
4
+ * A preset is just a partial config; users can publish their own as files
5
+ * or npm packages.
6
+ */
7
+ /**
8
+ * Today's default behavior: every rule at its registry default severity.
9
+ *
10
+ * The untrusted-role rules take whichever roles the surface says are reachable
11
+ * without credentials, so a declared `exposure.anonRoles` (or an adapter that
12
+ * resolves one) is enough to switch them on. With no such surface `anonRoles`
13
+ * is empty and they stay inert, exactly as before.
14
+ */
15
+ export declare const recommended: SafegresConfig;
16
+ /**
17
+ * Everything on and escalated. Fail-closed hygiene findings (dead grants,
18
+ * locked tables) are re-tuned upward and even contribute a fraction of
19
+ * their weight to the score.
20
+ */
21
+ export declare const strict: SafegresConfig;
22
+ /**
23
+ * Tuned for Constructive's architecture:
24
+ * - the exposure surface auto-resolves from the routing plane
25
+ * (`routing_public.apis` → `api_schemas` → `metaschema_public.schema`),
26
+ * so only what the exposed APIs can reach drives the score;
27
+ * - untrusted-role rules watch `anonymous`; anything that can leak rows
28
+ * across the role boundary is critical;
29
+ * - A3 is demoted to `info` rather than switched off — API roles never own
30
+ * tables in the Constructive model, so non-FORCEd RLS is not an exposure
31
+ * here, but the finding stays visible (info carries zero weight, so the
32
+ * score is identical to switching it off) and re-tunable;
33
+ * - `pg_partman`'s schema is skipped: it creates child partitions and
34
+ * templates at runtime with no dependency on the extension, so ownership
35
+ * alone leaves them looking like unsecured application tables.
36
+ */
37
+ export declare const constructive: SafegresConfig;
38
+ /** Structural flags only — a fast CI smoke check. */
39
+ export declare const minimal: SafegresConfig;
40
+ /**
41
+ * PostgREST: the exposed schemas come from `pgrst.db_schemas` in the catalog,
42
+ * and `anon` is the role an unauthenticated request runs as — so anything it
43
+ * can write, or any policy that lets it through, is the whole threat model.
44
+ *
45
+ * The connecting role (`authenticator`) is graded as a secondary plane rather
46
+ * than folded into the headline: it can `SET ROLE`, so its own grants are a
47
+ * separate question from what the API serves.
48
+ */
49
+ export declare const postgrest: SafegresConfig;
50
+ /**
51
+ * Supabase is PostgREST with a fixed role vocabulary: `anon` (unauthenticated)
52
+ * and `authenticated` (any signed-up user — a much weaker boundary than it
53
+ * reads, since anyone can sign up) are both untrusted; `service_role` bypasses
54
+ * RLS by design, so its plane reports as skipped rather than F.
55
+ *
56
+ * `auth`, `storage`, `realtime`, `vault` and friends are Supabase's own
57
+ * schemas: they ship with their own policies and are not the developer's to
58
+ * fix, so they are scoped out of the score while staying in the report.
59
+ */
60
+ export declare const supabase: SafegresConfig;
61
+ /**
62
+ * PostGraphile / graphile-starter: `app_public` is served, `app_hidden` is
63
+ * reachable through it, `app_private` is not exposed.
64
+ *
65
+ * The request role is `<app>_visitor` — project-specific, so it cannot be
66
+ * named here. The adapter resolves it from role membership and the rules take
67
+ * it from there. Every request runs as that role, authenticated or not (the
68
+ * caller is a JWT claim, not a role), which is exactly what makes it
69
+ * untrusted.
70
+ *
71
+ * A3 stays at its default: in this layout the API role is *not* the table
72
+ * owner, so a missing FORCE genuinely is an owner-bypass hole.
73
+ */
74
+ export declare const graphile: SafegresConfig;
75
+ /** Hasura: the surface is whatever is *tracked* in `hdb_catalog`. */
76
+ export declare const hasura: SafegresConfig;
77
+ /**
78
+ * Tenancy posture, independent of stack: in a shared-table multi-tenant
79
+ * database every RLS gap is a cross-tenant read, not a self-service leak, so
80
+ * the row-visibility rules are escalated and a single critical caps the grade
81
+ * at D. Owner-bypass hygiene is *not* escalated — it is a deployment property,
82
+ * and drowning the report in it hides the boundary findings.
83
+ *
84
+ * Compose it: `"extends": ["safegres:supabase", "safegres:multi-tenant"]`.
85
+ */
86
+ export declare const multiTenant: SafegresConfig;
87
+ /**
88
+ * Perf posture for a read-heavy OLTP database, where a sequential scan behind
89
+ * an RLS policy is a production incident rather than a slow report. Security
90
+ * stays at whatever the composed preset says; this only retunes the P/X axis
91
+ * and gates on the perf grade, which is only meaningful once a baseline
92
+ * exists — see `--write-baseline`.
93
+ */
94
+ export declare const oltp: SafegresConfig;
95
+ export declare const PRESETS: Record<string, SafegresConfig>;
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Built-in presets, resolvable via `extends: 'safegres:<name>'`.
3
+ * A preset is just a partial config; users can publish their own as files
4
+ * or npm packages.
5
+ */
6
+ /**
7
+ * Today's default behavior: every rule at its registry default severity.
8
+ *
9
+ * The untrusted-role rules take whichever roles the surface says are reachable
10
+ * without credentials, so a declared `exposure.anonRoles` (or an adapter that
11
+ * resolves one) is enough to switch them on. With no such surface `anonRoles`
12
+ * is empty and they stay inert, exactly as before.
13
+ */
14
+ export const recommended = {
15
+ rules: {
16
+ // The convention linter (`C*`) enforces Constructive house style rather
17
+ // than a universal security fact, so the big-tent preset leaves it off;
18
+ // the `constructive` preset turns it on.
19
+ 'C*': 'off',
20
+ R1: ['critical', { rolesFrom: 'anon' }],
21
+ R2: ['high', { rolesFrom: 'anon' }],
22
+ L5: ['info', { rolesFrom: 'anon' }],
23
+ // Signal-only for now: an unauthenticated role that can SET ROLE to a role
24
+ // with more reach is a real escalation, but the rule is new and unproven,
25
+ // so it reports at zero weight until validated. See the registry entry.
26
+ L7: ['info', { rolesFrom: 'anon' }],
27
+ // Same posture, same reason: a view that executes as its owner hands its
28
+ // readers that owner's reach, which is a real bypass, but the rule is new
29
+ // and body-derived, so it reports at zero weight until validated.
30
+ L8: ['info', { rolesFrom: 'anon' }],
31
+ // The write half of the same story: an auto-updatable definer view, and a
32
+ // rewrite rule whose action runs as the view owner. Same posture again —
33
+ // new, body-derived, zero weight until validated.
34
+ L9: ['info', { rolesFrom: 'anon' }],
35
+ L10: ['info', { rolesFrom: 'anon' }],
36
+ // The other two ways a readable view is not what its definition says: a
37
+ // materialized view serves rows RLS never filtered, and a filtering view
38
+ // without `security_barrier` is not a boundary. Same posture again.
39
+ L11: ['info', { rolesFrom: 'anon' }],
40
+ L12: ['info', { rolesFrom: 'anon' }],
41
+ // Reach that only `pg_attribute.attacl` shows. New, so zero weight — but
42
+ // note this one is catalog-derived, not body-derived: what was missing was
43
+ // the introspection, not the proof.
44
+ L13: ['info', { rolesFrom: 'anon' }],
45
+ // The coverage half of L8: a body reference the audit could not follow
46
+ // because the schema was out of scope. Reports an unknown, never a leak.
47
+ L14: ['info', { rolesFrom: 'anon' }],
48
+ // And the coverage half of the body itself: a view an untrusted role reads
49
+ // whose definition this analysis could not follow. Also an unknown.
50
+ L15: ['info', { rolesFrom: 'anon' }],
51
+ // Privileges on the objects that are not tables. Neither is row-filterable
52
+ // — RLS does not apply to a sequence and cannot be enabled on a foreign
53
+ // table at all — so both are reach the table rules never saw.
54
+ L16: ['info', { rolesFrom: 'anon' }],
55
+ L17: ['info', { rolesFrom: 'anon' }],
56
+ // The write-side twin of L12: a writable view whose `WHERE` is a read
57
+ // filter only, because `WITH CHECK OPTION` is not the default. Same
58
+ // posture again.
59
+ L18: ['info', { rolesFrom: 'anon' }],
60
+ // Privilege that arrives through a function body: a definer function's
61
+ // reach, and the write an INSTEAD OF trigger's definer function performs.
62
+ // Both are the L8 escalation with different syntax, and ship the same way.
63
+ L19: ['info', { rolesFrom: 'anon' }],
64
+ L20: ['info', { rolesFrom: 'anon' }]
65
+ }
66
+ };
67
+ /**
68
+ * Everything on and escalated. Fail-closed hygiene findings (dead grants,
69
+ * locked tables) are re-tuned upward and even contribute a fraction of
70
+ * their weight to the score.
71
+ */
72
+ export const strict = {
73
+ extends: 'safegres:recommended',
74
+ rules: {
75
+ A1: 'medium',
76
+ A3: 'medium',
77
+ A4: 'high',
78
+ A5: 'medium',
79
+ A6: 'medium',
80
+ A8: 'medium',
81
+ P1b: 'high'
82
+ },
83
+ scoring: { failClosedWeight: 0.25 },
84
+ failOn: { severity: 'high' }
85
+ };
86
+ /**
87
+ * Tuned for Constructive's architecture:
88
+ * - the exposure surface auto-resolves from the routing plane
89
+ * (`routing_public.apis` → `api_schemas` → `metaschema_public.schema`),
90
+ * so only what the exposed APIs can reach drives the score;
91
+ * - untrusted-role rules watch `anonymous`; anything that can leak rows
92
+ * across the role boundary is critical;
93
+ * - A3 is demoted to `info` rather than switched off — API roles never own
94
+ * tables in the Constructive model, so non-FORCEd RLS is not an exposure
95
+ * here, but the finding stays visible (info carries zero weight, so the
96
+ * score is identical to switching it off) and re-tunable;
97
+ * - `pg_partman`'s schema is skipped: it creates child partitions and
98
+ * templates at runtime with no dependency on the extension, so ownership
99
+ * alone leaves them looking like unsecured application tables.
100
+ */
101
+ export const constructive = {
102
+ extends: 'safegres:recommended',
103
+ exposure: { resolver: 'constructive' },
104
+ extensions: { ignore: ['pg_partman'] },
105
+ rules: {
106
+ A2: 'critical',
107
+ A3: 'info',
108
+ P5: 'critical',
109
+ // Each API declares its own `anon_role`; the adapter reads it, so a
110
+ // custom one is picked up instead of being missed, with the platform
111
+ // default kept explicitly so an unresolved surface still checks it.
112
+ // `role_name` (`authenticated`) is deliberately *not* included: a
113
+ // signed-in user holding a write grant is the product, not a finding.
114
+ R1: ['critical', { roles: ['anonymous'], rolesFrom: 'anon' }],
115
+ R2: ['high', { roles: ['anonymous'], rolesFrom: 'anon' }],
116
+ R3: 'medium',
117
+ L5: ['info', { roles: ['anonymous'], rolesFrom: 'anon' }],
118
+ // House-style convention rules, enforced here for the first time:
119
+ // never set search_path (C1), never use #variable_conflict (C2),
120
+ // schema-qualify every relation (C3, adoption severity — ratchet to
121
+ // error once clean), and no dynamic SQL (C4) unless waived inline with a
122
+ // categorized reason (`-- safegres-disable-next-line no-dynamic-sql --
123
+ // lookup-only: …`).
124
+ C1: 'high',
125
+ C2: 'medium',
126
+ C3: 'low',
127
+ C4: 'high'
128
+ },
129
+ scoring: { floorOnCritical: 'C' },
130
+ // The headline grades the API surface, which is the product's own question.
131
+ // These are the two other questions this codebase actually gates on, and
132
+ // neither is well served by being averaged into the first.
133
+ scorecards: {
134
+ 'anon-surface': {
135
+ title: 'Anonymous surface',
136
+ description: 'What an unauthenticated caller reaches — exposure ignored, leaks only.',
137
+ select: {
138
+ roles: ['anonymous'],
139
+ direction: 'fail-open',
140
+ exposure: 'all',
141
+ denominator: 'all',
142
+ severities: 'declared'
143
+ },
144
+ // Nothing about the anonymous surface is informational. The L-series
145
+ // ships at `info` because each rule is new, not because "an
146
+ // unauthenticated caller reaches this" is a matter of interest — so on
147
+ // the card that asks exactly that question, a finding costs a point.
148
+ weights: { info: 1 },
149
+ floorOnCritical: 'C'
150
+ },
151
+ 'sql-conventions': {
152
+ title: 'SQL conventions',
153
+ description: 'House style in function bodies — reachable or not, it is still the codebase.',
154
+ select: { rules: ['C*'], exposure: 'all', denominator: 'all' }
155
+ }
156
+ }
157
+ };
158
+ /** Structural flags only — a fast CI smoke check. */
159
+ export const minimal = {
160
+ rules: {
161
+ '*': 'off',
162
+ A1: 'critical',
163
+ A2: 'high',
164
+ A3: 'medium'
165
+ }
166
+ };
167
+ /**
168
+ * PostgREST: the exposed schemas come from `pgrst.db_schemas` in the catalog,
169
+ * and `anon` is the role an unauthenticated request runs as — so anything it
170
+ * can write, or any policy that lets it through, is the whole threat model.
171
+ *
172
+ * The connecting role (`authenticator`) is graded as a secondary plane rather
173
+ * than folded into the headline: it can `SET ROLE`, so its own grants are a
174
+ * separate question from what the API serves.
175
+ */
176
+ export const postgrest = {
177
+ extends: 'safegres:recommended',
178
+ exposure: { adapters: ['postgrest'] },
179
+ rules: {
180
+ // The anon role is `pgrst.db_anon_role`, so the adapter knows its name and
181
+ // the rules take it from the resolved surface rather than assuming `anon`.
182
+ R1: ['critical', { rolesFrom: 'anon' }],
183
+ R2: ['critical', { rolesFrom: 'anon' }],
184
+ R3: 'high',
185
+ L5: ['high', { rolesFrom: 'anon' }]
186
+ },
187
+ scoring: { floorOnCritical: 'C' }
188
+ };
189
+ /**
190
+ * Supabase is PostgREST with a fixed role vocabulary: `anon` (unauthenticated)
191
+ * and `authenticated` (any signed-up user — a much weaker boundary than it
192
+ * reads, since anyone can sign up) are both untrusted; `service_role` bypasses
193
+ * RLS by design, so its plane reports as skipped rather than F.
194
+ *
195
+ * `auth`, `storage`, `realtime`, `vault` and friends are Supabase's own
196
+ * schemas: they ship with their own policies and are not the developer's to
197
+ * fix, so they are scoped out of the score while staying in the report.
198
+ */
199
+ export const supabase = {
200
+ extends: 'safegres:postgrest',
201
+ exposure: { adapters: ['supabase'] },
202
+ rules: {
203
+ // Both are named outright, not just the anon one: on Supabase anyone can
204
+ // sign up, so `authenticated` is a boundary the internet crosses at will.
205
+ // Fixed by the platform, so unlike PostgREST's anon role they are safe to
206
+ // name — and the adapter's anon set unions in on a self-hosted project.
207
+ R1: ['critical', { roles: ['anon', 'authenticated'], rolesFrom: 'anon' }],
208
+ R2: ['critical', { roles: ['anon', 'authenticated'], rolesFrom: 'anon' }],
209
+ L5: ['high', { roles: ['anon', 'authenticated'], rolesFrom: 'anon' }]
210
+ },
211
+ overrides: [
212
+ // Supabase-managed schemas: their policies ship with the platform, not
213
+ // with your migrations. Demoted, not excluded — still in the report.
214
+ {
215
+ tables: [
216
+ 'auth.*',
217
+ 'storage.*',
218
+ 'realtime.*',
219
+ 'vault.*',
220
+ 'extensions.*',
221
+ 'graphql.*',
222
+ 'supabase_migrations.*'
223
+ ],
224
+ rules: { '*': 'info' }
225
+ }
226
+ ]
227
+ };
228
+ /**
229
+ * PostGraphile / graphile-starter: `app_public` is served, `app_hidden` is
230
+ * reachable through it, `app_private` is not exposed.
231
+ *
232
+ * The request role is `<app>_visitor` — project-specific, so it cannot be
233
+ * named here. The adapter resolves it from role membership and the rules take
234
+ * it from there. Every request runs as that role, authenticated or not (the
235
+ * caller is a JWT claim, not a role), which is exactly what makes it
236
+ * untrusted.
237
+ *
238
+ * A3 stays at its default: in this layout the API role is *not* the table
239
+ * owner, so a missing FORCE genuinely is an owner-bypass hole.
240
+ */
241
+ export const graphile = {
242
+ extends: 'safegres:recommended',
243
+ exposure: { adapters: ['graphile'] },
244
+ rules: {
245
+ // Every request is the visitor role, signed in or not, so for graphile the
246
+ // anon set and the API-edge set are the same thing.
247
+ R1: ['high', { rolesFrom: 'anon' }],
248
+ R2: ['high', { rolesFrom: 'anon' }],
249
+ R3: 'high',
250
+ L5: ['medium', { rolesFrom: 'anon' }]
251
+ }
252
+ };
253
+ /** Hasura: the surface is whatever is *tracked* in `hdb_catalog`. */
254
+ export const hasura = {
255
+ extends: 'safegres:recommended',
256
+ exposure: { adapters: ['hasura'] },
257
+ rules: {
258
+ R1: ['critical', { roles: ['anonymous', 'public'] }],
259
+ R2: ['critical', { roles: ['anonymous', 'public'] }],
260
+ R3: 'high'
261
+ },
262
+ overrides: [
263
+ // Hasura's own metadata catalog, managed by the engine.
264
+ { tables: ['hdb_catalog.*'], rules: { '*': 'info' } }
265
+ ]
266
+ };
267
+ /**
268
+ * Tenancy posture, independent of stack: in a shared-table multi-tenant
269
+ * database every RLS gap is a cross-tenant read, not a self-service leak, so
270
+ * the row-visibility rules are escalated and a single critical caps the grade
271
+ * at D. Owner-bypass hygiene is *not* escalated — it is a deployment property,
272
+ * and drowning the report in it hides the boundary findings.
273
+ *
274
+ * Compose it: `"extends": ["safegres:supabase", "safegres:multi-tenant"]`.
275
+ */
276
+ export const multiTenant = {
277
+ rules: {
278
+ A1: 'critical',
279
+ A2: 'critical',
280
+ A4: 'high',
281
+ L1: 'critical',
282
+ L2: 'critical',
283
+ L3: 'high',
284
+ L5: 'high',
285
+ P5: 'high'
286
+ },
287
+ scoring: { floorOnCritical: 'D' },
288
+ failOn: { severity: 'critical' }
289
+ };
290
+ /**
291
+ * Perf posture for a read-heavy OLTP database, where a sequential scan behind
292
+ * an RLS policy is a production incident rather than a slow report. Security
293
+ * stays at whatever the composed preset says; this only retunes the P/X axis
294
+ * and gates on the perf grade, which is only meaningful once a baseline
295
+ * exists — see `--write-baseline`.
296
+ */
297
+ export const oltp = {
298
+ rules: {
299
+ // The four that turn a policy into a per-row function call or defeat the
300
+ // index it should be using — the difference between an index scan and a
301
+ // seq scan per request.
302
+ X2: 'critical',
303
+ X3: 'critical',
304
+ X4: 'high',
305
+ X9: 'high',
306
+ X1: 'high',
307
+ X6: 'high',
308
+ P1b: 'high',
309
+ P5: 'high'
310
+ },
311
+ failOn: { perfGrade: 'C' }
312
+ };
313
+ export const PRESETS = {
314
+ 'safegres:recommended': recommended,
315
+ 'safegres:strict': strict,
316
+ 'safegres:constructive': constructive,
317
+ 'safegres:minimal': minimal,
318
+ 'safegres:postgrest': postgrest,
319
+ 'safegres:supabase': supabase,
320
+ 'safegres:graphile': graphile,
321
+ 'safegres:hasura': hasura,
322
+ 'safegres:multi-tenant': multiTenant,
323
+ 'safegres:oltp': oltp
324
+ };
@@ -0,0 +1,47 @@
1
+ import type { Dimension, Finding, Severity } from '../types';
2
+ import type { OverrideEntry, RulesConfig, SafegresConfig } from './types';
3
+ export interface ResolvedRule {
4
+ enabled: boolean;
5
+ severity: Severity;
6
+ options?: Record<string, unknown>;
7
+ }
8
+ export interface ResolvedRules {
9
+ rules: Map<string, ResolvedRule>;
10
+ overrides: OverrideEntry[];
11
+ }
12
+ export declare class ConfigValidationError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ /**
16
+ * Apply a RulesConfig on top of a base rule map. Wildcard selectors apply
17
+ * first, exact codes second — so `{ 'P*': 'off', P1: 'high' }` disables all
18
+ * P-rules except P1.
19
+ */
20
+ export declare function applyRulesConfig(base: Map<string, ResolvedRule>, rulesConfig: RulesConfig, strictUnknown?: boolean): Map<string, ResolvedRule>;
21
+ /** Base rule map: every registered rule enabled at its default severity. */
22
+ export declare function defaultRuleMap(): Map<string, ResolvedRule>;
23
+ /** Resolve the rule + override portion of a merged SafegresConfig. */
24
+ export declare function resolveRules(config: SafegresConfig): ResolvedRules;
25
+ /** Rule codes belonging to a scoring dimension. */
26
+ export declare function ruleCodesForDimension(dimension: Dimension): string[];
27
+ /** Simple `*` glob match against a qualified `schema.table` name. */
28
+ export declare function matchTablePattern(pattern: string, qualified: string): boolean;
29
+ /**
30
+ * The effective rule settings for one table: base rules plus any matching
31
+ * override entries (later entries win).
32
+ */
33
+ export declare function rulesForTable(resolved: ResolvedRules, schema: string, table: string): Map<string, ResolvedRule>;
34
+ /**
35
+ * Apply resolved rules to raw findings: drop findings for disabled rules and
36
+ * restamp severities from config (respecting per-table overrides).
37
+ */
38
+ export declare function applyRulesToFindings(resolved: ResolvedRules, findings: Finding[]): Finding[];
39
+ /**
40
+ * True when every AST-scoped rule is disabled (lets the audit skip parsing).
41
+ *
42
+ * The policy-aware index rules (X2–X4) are AST-scoped but only run when the
43
+ * perf dimension is on, so they hold parsing open only in that mode.
44
+ */
45
+ export declare function allAstRulesDisabled(resolved: ResolvedRules, options?: {
46
+ perf?: boolean;
47
+ }): boolean;
@@ -0,0 +1,180 @@
1
+ import { dimensionOf, expandRuleSelector, isKnownRule, RULES, RULES_BY_CODE } from '../rules/registry';
2
+ import { SEVERITY_ORDER } from '../types';
3
+ export class ConfigValidationError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'ConfigValidationError';
7
+ }
8
+ }
9
+ function parseSetting(code, setting) {
10
+ if (setting === 'off') {
11
+ return { enabled: false, severity: RULES_BY_CODE.get(code).defaultSeverity };
12
+ }
13
+ if (typeof setting === 'string') {
14
+ assertSeverity(code, setting);
15
+ return { enabled: true, severity: setting };
16
+ }
17
+ if (Array.isArray(setting) && setting.length >= 1) {
18
+ assertSeverity(code, setting[0]);
19
+ return { enabled: true, severity: setting[0], options: setting[1] };
20
+ }
21
+ throw new ConfigValidationError(`Invalid setting for rule "${code}": expected "off", a severity, or [severity, options].`);
22
+ }
23
+ function assertSeverity(code, sev) {
24
+ if (!(sev in SEVERITY_ORDER)) {
25
+ throw new ConfigValidationError(`Invalid severity "${sev}" for rule "${code}". Expected one of: ${Object.keys(SEVERITY_ORDER).join(', ')}, off.`);
26
+ }
27
+ }
28
+ /**
29
+ * Apply a RulesConfig on top of a base rule map. Wildcard selectors apply
30
+ * first, exact codes second — so `{ 'P*': 'off', P1: 'high' }` disables all
31
+ * P-rules except P1.
32
+ */
33
+ export function applyRulesConfig(base, rulesConfig, strictUnknown = true) {
34
+ const out = new Map(base);
35
+ const entries = Object.entries(rulesConfig);
36
+ const wildcards = entries.filter(([sel]) => sel.endsWith('*'));
37
+ const exacts = entries.filter(([sel]) => !sel.endsWith('*'));
38
+ for (const [selector, setting] of [...wildcards, ...exacts]) {
39
+ if (!selector.endsWith('*') && !isKnownRule(selector)) {
40
+ if (strictUnknown) {
41
+ throw new ConfigValidationError(`Unknown rule code "${selector}". Known rules: ${RULES.map((r) => r.code).join(', ')}.`);
42
+ }
43
+ continue;
44
+ }
45
+ for (const code of expandRuleSelector(selector)) {
46
+ out.set(code, parseSetting(code, setting));
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /** Base rule map: every registered rule enabled at its default severity. */
52
+ export function defaultRuleMap() {
53
+ return new Map(RULES.map((r) => [r.code, { enabled: true, severity: r.defaultSeverity }]));
54
+ }
55
+ /** Resolve the rule + override portion of a merged SafegresConfig. */
56
+ export function resolveRules(config) {
57
+ let rules = defaultRuleMap();
58
+ if (config.rules) {
59
+ rules = applyRulesConfig(rules, config.rules);
60
+ }
61
+ if (config.perf?.rules) {
62
+ assertPerfSelectors(config.perf.rules);
63
+ rules = applyRulesConfig(rules, config.perf.rules);
64
+ }
65
+ const overrides = config.overrides ?? [];
66
+ for (const o of overrides) {
67
+ if (!Array.isArray(o.tables) || o.tables.length === 0) {
68
+ throw new ConfigValidationError('Every "overrides" entry needs a non-empty "tables" array.');
69
+ }
70
+ // Validate override rule settings eagerly.
71
+ applyRulesConfig(rules, o.rules ?? {});
72
+ }
73
+ const publicRead = config.public?.read;
74
+ if (publicRead !== undefined) {
75
+ if (!Array.isArray(publicRead) || publicRead.some((p) => typeof p !== 'string' || p.length === 0)) {
76
+ throw new ConfigValidationError('"public.read" must be an array of non-empty schema.table glob patterns.');
77
+ }
78
+ }
79
+ const perfIgnore = config.perf?.ignore;
80
+ if (perfIgnore !== undefined) {
81
+ if (!Array.isArray(perfIgnore) || perfIgnore.some((p) => typeof p !== 'string' || p.length === 0)) {
82
+ throw new ConfigValidationError('"perf.ignore" must be an array of non-empty schema.table glob patterns.');
83
+ }
84
+ }
85
+ const ignoredExtensions = config.extensions?.ignore;
86
+ if (ignoredExtensions !== undefined) {
87
+ if (!Array.isArray(ignoredExtensions)
88
+ || ignoredExtensions.some((e) => typeof e !== 'string' || e.length === 0)) {
89
+ throw new ConfigValidationError('"extensions.ignore" must be an array of non-empty extension names.');
90
+ }
91
+ }
92
+ return { rules, overrides };
93
+ }
94
+ /**
95
+ * `perf.rules` may only retune perf-dimension codes — otherwise a security
96
+ * rule would silently change behind the perf flag.
97
+ */
98
+ function assertPerfSelectors(rules) {
99
+ for (const selector of Object.keys(rules)) {
100
+ const codes = expandRuleSelector(selector);
101
+ if (codes.length === 0) {
102
+ throw new ConfigValidationError(`Unknown rule code "${selector}" in "perf.rules". Perf rules: ${perfRuleCodes().join(', ')}.`);
103
+ }
104
+ const security = codes.filter((code) => dimensionOf(RULES_BY_CODE.get(code)) !== 'perf');
105
+ if (security.length > 0) {
106
+ throw new ConfigValidationError(`"perf.rules" may only configure perf-dimension rules; "${selector}" matches security rule(s) `
107
+ + `${security.join(', ')}. Configure those under the top-level "rules".`);
108
+ }
109
+ }
110
+ }
111
+ /** Rule codes belonging to a scoring dimension. */
112
+ export function ruleCodesForDimension(dimension) {
113
+ return RULES.filter((r) => dimensionOf(r) === dimension).map((r) => r.code);
114
+ }
115
+ function perfRuleCodes() {
116
+ return ruleCodesForDimension('perf');
117
+ }
118
+ /** Simple `*` glob match against a qualified `schema.table` name. */
119
+ export function matchTablePattern(pattern, qualified) {
120
+ const re = new RegExp(`^${pattern.split('*').map(escapeRegExp).join('.*')}$`);
121
+ return re.test(qualified);
122
+ }
123
+ function escapeRegExp(s) {
124
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
125
+ }
126
+ /**
127
+ * The effective rule settings for one table: base rules plus any matching
128
+ * override entries (later entries win).
129
+ */
130
+ export function rulesForTable(resolved, schema, table) {
131
+ const qualified = `${schema}.${table}`;
132
+ let rules = resolved.rules;
133
+ for (const o of resolved.overrides) {
134
+ if (o.tables.some((p) => matchTablePattern(p, qualified))) {
135
+ rules = applyRulesConfig(rules, o.rules ?? {});
136
+ }
137
+ }
138
+ return rules;
139
+ }
140
+ /**
141
+ * Apply resolved rules to raw findings: drop findings for disabled rules and
142
+ * restamp severities from config (respecting per-table overrides).
143
+ */
144
+ export function applyRulesToFindings(resolved, findings) {
145
+ const out = [];
146
+ for (const f of findings) {
147
+ const rules = f.schema && f.table ? rulesForTable(resolved, f.schema, f.table) : resolved.rules;
148
+ const rule = rules.get(f.code);
149
+ if (!rule) {
150
+ out.push(f); // unknown/unregistered code — pass through untouched
151
+ continue;
152
+ }
153
+ if (!rule.enabled)
154
+ continue;
155
+ out.push(f.severity === rule.severity ? f : { ...f, severity: rule.severity });
156
+ }
157
+ return out;
158
+ }
159
+ /**
160
+ * True when every AST-scoped rule is disabled (lets the audit skip parsing).
161
+ *
162
+ * The policy-aware index rules (X2–X4) are AST-scoped but only run when the
163
+ * perf dimension is on, so they hold parsing open only in that mode.
164
+ */
165
+ export function allAstRulesDisabled(resolved, options = {}) {
166
+ const astCodes = RULES
167
+ .filter((r) => r.scope === 'policy-ast')
168
+ .filter((r) => options.perf || r.category !== 'index')
169
+ .map((r) => r.code);
170
+ const base = astCodes.every((code) => resolved.rules.get(code)?.enabled === false);
171
+ if (!base)
172
+ return false;
173
+ // An override could re-enable an AST rule for some table.
174
+ for (const o of resolved.overrides) {
175
+ const merged = applyRulesConfig(resolved.rules, o.rules ?? {});
176
+ if (astCodes.some((code) => merged.get(code)?.enabled))
177
+ return false;
178
+ }
179
+ return true;
180
+ }