@safegres/test 0.0.2 → 1.30.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,569 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULES_BY_CODE = exports.RULES = void 0;
4
+ exports.dimensionOf = dimensionOf;
5
+ exports.subjectOf = subjectOf;
6
+ exports.isKnownRule = isKnownRule;
7
+ exports.expandRuleSelector = expandRuleSelector;
8
+ /** The scoring axis a rule belongs to (`security` unless declared otherwise). */
9
+ function dimensionOf(meta) {
10
+ return meta.dimension ?? 'security';
11
+ }
12
+ /** What a rule's findings are about (`relation` unless declared otherwise). */
13
+ function subjectOf(meta) {
14
+ return meta.subject ?? 'relation';
15
+ }
16
+ exports.RULES = [
17
+ {
18
+ code: 'A1',
19
+ category: 'flags',
20
+ defaultSeverity: 'low',
21
+ direction: 'fail-closed',
22
+ title: 'RLS enabled but no policies (deny-all — confirm the lock is intended)',
23
+ scope: 'table'
24
+ },
25
+ {
26
+ code: 'A2',
27
+ category: 'flags',
28
+ defaultSeverity: 'high',
29
+ direction: 'fail-open',
30
+ title: 'Grants exist on a table with RLS disabled',
31
+ scope: 'table'
32
+ },
33
+ {
34
+ code: 'A3',
35
+ category: 'flags',
36
+ defaultSeverity: 'low',
37
+ direction: 'fail-open',
38
+ title: 'RLS enabled but FORCE ROW LEVEL SECURITY not set (owner bypass)',
39
+ scope: 'table'
40
+ },
41
+ {
42
+ code: 'A4',
43
+ category: 'coverage',
44
+ defaultSeverity: 'low',
45
+ direction: 'fail-closed',
46
+ title: 'INSERT/UPDATE/DELETE grant with no covering policy — writes are denied at runtime',
47
+ scope: 'table'
48
+ },
49
+ {
50
+ code: 'A5',
51
+ category: 'coverage',
52
+ defaultSeverity: 'low',
53
+ direction: 'fail-closed',
54
+ title: 'SELECT grant with no covering policy — queries silently return 0 rows',
55
+ scope: 'table'
56
+ },
57
+ {
58
+ code: 'A6',
59
+ category: 'coverage',
60
+ defaultSeverity: 'info',
61
+ direction: 'fail-closed',
62
+ title: 'UPDATE has USING but no WITH CHECK (row-smuggling surface)',
63
+ scope: 'table'
64
+ },
65
+ {
66
+ code: 'A7',
67
+ category: 'anti-pattern',
68
+ defaultSeverity: 'critical',
69
+ direction: 'fail-open',
70
+ title: 'Trivially-permissive WRITE policy (INSERT/UPDATE/DELETE/ALL with literal true)',
71
+ scope: 'policy-ast'
72
+ },
73
+ {
74
+ code: 'A8',
75
+ category: 'anti-pattern',
76
+ defaultSeverity: 'low',
77
+ direction: 'fail-open',
78
+ title: 'Trivially-permissive SELECT policy (USING (true) — confirm public-read is intended)',
79
+ scope: 'policy-ast'
80
+ },
81
+ {
82
+ code: 'P1',
83
+ category: 'anti-pattern',
84
+ defaultSeverity: 'high',
85
+ direction: 'neutral',
86
+ dimension: 'perf',
87
+ title: 'Policy body calls a VOLATILE function (per-row evaluation)',
88
+ scope: 'policy-ast'
89
+ },
90
+ {
91
+ code: 'P1b',
92
+ category: 'anti-pattern',
93
+ defaultSeverity: 'medium',
94
+ direction: 'neutral',
95
+ dimension: 'perf',
96
+ title: 'Policy body calls a SECURITY DEFINER wrapper (cannot be inlined)',
97
+ scope: 'policy-ast'
98
+ },
99
+ {
100
+ code: 'P5',
101
+ category: 'anti-pattern',
102
+ defaultSeverity: 'high',
103
+ direction: 'fail-open',
104
+ title: 'Policy body references session_user / current_user / pg_has_role',
105
+ scope: 'policy-ast'
106
+ },
107
+ {
108
+ code: 'R1',
109
+ category: 'anti-pattern',
110
+ defaultSeverity: 'critical',
111
+ direction: 'fail-open',
112
+ title: 'Untrusted role holds a write privilege (options: { roles: [...] })',
113
+ scope: 'table'
114
+ },
115
+ {
116
+ code: 'R2',
117
+ category: 'anti-pattern',
118
+ defaultSeverity: 'high',
119
+ direction: 'fail-open',
120
+ title: 'Permissive write policy applies to an untrusted role or PUBLIC (options: { roles: [...] })',
121
+ scope: 'table'
122
+ },
123
+ {
124
+ code: 'R3',
125
+ category: 'anti-pattern',
126
+ defaultSeverity: 'medium',
127
+ direction: 'fail-open',
128
+ title: 'RLS table has grants TO PUBLIC (includes all current and future roles)',
129
+ scope: 'table'
130
+ },
131
+ {
132
+ code: 'L1',
133
+ category: 'coverage',
134
+ defaultSeverity: 'low',
135
+ direction: 'fail-closed',
136
+ title: 'Dead indirect grant — privilege arrives via PUBLIC or role inheritance but no policy can admit it',
137
+ scope: 'table'
138
+ },
139
+ {
140
+ code: 'L2',
141
+ category: 'coverage',
142
+ defaultSeverity: 'low',
143
+ direction: 'fail-closed',
144
+ title: 'Dead policy — a permissive policy applies to a role holding no corresponding grant',
145
+ scope: 'table'
146
+ },
147
+ {
148
+ code: 'L3',
149
+ category: 'coverage',
150
+ defaultSeverity: 'low',
151
+ direction: 'fail-closed',
152
+ title: 'Unreachable grant — object privilege without USAGE on the schema',
153
+ scope: 'table'
154
+ },
155
+ {
156
+ code: 'L4',
157
+ category: 'coverage',
158
+ defaultSeverity: 'info',
159
+ direction: 'neutral',
160
+ title: 'Dead schema USAGE — the role reaches no relation and no function in the schema (advisory)',
161
+ scope: 'table'
162
+ },
163
+ {
164
+ code: 'L5',
165
+ category: 'anti-pattern',
166
+ defaultSeverity: 'info',
167
+ direction: 'fail-open',
168
+ title: 'Untrusted role reaches an RLS-off table via PUBLIC or inheritance (options: { roles: [...] })',
169
+ scope: 'table'
170
+ },
171
+ {
172
+ code: 'L6',
173
+ category: 'coverage',
174
+ defaultSeverity: 'info',
175
+ direction: 'neutral',
176
+ title: 'Unaddressable grant — an API role holds privileges on a relation its API cannot name',
177
+ scope: 'table'
178
+ },
179
+ {
180
+ code: 'L7',
181
+ category: 'anti-pattern',
182
+ // Ships `info` (signal-only, zero score weight) because the rule is new
183
+ // and unproven — NOT because the fact is minor. An unauthenticated role
184
+ // that can SET ROLE to a role with extra grants is a real escalation, and
185
+ // when the assumed role bypasses RLS (context.targetBypassesRls) it should
186
+ // be `high`; escalate via config/preset once the finding proves itself.
187
+ defaultSeverity: 'info',
188
+ direction: 'fail-open',
189
+ title: 'Untrusted role can SET ROLE to a role with more reach (options: { roles: [...] })',
190
+ scope: 'table'
191
+ },
192
+ {
193
+ code: 'L8',
194
+ category: 'anti-pattern',
195
+ // Ships `info` (signal-only, zero score weight) because the rule is new
196
+ // and, uniquely among the L rules, reads SQL bodies rather than pure
197
+ // catalog. The fact is not informational: a definer view handing an
198
+ // anonymous role a table it holds no grant on is A2/L5 laundered through
199
+ // a view, and it is `medium` on its own merits — `high` when the base
200
+ // table has RLS the view owner is exempt from (`context.rlsBypassed`),
201
+ // because then the view also deletes the row filter. Escalate via
202
+ // config/preset once the finding proves itself in the field.
203
+ defaultSeverity: 'info',
204
+ direction: 'fail-open',
205
+ title: 'DEFINER view bypass — an untrusted role reads a base relation as the view owner (options: { roles: [...] })',
206
+ // Reported against the base relation, and gated on `skipAstChecks` in the
207
+ // audit rather than on `policy-ast`: the body it parses is a view's, not a
208
+ // policy's, so turning the `P*` rules off does not turn this one off.
209
+ scope: 'table'
210
+ },
211
+ {
212
+ code: 'L9',
213
+ category: 'anti-pattern',
214
+ // Ships `info` for the same reason as L8, and understates the same way: a
215
+ // definer view that is auto-updatable does not just leak rows, it lets an
216
+ // untrusted role *write* a table it holds nothing on, as the owner. On its
217
+ // own merits that is `high` — the write is unconditional, and when the
218
+ // base table has RLS the owner is exempt from (`context.rlsBypassed`) it
219
+ // also writes rows no policy would have admitted. Escalate via
220
+ // config/preset once the finding proves itself in the field.
221
+ defaultSeverity: 'info',
222
+ direction: 'fail-open',
223
+ title: 'DEFINER view write — an untrusted role writes a base relation as the view owner (options: { roles: [...] })',
224
+ scope: 'table'
225
+ },
226
+ {
227
+ code: 'L10',
228
+ category: 'anti-pattern',
229
+ // Ships `info` on the same new-rule posture. Note this one fires on
230
+ // `security_invoker` views too: `security_invoker` governs the view's own
231
+ // base relations, not the relations a rewrite rule's actions name, which
232
+ // are checked against the rule's table owner either way (verified against
233
+ // PG 18). A rule is also invisible to `pg_get_viewdef`, so this is reach
234
+ // no reading of the view's definition can find.
235
+ defaultSeverity: 'info',
236
+ direction: 'fail-open',
237
+ title: 'Rewrite-rule bypass — a rule on a view writes a relation as the view owner (options: { roles: [...] })',
238
+ scope: 'table'
239
+ },
240
+ {
241
+ code: 'L11',
242
+ category: 'anti-pattern',
243
+ // Ships `info` on the same new-rule posture, and understates: a matview is
244
+ // a stored copy, so a SELECT grant on it is an unconditional grant on the
245
+ // rows a REFRESH captured — RLS on the base relation never runs, and the
246
+ // matview can carry neither policies nor `security_invoker`. On its own
247
+ // merits that is `high` when the base is RLS-protected.
248
+ defaultSeverity: 'info',
249
+ direction: 'fail-open',
250
+ title: 'Materialized-view snapshot — an untrusted role reads stored rows the base relation would not serve it (options: { roles: [...] })',
251
+ scope: 'table'
252
+ },
253
+ {
254
+ code: 'L12',
255
+ category: 'anti-pattern',
256
+ // Ships `info`, and is the weakest of the L-series on purpose: the leak
257
+ // needs a leaky operator or a cheap function in the caller's own qual, so
258
+ // it is a capability rather than an unconditional read. `medium` once
259
+ // proven — the rows it exposes are precisely the ones the view was written
260
+ // to withhold.
261
+ defaultSeverity: 'info',
262
+ direction: 'fail-open',
263
+ title: 'Non-barrier filtering view — a view is an untrusted role\'s only path to a relation but its row filter is not a boundary (options: { roles: [...] })',
264
+ scope: 'table'
265
+ },
266
+ {
267
+ code: 'L13',
268
+ category: 'anti-pattern',
269
+ // Ships `info` on the same new-rule posture. The honest severity depends
270
+ // entirely on the relation: column-level SELECT on an RLS-off table for an
271
+ // anonymous role is the same exposure A2 calls `high`, and until this rule
272
+ // existed nothing in the package saw it at all, because every grant query
273
+ // read `relacl` and a column grant lives in `pg_attribute.attacl`.
274
+ defaultSeverity: 'info',
275
+ direction: 'fail-open',
276
+ title: 'Column-level grant — an untrusted role reaches a relation through column privileges no relation ACL shows (options: { roles: [...] })',
277
+ scope: 'table'
278
+ },
279
+ {
280
+ code: 'L14',
281
+ // `coverage`, not `anti-pattern`: the finding is that the audit stopped at
282
+ // the schema boundary, not that the view is wrong. Severity stays `info`
283
+ // however the reach is graded later — an unknown is never a proven leak,
284
+ // and scoring one would let an excluded schema move the number.
285
+ category: 'coverage',
286
+ defaultSeverity: 'info',
287
+ direction: 'neutral',
288
+ title: 'Unaudited base relation — a definer view reads a relation in a schema the audit did not introspect (options: { roles: [...] })',
289
+ scope: 'table'
290
+ },
291
+ {
292
+ code: 'L15',
293
+ // The other half of L14's coverage story: there the far end was out of
294
+ // scope, here the body itself is. Both report that nothing was graded,
295
+ // which is why neither is scored — an unreadable view is not evidence of
296
+ // a leak, and letting one move the number would reward opacity.
297
+ category: 'coverage',
298
+ defaultSeverity: 'info',
299
+ direction: 'neutral',
300
+ title: 'Unreadable body — an untrusted role reaches through a definer view or function whose body could not be followed (options: { roles: [...] })',
301
+ scope: 'table'
302
+ },
303
+ {
304
+ code: 'L16',
305
+ category: 'anti-pattern',
306
+ defaultSeverity: 'info',
307
+ direction: 'fail-open',
308
+ title: 'Sequence privilege — an untrusted role can advance or read a sequence, which no policy filters (options: { roles: [...] })',
309
+ scope: 'table'
310
+ },
311
+ {
312
+ code: 'L17',
313
+ // The A2 shape on an object where A2's remedy does not exist: Postgres
314
+ // refuses RLS on a foreign table, so "add a policy" is not available and
315
+ // the grant is unconditionally unfiltered.
316
+ category: 'anti-pattern',
317
+ defaultSeverity: 'info',
318
+ direction: 'fail-open',
319
+ title: 'Foreign-table grant — an untrusted role reaches a relation that cannot carry RLS (options: { roles: [...] })',
320
+ scope: 'table'
321
+ },
322
+ {
323
+ code: 'L18',
324
+ category: 'anti-pattern',
325
+ // Ships `info` on the same new-rule posture. The honest severity is
326
+ // `medium`: the view's `WHERE` is the only thing standing between an
327
+ // untrusted role and the relation, and without `WITH CHECK OPTION` that
328
+ // condition governs reads alone — the role writes rows outside it, which
329
+ // on a tenant or ownership predicate is a cross-boundary write. It stays
330
+ // below L9 because the write path itself is intended; what is missing is
331
+ // the constraint on it.
332
+ defaultSeverity: 'info',
333
+ direction: 'fail-open',
334
+ title: 'Writable filtering view without WITH CHECK OPTION — an untrusted role writes rows the view excludes (options: { roles: [...] })',
335
+ scope: 'table'
336
+ },
337
+ {
338
+ code: 'L19',
339
+ category: 'anti-pattern',
340
+ // The largest reach edge in the L-series, shipping on the same new-rule
341
+ // posture as the rest. Proven, the honest severity is A2's: a definer
342
+ // function hands an anonymous role a relation it holds nothing on, and
343
+ // unlike a view there is no `security_invoker` default to fall back on —
344
+ // Postgres grants EXECUTE to PUBLIC unless someone says otherwise.
345
+ defaultSeverity: 'info',
346
+ direction: 'fail-open',
347
+ title: 'Definer-function reach — an untrusted role touches a relation by executing a SECURITY DEFINER function (options: { roles: [...] })',
348
+ scope: 'table',
349
+ // The reach is the function's, not the relation's: a definer function in
350
+ // an unexposed schema still runs as its owner for whoever holds EXECUTE.
351
+ subject: 'routine',
352
+ // One finding per (relation × function) pair, but one revoke per function.
353
+ dedupBy: 'context.function'
354
+ },
355
+ {
356
+ code: 'L20',
357
+ category: 'anti-pattern',
358
+ // The write half of L19, and the suppression L9 has carried since it
359
+ // shipped: a write against a view with INSTEAD OF triggers becomes the
360
+ // trigger function's body, so it is that function's security attribute —
361
+ // not the view's — that decides who the write runs as.
362
+ defaultSeverity: 'info',
363
+ direction: 'fail-open',
364
+ title: 'INSTEAD OF trigger write — an untrusted role writes a relation through a trigger function that runs as its owner (options: { roles: [...] })',
365
+ scope: 'table',
366
+ subject: 'routine',
367
+ dedupBy: 'context.function'
368
+ },
369
+ {
370
+ code: 'L21',
371
+ // `granted − reachable`. The user asked for "L10", but L10 has been the
372
+ // rewrite-rule bypass rule since it shipped; this takes the next free
373
+ // L-code and keeps every existing identity intact.
374
+ //
375
+ // `coverage`, not `anti-pattern`: like L1/L3/L6 the finding is about what a
376
+ // grant can and cannot reach, not a leak — and it is `fail-closed`, so it
377
+ // is weightless and never moves the score. It ships `info` because it is
378
+ // new and, uniquely, its verdict is a *revoke recommendation*: the cost of
379
+ // a wrong one is an outage, so it stays advisory until the reachability
380
+ // proof has earned trust in the field. Its honest severity once proven is
381
+ // `low` — a revocable grant is latent surface, not an active leak, and the
382
+ // value is the retained-grant proof list, which is emitted whatever the
383
+ // severity. The rule is fail-closed by construction: it recommends a revoke
384
+ // only for a grant it proved unreachable, and suppresses (retains) every
385
+ // grant whose closure it could not fully read.
386
+ category: 'coverage',
387
+ defaultSeverity: 'info',
388
+ direction: 'fail-closed',
389
+ title: 'Revocable grant — a role holds EXECUTE no reachable path exercises (options: { roles: [...] })',
390
+ scope: 'table',
391
+ subject: 'routine'
392
+ },
393
+ {
394
+ code: 'W1',
395
+ category: 'meta',
396
+ defaultSeverity: 'medium',
397
+ direction: 'neutral',
398
+ title: 'No exposure surface configured — the whole database is assumed reachable and the score is capped',
399
+ scope: 'table'
400
+ },
401
+ {
402
+ code: 'X1',
403
+ category: 'index',
404
+ defaultSeverity: 'medium',
405
+ direction: 'neutral',
406
+ dimension: 'perf',
407
+ title: 'Foreign key with no covering index (slow joins and cascading deletes)',
408
+ scope: 'index'
409
+ },
410
+ {
411
+ code: 'X2',
412
+ category: 'index',
413
+ defaultSeverity: 'medium',
414
+ direction: 'neutral',
415
+ dimension: 'perf',
416
+ title: 'RLS policy filters on a column that is not the leading column of any index',
417
+ scope: 'policy-ast'
418
+ },
419
+ {
420
+ code: 'X3',
421
+ category: 'index',
422
+ defaultSeverity: 'medium',
423
+ direction: 'neutral',
424
+ dimension: 'perf',
425
+ title: 'RLS policy casts or wraps its own column, defeating a plain index',
426
+ scope: 'policy-ast'
427
+ },
428
+ {
429
+ code: 'X4',
430
+ category: 'index',
431
+ defaultSeverity: 'low',
432
+ direction: 'neutral',
433
+ dimension: 'perf',
434
+ title: 'RLS policy calls a non-LEAKPROOF function, blocking qual pushdown',
435
+ scope: 'policy-ast'
436
+ },
437
+ {
438
+ code: 'X5',
439
+ category: 'index',
440
+ defaultSeverity: 'low',
441
+ direction: 'neutral',
442
+ dimension: 'perf',
443
+ title: 'Redundant index — duplicated by, or a leading-column prefix of, another index',
444
+ scope: 'index'
445
+ },
446
+ {
447
+ code: 'X6',
448
+ category: 'index',
449
+ defaultSeverity: 'low',
450
+ direction: 'neutral',
451
+ dimension: 'perf',
452
+ title: 'Table has no primary key / no usable replica identity',
453
+ scope: 'index'
454
+ },
455
+ {
456
+ code: 'X7',
457
+ category: 'index',
458
+ defaultSeverity: 'medium',
459
+ direction: 'neutral',
460
+ dimension: 'perf',
461
+ title: 'Search column (tsvector/vector) with no index the search can use',
462
+ scope: 'index'
463
+ },
464
+ {
465
+ code: 'X8',
466
+ category: 'index',
467
+ defaultSeverity: 'info',
468
+ direction: 'neutral',
469
+ dimension: 'perf',
470
+ title: 'Sort-shaped column (timestamp/date) leads no index — heuristic advisory',
471
+ scope: 'index'
472
+ },
473
+ {
474
+ code: 'X9',
475
+ category: 'index',
476
+ defaultSeverity: 'medium',
477
+ direction: 'neutral',
478
+ dimension: 'perf',
479
+ title: 'RLS policy calls a STABLE function per row — not wrapped in a scalar sub-select (no InitPlan)',
480
+ scope: 'policy-ast'
481
+ },
482
+ {
483
+ code: 'S1',
484
+ category: 'index',
485
+ defaultSeverity: 'medium',
486
+ direction: 'neutral',
487
+ dimension: 'perf',
488
+ title: 'Sequential-scan-dominant table (runtime statistics)',
489
+ scope: 'stats'
490
+ },
491
+ {
492
+ code: 'S2',
493
+ category: 'index',
494
+ defaultSeverity: 'low',
495
+ direction: 'neutral',
496
+ dimension: 'perf',
497
+ title: 'Index the planner has never chosen (runtime statistics)',
498
+ scope: 'stats'
499
+ },
500
+ {
501
+ code: 'S3',
502
+ category: 'index',
503
+ defaultSeverity: 'low',
504
+ direction: 'neutral',
505
+ dimension: 'perf',
506
+ title: 'Dead-tuple bloat — autovacuum is not keeping up (runtime statistics)',
507
+ scope: 'stats'
508
+ },
509
+ {
510
+ code: 'S4',
511
+ category: 'index',
512
+ defaultSeverity: 'info',
513
+ direction: 'neutral',
514
+ dimension: 'perf',
515
+ title: 'Statement hotspot on a table in scope (pg_stat_statements)',
516
+ scope: 'stats'
517
+ },
518
+ {
519
+ code: 'C1',
520
+ category: 'convention',
521
+ defaultSeverity: 'high',
522
+ direction: 'fail-open',
523
+ title: 'Function sets search_path (house rule: never set it — fully-qualify instead)',
524
+ scope: 'function-src',
525
+ subject: 'routine'
526
+ },
527
+ {
528
+ code: 'C2',
529
+ category: 'convention',
530
+ defaultSeverity: 'medium',
531
+ direction: 'neutral',
532
+ title: 'Function uses a #variable_conflict directive',
533
+ scope: 'function-src',
534
+ subject: 'routine'
535
+ },
536
+ {
537
+ code: 'C3',
538
+ category: 'convention',
539
+ defaultSeverity: 'low',
540
+ direction: 'neutral',
541
+ title: 'Function has an unqualified relation reference (relies on search_path)',
542
+ scope: 'function-src',
543
+ subject: 'routine',
544
+ // Every unqualified reference in one body is one edit to that body.
545
+ dedupBy: 'context.function'
546
+ },
547
+ {
548
+ code: 'C4',
549
+ category: 'convention',
550
+ defaultSeverity: 'high',
551
+ direction: 'fail-open',
552
+ title: 'Function uses dynamic SQL (waivable with a categorized reason)',
553
+ scope: 'function-src',
554
+ subject: 'routine',
555
+ dedupBy: 'context.function'
556
+ }
557
+ ];
558
+ exports.RULES_BY_CODE = new Map(exports.RULES.map((r) => [r.code, r]));
559
+ function isKnownRule(code) {
560
+ return exports.RULES_BY_CODE.has(code);
561
+ }
562
+ /** Expand a rule selector: an exact code (`P1b`) or a prefix wildcard (`P*`, `*`). */
563
+ function expandRuleSelector(selector) {
564
+ if (selector.endsWith('*')) {
565
+ const prefix = selector.slice(0, -1);
566
+ return exports.RULES.filter((r) => r.code.startsWith(prefix)).map((r) => r.code);
567
+ }
568
+ return isKnownRule(selector) ? [selector] : [];
569
+ }