@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,210 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.safegresConfigLoader = safegresConfigLoader;
37
+ exports.loadConfig = loadConfig;
38
+ exports.configPathBase = configPathBase;
39
+ const confstash_1 = require("confstash");
40
+ const path = __importStar(require("path"));
41
+ const presets_1 = require("./presets");
42
+ const resolve_1 = require("./resolve");
43
+ const validate_1 = require("./validate");
44
+ /**
45
+ * The safegres config loader. Discovers `safegres.config.{ts,js,mjs,cjs}`,
46
+ * `.safegresrc{,.json,.yaml,.yml,.js}`, `safegres.json`, or the `"safegres"`
47
+ * package.json key by walking up from cwd; resolves `extends` against the
48
+ * built-in presets (or files / npm packages).
49
+ */
50
+ function safegresConfigLoader() {
51
+ return (0, confstash_1.createConfigLoader)({
52
+ tool: 'safegres',
53
+ presets: presets_1.PRESETS,
54
+ defaults: {},
55
+ validate: (config) => {
56
+ (0, validate_1.validateConfigShape)(config); // unknown keys, wrong value kinds
57
+ (0, resolve_1.resolveRules)(config); // throws ConfigValidationError on bad rules/overrides
58
+ }
59
+ });
60
+ }
61
+ /** Load and merge the effective safegres config. */
62
+ function loadConfig(params = {}) {
63
+ if (params.sealed)
64
+ return loadSealedConfig(params.preset);
65
+ const loader = safegresConfigLoader();
66
+ const overrides = { ...(params.overrides ?? {}) };
67
+ if (params.preset) {
68
+ const name = params.preset.includes(':') ? params.preset : `safegres:${params.preset}`;
69
+ if (!(name in presets_1.PRESETS)) {
70
+ throw new Error(`Unknown preset "${params.preset}". Available: ${Object.keys(presets_1.PRESETS).join(', ')}`);
71
+ }
72
+ overrides.extends = name;
73
+ }
74
+ // `extends` in overrides isn't expanded by confstash (it only expands file
75
+ // layers), so a CLI --preset is merged in as a layer *below* the file config.
76
+ const result = describeExtendsFailure(() => loader.loadSync({
77
+ cwd: params.cwd,
78
+ configFile: params.configFile,
79
+ overrides: stripExtends(overrides)
80
+ }));
81
+ const config = withUnionedOverrides(result);
82
+ if (params.preset) {
83
+ const presetConfig = presets_1.PRESETS[overrides.extends];
84
+ return { ...result, config: mergePreset(presetConfig, config) };
85
+ }
86
+ return { ...result, config };
87
+ }
88
+ /**
89
+ * A missing `extends` target surfaces from the loader as a bare `ENOENT` on a
90
+ * path nobody typed — the resolved one. Say what it was reached from, and that
91
+ * a name is a preset while a path is a file, which is the actual mistake.
92
+ */
93
+ function describeExtendsFailure(load) {
94
+ try {
95
+ return load();
96
+ }
97
+ catch (error) {
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ if (!message.includes('ENOENT'))
100
+ throw error;
101
+ throw new Error(`${message}\nsafegres: an "extends" target could not be read. A built-in preset is a name `
102
+ + `(${Object.keys(presets_1.PRESETS).join(', ')}); anything else is resolved as a path relative to `
103
+ + 'the file that declared it, or as an npm package.');
104
+ }
105
+ }
106
+ /**
107
+ * `overrides` is a list of scoped exceptions, so inheriting one file from
108
+ * another has to *add* to it. Array-typed keys otherwise replace — a child
109
+ * that narrows `exposure.schemas` means the narrower list — but replacing here
110
+ * would silently drop the base file's exceptions, which is how a shared config
111
+ * turns into two divergent copies. Preset chains have always unioned it; this
112
+ * makes a file chain agree.
113
+ */
114
+ function withUnionedOverrides(result) {
115
+ const seen = new Set();
116
+ const unioned = [];
117
+ for (const layer of result.layers) {
118
+ for (const override of layer.config.overrides ?? []) {
119
+ const key = JSON.stringify(override);
120
+ if (seen.has(key))
121
+ continue;
122
+ seen.add(key);
123
+ unioned.push(override);
124
+ }
125
+ }
126
+ if (unioned.length === 0)
127
+ return result.config;
128
+ return { ...result.config, overrides: unioned };
129
+ }
130
+ /**
131
+ * Resolve each configured path against the file that declared it, so an
132
+ * inherited `perf.baseline` keeps pointing at the base file's directory.
133
+ * Values that came from a preset or from CLI overrides have no directory of
134
+ * their own and fall back to the discovered config file, then to `cwd`.
135
+ */
136
+ function configPathBase(result, cwd = process.cwd()) {
137
+ const fallback = result.filepath ? path.dirname(result.filepath) : cwd;
138
+ return {
139
+ dirFor(key) {
140
+ // Highest precedence first: the layer that won the merge is the layer
141
+ // whose directory the value is written relative to.
142
+ for (let i = result.layers.length - 1; i >= 0; i--) {
143
+ const layer = result.layers[i];
144
+ if (valueAt(layer.config, key) === undefined)
145
+ continue;
146
+ return layer.source === 'file' && layer.origin ? path.dirname(layer.origin) : fallback;
147
+ }
148
+ return fallback;
149
+ }
150
+ };
151
+ }
152
+ function valueAt(config, key) {
153
+ let node = config;
154
+ for (const part of key.split('.')) {
155
+ if (node == null || typeof node !== 'object')
156
+ return undefined;
157
+ node = node[part];
158
+ }
159
+ return node;
160
+ }
161
+ /**
162
+ * The sealed config: one named preset, expanded, and nothing else. No
163
+ * discovery, no overrides, no `extends` to a local file — so the only thing
164
+ * that can change the score is the database.
165
+ */
166
+ function loadSealedConfig(preset = 'recommended') {
167
+ const name = preset.includes(':') ? preset : `safegres:${preset}`;
168
+ if (!(name in presets_1.PRESETS)) {
169
+ throw new Error(`Unknown preset "${preset}". Available: ${Object.keys(presets_1.PRESETS).join(', ')}`);
170
+ }
171
+ return { config: expandPreset(presets_1.PRESETS[name]), layers: [], isEmpty: true };
172
+ }
173
+ function stripExtends(config) {
174
+ const { extends: _extends, ...rest } = config;
175
+ return rest;
176
+ }
177
+ function mergePreset(preset, over) {
178
+ const base = expandPreset(preset);
179
+ return {
180
+ ...base,
181
+ ...over,
182
+ rules: { ...(base.rules ?? {}), ...(over.rules ?? {}) },
183
+ scoring: { ...(base.scoring ?? {}), ...(over.scoring ?? {}) },
184
+ failOn: { ...(base.failOn ?? {}), ...(over.failOn ?? {}) },
185
+ // Per name: a project adds its own card without dropping the preset's,
186
+ // and redefines one by naming it.
187
+ ...(base.scorecards || over.scorecards
188
+ ? { scorecards: { ...(base.scorecards ?? {}), ...(over.scorecards ?? {}) } }
189
+ : {}),
190
+ overrides: [...(base.overrides ?? []), ...(over.overrides ?? [])],
191
+ // Merged per-key so retuning `skipOwned` doesn't silently drop the
192
+ // preset's `ignore` list. Omitted entirely when neither side sets it.
193
+ ...(base.extensions || over.extensions
194
+ ? { extensions: { ...(base.extensions ?? {}), ...(over.extensions ?? {}) } }
195
+ : {})
196
+ };
197
+ }
198
+ function expandPreset(preset) {
199
+ if (!preset.extends)
200
+ return preset;
201
+ const parents = Array.isArray(preset.extends) ? preset.extends : [preset.extends];
202
+ let acc = {};
203
+ for (const p of parents) {
204
+ const parent = presets_1.PRESETS[p];
205
+ if (parent)
206
+ acc = mergePreset(expandPreset(parent), acc);
207
+ }
208
+ const { extends: _extends, ...rest } = preset;
209
+ return mergePreset(acc, rest);
210
+ }
@@ -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,327 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRESETS = exports.oltp = exports.multiTenant = exports.hasura = exports.graphile = exports.supabase = exports.postgrest = exports.minimal = exports.constructive = exports.strict = exports.recommended = void 0;
4
+ /**
5
+ * Built-in presets, resolvable via `extends: 'safegres:<name>'`.
6
+ * A preset is just a partial config; users can publish their own as files
7
+ * or npm packages.
8
+ */
9
+ /**
10
+ * Today's default behavior: every rule at its registry default severity.
11
+ *
12
+ * The untrusted-role rules take whichever roles the surface says are reachable
13
+ * without credentials, so a declared `exposure.anonRoles` (or an adapter that
14
+ * resolves one) is enough to switch them on. With no such surface `anonRoles`
15
+ * is empty and they stay inert, exactly as before.
16
+ */
17
+ exports.recommended = {
18
+ rules: {
19
+ // The convention linter (`C*`) enforces Constructive house style rather
20
+ // than a universal security fact, so the big-tent preset leaves it off;
21
+ // the `constructive` preset turns it on.
22
+ 'C*': 'off',
23
+ R1: ['critical', { rolesFrom: 'anon' }],
24
+ R2: ['high', { rolesFrom: 'anon' }],
25
+ L5: ['info', { rolesFrom: 'anon' }],
26
+ // Signal-only for now: an unauthenticated role that can SET ROLE to a role
27
+ // with more reach is a real escalation, but the rule is new and unproven,
28
+ // so it reports at zero weight until validated. See the registry entry.
29
+ L7: ['info', { rolesFrom: 'anon' }],
30
+ // Same posture, same reason: a view that executes as its owner hands its
31
+ // readers that owner's reach, which is a real bypass, but the rule is new
32
+ // and body-derived, so it reports at zero weight until validated.
33
+ L8: ['info', { rolesFrom: 'anon' }],
34
+ // The write half of the same story: an auto-updatable definer view, and a
35
+ // rewrite rule whose action runs as the view owner. Same posture again —
36
+ // new, body-derived, zero weight until validated.
37
+ L9: ['info', { rolesFrom: 'anon' }],
38
+ L10: ['info', { rolesFrom: 'anon' }],
39
+ // The other two ways a readable view is not what its definition says: a
40
+ // materialized view serves rows RLS never filtered, and a filtering view
41
+ // without `security_barrier` is not a boundary. Same posture again.
42
+ L11: ['info', { rolesFrom: 'anon' }],
43
+ L12: ['info', { rolesFrom: 'anon' }],
44
+ // Reach that only `pg_attribute.attacl` shows. New, so zero weight — but
45
+ // note this one is catalog-derived, not body-derived: what was missing was
46
+ // the introspection, not the proof.
47
+ L13: ['info', { rolesFrom: 'anon' }],
48
+ // The coverage half of L8: a body reference the audit could not follow
49
+ // because the schema was out of scope. Reports an unknown, never a leak.
50
+ L14: ['info', { rolesFrom: 'anon' }],
51
+ // And the coverage half of the body itself: a view an untrusted role reads
52
+ // whose definition this analysis could not follow. Also an unknown.
53
+ L15: ['info', { rolesFrom: 'anon' }],
54
+ // Privileges on the objects that are not tables. Neither is row-filterable
55
+ // — RLS does not apply to a sequence and cannot be enabled on a foreign
56
+ // table at all — so both are reach the table rules never saw.
57
+ L16: ['info', { rolesFrom: 'anon' }],
58
+ L17: ['info', { rolesFrom: 'anon' }],
59
+ // The write-side twin of L12: a writable view whose `WHERE` is a read
60
+ // filter only, because `WITH CHECK OPTION` is not the default. Same
61
+ // posture again.
62
+ L18: ['info', { rolesFrom: 'anon' }],
63
+ // Privilege that arrives through a function body: a definer function's
64
+ // reach, and the write an INSTEAD OF trigger's definer function performs.
65
+ // Both are the L8 escalation with different syntax, and ship the same way.
66
+ L19: ['info', { rolesFrom: 'anon' }],
67
+ L20: ['info', { rolesFrom: 'anon' }]
68
+ }
69
+ };
70
+ /**
71
+ * Everything on and escalated. Fail-closed hygiene findings (dead grants,
72
+ * locked tables) are re-tuned upward and even contribute a fraction of
73
+ * their weight to the score.
74
+ */
75
+ exports.strict = {
76
+ extends: 'safegres:recommended',
77
+ rules: {
78
+ A1: 'medium',
79
+ A3: 'medium',
80
+ A4: 'high',
81
+ A5: 'medium',
82
+ A6: 'medium',
83
+ A8: 'medium',
84
+ P1b: 'high'
85
+ },
86
+ scoring: { failClosedWeight: 0.25 },
87
+ failOn: { severity: 'high' }
88
+ };
89
+ /**
90
+ * Tuned for Constructive's architecture:
91
+ * - the exposure surface auto-resolves from the routing plane
92
+ * (`routing_public.apis` → `api_schemas` → `metaschema_public.schema`),
93
+ * so only what the exposed APIs can reach drives the score;
94
+ * - untrusted-role rules watch `anonymous`; anything that can leak rows
95
+ * across the role boundary is critical;
96
+ * - A3 is demoted to `info` rather than switched off — API roles never own
97
+ * tables in the Constructive model, so non-FORCEd RLS is not an exposure
98
+ * here, but the finding stays visible (info carries zero weight, so the
99
+ * score is identical to switching it off) and re-tunable;
100
+ * - `pg_partman`'s schema is skipped: it creates child partitions and
101
+ * templates at runtime with no dependency on the extension, so ownership
102
+ * alone leaves them looking like unsecured application tables.
103
+ */
104
+ exports.constructive = {
105
+ extends: 'safegres:recommended',
106
+ exposure: { resolver: 'constructive' },
107
+ extensions: { ignore: ['pg_partman'] },
108
+ rules: {
109
+ A2: 'critical',
110
+ A3: 'info',
111
+ P5: 'critical',
112
+ // Each API declares its own `anon_role`; the adapter reads it, so a
113
+ // custom one is picked up instead of being missed, with the platform
114
+ // default kept explicitly so an unresolved surface still checks it.
115
+ // `role_name` (`authenticated`) is deliberately *not* included: a
116
+ // signed-in user holding a write grant is the product, not a finding.
117
+ R1: ['critical', { roles: ['anonymous'], rolesFrom: 'anon' }],
118
+ R2: ['high', { roles: ['anonymous'], rolesFrom: 'anon' }],
119
+ R3: 'medium',
120
+ L5: ['info', { roles: ['anonymous'], rolesFrom: 'anon' }],
121
+ // House-style convention rules, enforced here for the first time:
122
+ // never set search_path (C1), never use #variable_conflict (C2),
123
+ // schema-qualify every relation (C3, adoption severity — ratchet to
124
+ // error once clean), and no dynamic SQL (C4) unless waived inline with a
125
+ // categorized reason (`-- safegres-disable-next-line no-dynamic-sql --
126
+ // lookup-only: …`).
127
+ C1: 'high',
128
+ C2: 'medium',
129
+ C3: 'low',
130
+ C4: 'high'
131
+ },
132
+ scoring: { floorOnCritical: 'C' },
133
+ // The headline grades the API surface, which is the product's own question.
134
+ // These are the two other questions this codebase actually gates on, and
135
+ // neither is well served by being averaged into the first.
136
+ scorecards: {
137
+ 'anon-surface': {
138
+ title: 'Anonymous surface',
139
+ description: 'What an unauthenticated caller reaches — exposure ignored, leaks only.',
140
+ select: {
141
+ roles: ['anonymous'],
142
+ direction: 'fail-open',
143
+ exposure: 'all',
144
+ denominator: 'all',
145
+ severities: 'declared'
146
+ },
147
+ // Nothing about the anonymous surface is informational. The L-series
148
+ // ships at `info` because each rule is new, not because "an
149
+ // unauthenticated caller reaches this" is a matter of interest — so on
150
+ // the card that asks exactly that question, a finding costs a point.
151
+ weights: { info: 1 },
152
+ floorOnCritical: 'C'
153
+ },
154
+ 'sql-conventions': {
155
+ title: 'SQL conventions',
156
+ description: 'House style in function bodies — reachable or not, it is still the codebase.',
157
+ select: { rules: ['C*'], exposure: 'all', denominator: 'all' }
158
+ }
159
+ }
160
+ };
161
+ /** Structural flags only — a fast CI smoke check. */
162
+ exports.minimal = {
163
+ rules: {
164
+ '*': 'off',
165
+ A1: 'critical',
166
+ A2: 'high',
167
+ A3: 'medium'
168
+ }
169
+ };
170
+ /**
171
+ * PostgREST: the exposed schemas come from `pgrst.db_schemas` in the catalog,
172
+ * and `anon` is the role an unauthenticated request runs as — so anything it
173
+ * can write, or any policy that lets it through, is the whole threat model.
174
+ *
175
+ * The connecting role (`authenticator`) is graded as a secondary plane rather
176
+ * than folded into the headline: it can `SET ROLE`, so its own grants are a
177
+ * separate question from what the API serves.
178
+ */
179
+ exports.postgrest = {
180
+ extends: 'safegres:recommended',
181
+ exposure: { adapters: ['postgrest'] },
182
+ rules: {
183
+ // The anon role is `pgrst.db_anon_role`, so the adapter knows its name and
184
+ // the rules take it from the resolved surface rather than assuming `anon`.
185
+ R1: ['critical', { rolesFrom: 'anon' }],
186
+ R2: ['critical', { rolesFrom: 'anon' }],
187
+ R3: 'high',
188
+ L5: ['high', { rolesFrom: 'anon' }]
189
+ },
190
+ scoring: { floorOnCritical: 'C' }
191
+ };
192
+ /**
193
+ * Supabase is PostgREST with a fixed role vocabulary: `anon` (unauthenticated)
194
+ * and `authenticated` (any signed-up user — a much weaker boundary than it
195
+ * reads, since anyone can sign up) are both untrusted; `service_role` bypasses
196
+ * RLS by design, so its plane reports as skipped rather than F.
197
+ *
198
+ * `auth`, `storage`, `realtime`, `vault` and friends are Supabase's own
199
+ * schemas: they ship with their own policies and are not the developer's to
200
+ * fix, so they are scoped out of the score while staying in the report.
201
+ */
202
+ exports.supabase = {
203
+ extends: 'safegres:postgrest',
204
+ exposure: { adapters: ['supabase'] },
205
+ rules: {
206
+ // Both are named outright, not just the anon one: on Supabase anyone can
207
+ // sign up, so `authenticated` is a boundary the internet crosses at will.
208
+ // Fixed by the platform, so unlike PostgREST's anon role they are safe to
209
+ // name — and the adapter's anon set unions in on a self-hosted project.
210
+ R1: ['critical', { roles: ['anon', 'authenticated'], rolesFrom: 'anon' }],
211
+ R2: ['critical', { roles: ['anon', 'authenticated'], rolesFrom: 'anon' }],
212
+ L5: ['high', { roles: ['anon', 'authenticated'], rolesFrom: 'anon' }]
213
+ },
214
+ overrides: [
215
+ // Supabase-managed schemas: their policies ship with the platform, not
216
+ // with your migrations. Demoted, not excluded — still in the report.
217
+ {
218
+ tables: [
219
+ 'auth.*',
220
+ 'storage.*',
221
+ 'realtime.*',
222
+ 'vault.*',
223
+ 'extensions.*',
224
+ 'graphql.*',
225
+ 'supabase_migrations.*'
226
+ ],
227
+ rules: { '*': 'info' }
228
+ }
229
+ ]
230
+ };
231
+ /**
232
+ * PostGraphile / graphile-starter: `app_public` is served, `app_hidden` is
233
+ * reachable through it, `app_private` is not exposed.
234
+ *
235
+ * The request role is `<app>_visitor` — project-specific, so it cannot be
236
+ * named here. The adapter resolves it from role membership and the rules take
237
+ * it from there. Every request runs as that role, authenticated or not (the
238
+ * caller is a JWT claim, not a role), which is exactly what makes it
239
+ * untrusted.
240
+ *
241
+ * A3 stays at its default: in this layout the API role is *not* the table
242
+ * owner, so a missing FORCE genuinely is an owner-bypass hole.
243
+ */
244
+ exports.graphile = {
245
+ extends: 'safegres:recommended',
246
+ exposure: { adapters: ['graphile'] },
247
+ rules: {
248
+ // Every request is the visitor role, signed in or not, so for graphile the
249
+ // anon set and the API-edge set are the same thing.
250
+ R1: ['high', { rolesFrom: 'anon' }],
251
+ R2: ['high', { rolesFrom: 'anon' }],
252
+ R3: 'high',
253
+ L5: ['medium', { rolesFrom: 'anon' }]
254
+ }
255
+ };
256
+ /** Hasura: the surface is whatever is *tracked* in `hdb_catalog`. */
257
+ exports.hasura = {
258
+ extends: 'safegres:recommended',
259
+ exposure: { adapters: ['hasura'] },
260
+ rules: {
261
+ R1: ['critical', { roles: ['anonymous', 'public'] }],
262
+ R2: ['critical', { roles: ['anonymous', 'public'] }],
263
+ R3: 'high'
264
+ },
265
+ overrides: [
266
+ // Hasura's own metadata catalog, managed by the engine.
267
+ { tables: ['hdb_catalog.*'], rules: { '*': 'info' } }
268
+ ]
269
+ };
270
+ /**
271
+ * Tenancy posture, independent of stack: in a shared-table multi-tenant
272
+ * database every RLS gap is a cross-tenant read, not a self-service leak, so
273
+ * the row-visibility rules are escalated and a single critical caps the grade
274
+ * at D. Owner-bypass hygiene is *not* escalated — it is a deployment property,
275
+ * and drowning the report in it hides the boundary findings.
276
+ *
277
+ * Compose it: `"extends": ["safegres:supabase", "safegres:multi-tenant"]`.
278
+ */
279
+ exports.multiTenant = {
280
+ rules: {
281
+ A1: 'critical',
282
+ A2: 'critical',
283
+ A4: 'high',
284
+ L1: 'critical',
285
+ L2: 'critical',
286
+ L3: 'high',
287
+ L5: 'high',
288
+ P5: 'high'
289
+ },
290
+ scoring: { floorOnCritical: 'D' },
291
+ failOn: { severity: 'critical' }
292
+ };
293
+ /**
294
+ * Perf posture for a read-heavy OLTP database, where a sequential scan behind
295
+ * an RLS policy is a production incident rather than a slow report. Security
296
+ * stays at whatever the composed preset says; this only retunes the P/X axis
297
+ * and gates on the perf grade, which is only meaningful once a baseline
298
+ * exists — see `--write-baseline`.
299
+ */
300
+ exports.oltp = {
301
+ rules: {
302
+ // The four that turn a policy into a per-row function call or defeat the
303
+ // index it should be using — the difference between an index scan and a
304
+ // seq scan per request.
305
+ X2: 'critical',
306
+ X3: 'critical',
307
+ X4: 'high',
308
+ X9: 'high',
309
+ X1: 'high',
310
+ X6: 'high',
311
+ P1b: 'high',
312
+ P5: 'high'
313
+ },
314
+ failOn: { perfGrade: 'C' }
315
+ };
316
+ exports.PRESETS = {
317
+ 'safegres:recommended': exports.recommended,
318
+ 'safegres:strict': exports.strict,
319
+ 'safegres:constructive': exports.constructive,
320
+ 'safegres:minimal': exports.minimal,
321
+ 'safegres:postgrest': exports.postgrest,
322
+ 'safegres:supabase': exports.supabase,
323
+ 'safegres:graphile': exports.graphile,
324
+ 'safegres:hasura': exports.hasura,
325
+ 'safegres:multi-tenant': exports.multiTenant,
326
+ 'safegres:oltp': exports.oltp
327
+ };
@@ -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;