@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,18 @@
1
+ DROP SCHEMA IF EXISTS c_owner_bypass CASCADE;
2
+ CREATE SCHEMA c_owner_bypass;
3
+ GRANT USAGE ON SCHEMA c_owner_bypass TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_owner_bypass.secrets (
6
+ id bigserial PRIMARY KEY,
7
+ owner_id uuid NOT NULL,
8
+ token text NOT NULL
9
+ );
10
+ CREATE INDEX secrets_owner_idx ON c_owner_bypass.secrets (owner_id);
11
+ -- The flaw: RLS is enabled but not FORCEd, so the table owner — and anything
12
+ -- running as it, including a SECURITY DEFINER function — bypasses every policy.
13
+ ALTER TABLE c_owner_bypass.secrets ENABLE ROW LEVEL SECURITY;
14
+
15
+ CREATE POLICY secrets_owner ON c_owner_bypass.secrets FOR SELECT TO corpus_user
16
+ USING (owner_id = (SELECT nullif(current_setting('jwt.claims.user_id', true), '')::uuid));
17
+
18
+ GRANT SELECT ON c_owner_bypass.secrets TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Policy authorizes on current_user rather than on request identity",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_session_user"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "P5",
19
+ "relation": "c_session_user.reports"
20
+ }
21
+ ],
22
+ "fix": "Authorize on the request claim (current_setting('jwt.claims.user_id')), not the connection role.",
23
+ "id": "11-session-user-in-policy"
24
+ }
@@ -0,0 +1,18 @@
1
+ DROP SCHEMA IF EXISTS c_session_user CASCADE;
2
+ CREATE SCHEMA c_session_user;
3
+ GRANT USAGE ON SCHEMA c_session_user TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_session_user.reports (
6
+ id bigserial PRIMARY KEY,
7
+ owner_name name NOT NULL,
8
+ body text
9
+ );
10
+ ALTER TABLE c_session_user.reports ENABLE ROW LEVEL SECURITY;
11
+ ALTER TABLE c_session_user.reports FORCE ROW LEVEL SECURITY;
12
+
13
+ -- The flaw: identity is taken from the *database* role, but every API request
14
+ -- arrives as the same role. current_user is a constant here, not a user.
15
+ CREATE POLICY reports_owner ON c_session_user.reports FOR SELECT TO corpus_user
16
+ USING (owner_name = current_user);
17
+
18
+ GRANT SELECT ON c_session_user.reports TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Policy calls a VOLATILE function (per-row evaluation)",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_volatile_policy"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "P1",
19
+ "relation": "c_volatile_policy.events"
20
+ }
21
+ ],
22
+ "fix": "Mark the function STABLE and wrap the call in a scalar sub-select so it becomes an InitPlan.",
23
+ "id": "12-volatile-function-in-policy"
24
+ }
@@ -0,0 +1,21 @@
1
+ DROP SCHEMA IF EXISTS c_volatile_policy CASCADE;
2
+ CREATE SCHEMA c_volatile_policy;
3
+ GRANT USAGE ON SCHEMA c_volatile_policy TO corpus_user, corpus_anon;
4
+
5
+ CREATE FUNCTION c_volatile_policy.current_actor() RETURNS uuid
6
+ LANGUAGE sql VOLATILE AS $$ SELECT gen_random_uuid() $$;
7
+
8
+ CREATE TABLE c_volatile_policy.events (
9
+ id bigserial PRIMARY KEY,
10
+ actor_id uuid NOT NULL
11
+ );
12
+ CREATE INDEX events_actor_idx ON c_volatile_policy.events (actor_id);
13
+ ALTER TABLE c_volatile_policy.events ENABLE ROW LEVEL SECURITY;
14
+ ALTER TABLE c_volatile_policy.events FORCE ROW LEVEL SECURITY;
15
+
16
+ -- The flaw: a VOLATILE function cannot be hoisted, so it runs once per row
17
+ -- scanned — not once per query.
18
+ CREATE POLICY events_actor ON c_volatile_policy.events FOR SELECT TO corpus_user
19
+ USING (actor_id = c_volatile_policy.current_actor());
20
+
21
+ GRANT SELECT ON c_volatile_policy.events TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Policy calls a SECURITY DEFINER wrapper",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_definer_wrapper"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "P1b",
19
+ "relation": "c_definer_wrapper.memberships"
20
+ }
21
+ ],
22
+ "fix": "Inline the predicate, or hoist the definer call out of the per-row path.",
23
+ "id": "13-security-definer-policy-wrapper"
24
+ }
@@ -0,0 +1,21 @@
1
+ DROP SCHEMA IF EXISTS c_definer_wrapper CASCADE;
2
+ CREATE SCHEMA c_definer_wrapper;
3
+ GRANT USAGE ON SCHEMA c_definer_wrapper TO corpus_user, corpus_anon;
4
+
5
+ CREATE FUNCTION c_definer_wrapper.is_member(tenant uuid) RETURNS boolean
6
+ LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT tenant IS NOT NULL $$;
7
+
8
+ CREATE TABLE c_definer_wrapper.memberships (
9
+ id bigserial PRIMARY KEY,
10
+ tenant_id uuid NOT NULL
11
+ );
12
+ CREATE INDEX memberships_tenant_idx ON c_definer_wrapper.memberships (tenant_id);
13
+ ALTER TABLE c_definer_wrapper.memberships ENABLE ROW LEVEL SECURITY;
14
+ ALTER TABLE c_definer_wrapper.memberships FORCE ROW LEVEL SECURITY;
15
+
16
+ -- The flaw: a SECURITY DEFINER function is a plan fence — it cannot be
17
+ -- inlined, so the qual never reaches the index.
18
+ CREATE POLICY memberships_member ON c_definer_wrapper.memberships FOR SELECT TO corpus_user
19
+ USING (c_definer_wrapper.is_member(tenant_id));
20
+
21
+ GRANT SELECT ON c_definer_wrapper.memberships TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Dead policy \u2014 written for a role that holds no grant",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_dead_policy"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L2",
19
+ "relation": "c_dead_policy.drafts"
20
+ }
21
+ ],
22
+ "fix": "Grant SELECT to the role the policy names, or point the policy at the role that has the grant.",
23
+ "id": "14-dead-policy"
24
+ }
@@ -0,0 +1,18 @@
1
+ DROP SCHEMA IF EXISTS c_dead_policy CASCADE;
2
+ CREATE SCHEMA c_dead_policy;
3
+ GRANT USAGE ON SCHEMA c_dead_policy TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_dead_policy.drafts (
6
+ id bigserial PRIMARY KEY,
7
+ owner_id uuid NOT NULL
8
+ );
9
+ CREATE INDEX drafts_owner_idx ON c_dead_policy.drafts (owner_id);
10
+ ALTER TABLE c_dead_policy.drafts ENABLE ROW LEVEL SECURITY;
11
+ ALTER TABLE c_dead_policy.drafts FORCE ROW LEVEL SECURITY;
12
+
13
+ -- The flaw: the policy names a role that holds no grant on the table, so it
14
+ -- can never admit a row. Usually a rename that only got applied on one side.
15
+ CREATE POLICY drafts_owner ON c_dead_policy.drafts FOR SELECT TO corpus_anon
16
+ USING (owner_id = (SELECT nullif(current_setting('jwt.claims.user_id', true), '')::uuid));
17
+
18
+ GRANT SELECT ON c_dead_policy.drafts TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Table grant without USAGE on the schema",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_unreachable_grant"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L3",
19
+ "relation": "c_unreachable_grant.attachments"
20
+ }
21
+ ],
22
+ "fix": "GRANT USAGE ON SCHEMA c_unreachable_grant TO corpus_anon, or drop the table grant.",
23
+ "id": "15-unreachable-grant"
24
+ }
@@ -0,0 +1,18 @@
1
+ DROP SCHEMA IF EXISTS c_unreachable_grant CASCADE;
2
+ CREATE SCHEMA c_unreachable_grant;
3
+ GRANT USAGE ON SCHEMA c_unreachable_grant TO corpus_user;
4
+
5
+ CREATE TABLE c_unreachable_grant.attachments (
6
+ id bigserial PRIMARY KEY,
7
+ owner_id uuid NOT NULL
8
+ );
9
+ CREATE INDEX attachments_owner_idx ON c_unreachable_grant.attachments (owner_id);
10
+ ALTER TABLE c_unreachable_grant.attachments ENABLE ROW LEVEL SECURITY;
11
+ ALTER TABLE c_unreachable_grant.attachments FORCE ROW LEVEL SECURITY;
12
+
13
+ CREATE POLICY attachments_read ON c_unreachable_grant.attachments FOR SELECT TO corpus_anon
14
+ USING (owner_id = (SELECT nullif(current_setting('jwt.claims.user_id', true), '')::uuid));
15
+
16
+ -- The flaw: the table privilege exists, but the role has no USAGE on the
17
+ -- schema, so the grant can never be exercised.
18
+ GRANT SELECT ON c_unreachable_grant.attachments TO corpus_anon;
@@ -0,0 +1,34 @@
1
+ {
2
+ "title": "Open write policy applying to the anonymous role",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_anon_permissive_write"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "A7",
19
+ "relation": "c_anon_permissive_write.signups"
20
+ },
21
+ {
22
+ "code": "R1",
23
+ "relation": "c_anon_permissive_write.signups"
24
+ },
25
+ {
26
+ "code": "R2",
27
+ "relation": "c_anon_permissive_write.signups",
28
+ "note": "the policy, not just the grant, lets the anonymous role through"
29
+ }
30
+ ],
31
+ "worstSeverity": "critical",
32
+ "fix": "Constrain the WITH CHECK (approved = false), or take signups through a SECURITY DEFINER function.",
33
+ "id": "16-anon-permissive-write-policy"
34
+ }
@@ -0,0 +1,17 @@
1
+ DROP SCHEMA IF EXISTS c_anon_permissive_write CASCADE;
2
+ CREATE SCHEMA c_anon_permissive_write;
3
+ GRANT USAGE ON SCHEMA c_anon_permissive_write TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_anon_permissive_write.signups (
6
+ id bigserial PRIMARY KEY,
7
+ email text NOT NULL,
8
+ approved boolean NOT NULL DEFAULT false
9
+ );
10
+ ALTER TABLE c_anon_permissive_write.signups ENABLE ROW LEVEL SECURITY;
11
+ ALTER TABLE c_anon_permissive_write.signups FORCE ROW LEVEL SECURITY;
12
+
13
+ -- The flaw: the open write policy applies to the *anonymous* role. Accepting
14
+ -- an anonymous signup row is fine; accepting `approved = true` is not.
15
+ CREATE POLICY signups_insert ON c_anon_permissive_write.signups FOR INSERT TO corpus_anon WITH CHECK (true);
16
+
17
+ GRANT INSERT ON c_anon_permissive_write.signups TO corpus_anon;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Foreign key with no covering index",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_fk_no_index"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X1",
19
+ "relation": "c_fk_no_index.books"
20
+ }
21
+ ],
22
+ "fix": "CREATE INDEX ON c_fk_no_index.books (author_id).",
23
+ "id": "17-foreign-key-without-index"
24
+ }
@@ -0,0 +1,18 @@
1
+ DROP SCHEMA IF EXISTS c_fk_no_index CASCADE;
2
+ CREATE SCHEMA c_fk_no_index;
3
+ GRANT USAGE ON SCHEMA c_fk_no_index TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_fk_no_index.authors (
6
+ id bigserial PRIMARY KEY,
7
+ name text NOT NULL
8
+ );
9
+
10
+ CREATE TABLE c_fk_no_index.books (
11
+ id bigserial PRIMARY KEY,
12
+ -- The flaw: no index on the referencing column. Every DELETE on authors
13
+ -- seq-scans books, and the join has no index either.
14
+ author_id bigint NOT NULL REFERENCES c_fk_no_index.authors (id) ON DELETE CASCADE,
15
+ title text NOT NULL
16
+ );
17
+
18
+ GRANT SELECT ON c_fk_no_index.authors, c_fk_no_index.books TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "RLS predicate column leads no index",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_policy_unindexed"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X2",
19
+ "relation": "c_policy_unindexed.documents"
20
+ }
21
+ ],
22
+ "fix": "CREATE INDEX ON c_policy_unindexed.documents (tenant_id) \u2014 the policy column belongs first.",
23
+ "id": "18-policy-column-unindexed"
24
+ }
@@ -0,0 +1,23 @@
1
+ DROP SCHEMA IF EXISTS c_policy_unindexed CASCADE;
2
+ CREATE SCHEMA c_policy_unindexed;
3
+ GRANT USAGE ON SCHEMA c_policy_unindexed TO corpus_user, corpus_anon;
4
+
5
+ CREATE FUNCTION c_policy_unindexed.current_tenant() RETURNS uuid
6
+ LANGUAGE sql STABLE AS $$
7
+ SELECT nullif(current_setting('jwt.claims.tenant_id', true), '')::uuid
8
+ $$;
9
+
10
+ CREATE TABLE c_policy_unindexed.documents (
11
+ id bigserial PRIMARY KEY,
12
+ -- The flaw: every query against this table carries the policy's
13
+ -- tenant_id predicate, and no index leads with tenant_id.
14
+ tenant_id uuid NOT NULL,
15
+ title text
16
+ );
17
+ ALTER TABLE c_policy_unindexed.documents ENABLE ROW LEVEL SECURITY;
18
+ ALTER TABLE c_policy_unindexed.documents FORCE ROW LEVEL SECURITY;
19
+
20
+ CREATE POLICY documents_tenant ON c_policy_unindexed.documents FOR SELECT TO corpus_user
21
+ USING (tenant_id = (SELECT c_policy_unindexed.current_tenant()));
22
+
23
+ GRANT SELECT ON c_policy_unindexed.documents TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Policy casts its own column, defeating the index",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_policy_cast"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X3",
19
+ "relation": "c_policy_cast.accounts"
20
+ }
21
+ ],
22
+ "fix": "Cast the parameter instead: tenant_id = current_setting(...)::uuid.",
23
+ "id": "19-policy-column-cast"
24
+ }
@@ -0,0 +1,19 @@
1
+ DROP SCHEMA IF EXISTS c_policy_cast CASCADE;
2
+ CREATE SCHEMA c_policy_cast;
3
+ GRANT USAGE ON SCHEMA c_policy_cast TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_policy_cast.accounts (
6
+ id bigserial PRIMARY KEY,
7
+ tenant_id uuid NOT NULL,
8
+ email text
9
+ );
10
+ CREATE INDEX accounts_tenant_idx ON c_policy_cast.accounts (tenant_id);
11
+ ALTER TABLE c_policy_cast.accounts ENABLE ROW LEVEL SECURITY;
12
+ ALTER TABLE c_policy_cast.accounts FORCE ROW LEVEL SECURITY;
13
+
14
+ -- The flaw: casting the *column* (rather than the parameter) makes the
15
+ -- b-tree index above unusable. The index exists and is never chosen.
16
+ CREATE POLICY accounts_tenant ON c_policy_cast.accounts FOR SELECT TO corpus_user
17
+ USING (tenant_id::text = current_setting('jwt.claims.tenant_id', true));
18
+
19
+ GRANT SELECT ON c_policy_cast.accounts TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "STABLE function evaluated per row (no InitPlan)",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_stable_per_row"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X9",
19
+ "relation": "c_stable_per_row.ledger"
20
+ }
21
+ ],
22
+ "fix": "USING (tenant_id = (SELECT current_tenant())) \u2014 the sub-select is what makes it an InitPlan.",
23
+ "id": "20-stable-function-per-row"
24
+ }
@@ -0,0 +1,25 @@
1
+ DROP SCHEMA IF EXISTS c_stable_per_row CASCADE;
2
+ CREATE SCHEMA c_stable_per_row;
3
+ GRANT USAGE ON SCHEMA c_stable_per_row TO corpus_user, corpus_anon;
4
+
5
+ CREATE FUNCTION c_stable_per_row.current_tenant() RETURNS uuid
6
+ LANGUAGE sql STABLE AS $$
7
+ SELECT nullif(current_setting('jwt.claims.tenant_id', true), '')::uuid
8
+ $$;
9
+
10
+ CREATE TABLE c_stable_per_row.ledger (
11
+ id bigserial PRIMARY KEY,
12
+ tenant_id uuid NOT NULL,
13
+ amount numeric
14
+ );
15
+ CREATE INDEX ledger_tenant_idx ON c_stable_per_row.ledger (tenant_id);
16
+ ALTER TABLE c_stable_per_row.ledger ENABLE ROW LEVEL SECURITY;
17
+ ALTER TABLE c_stable_per_row.ledger FORCE ROW LEVEL SECURITY;
18
+
19
+ -- The flaw: the call is row-independent but not wrapped in a scalar
20
+ -- sub-select, so the planner re-evaluates it per row instead of hoisting it
21
+ -- into an InitPlan. Same predicate, one extra pair of parentheses apart.
22
+ CREATE POLICY ledger_tenant ON c_stable_per_row.ledger FOR SELECT TO corpus_user
23
+ USING (tenant_id = c_stable_per_row.current_tenant());
24
+
25
+ GRANT SELECT ON c_stable_per_row.ledger TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Table with no primary key",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_no_pk"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X6",
19
+ "relation": "c_no_pk.click_events"
20
+ }
21
+ ],
22
+ "fix": "Add a primary key, or set an explicit REPLICA IDENTITY.",
23
+ "id": "21-table-without-primary-key"
24
+ }
@@ -0,0 +1,14 @@
1
+ DROP SCHEMA IF EXISTS c_no_pk CASCADE;
2
+ CREATE SCHEMA c_no_pk;
3
+ GRANT USAGE ON SCHEMA c_no_pk TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_no_pk.click_events (
6
+ -- The flaw: no primary key and no replica identity. Rows cannot be
7
+ -- addressed individually, and logical replication of updates fails.
8
+ session_id uuid NOT NULL,
9
+ occurred_at timestamptz NOT NULL DEFAULT now(),
10
+ path text
11
+ );
12
+ CREATE INDEX click_events_session_idx ON c_no_pk.click_events (session_id);
13
+
14
+ GRANT SELECT ON c_no_pk.click_events TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Redundant index \u2014 a prefix of another index",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_redundant_index"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X5",
19
+ "relation": "c_redundant_index.shipments"
20
+ }
21
+ ],
22
+ "fix": "DROP INDEX c_redundant_index.shipments_tenant_idx.",
23
+ "id": "22-redundant-index"
24
+ }
@@ -0,0 +1,16 @@
1
+ DROP SCHEMA IF EXISTS c_redundant_index CASCADE;
2
+ CREATE SCHEMA c_redundant_index;
3
+ GRANT USAGE ON SCHEMA c_redundant_index TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_redundant_index.shipments (
6
+ id bigserial PRIMARY KEY,
7
+ tenant_id uuid NOT NULL,
8
+ status text NOT NULL
9
+ );
10
+
11
+ -- The flaw: the first index is a leading-column prefix of the second, so it
12
+ -- serves no query the second cannot — it only costs write throughput.
13
+ CREATE INDEX shipments_tenant_idx ON c_redundant_index.shipments (tenant_id);
14
+ CREATE INDEX shipments_tenant_status_idx ON c_redundant_index.shipments (tenant_id, status);
15
+
16
+ GRANT SELECT ON c_redundant_index.shipments TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Search column with no index the search can use",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_search_no_index"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X7",
19
+ "relation": "c_search_no_index.articles"
20
+ }
21
+ ],
22
+ "fix": "CREATE INDEX ON c_search_no_index.articles USING gin (search_doc).",
23
+ "id": "23-tsvector-without-index"
24
+ }
@@ -0,0 +1,13 @@
1
+ DROP SCHEMA IF EXISTS c_search_no_index CASCADE;
2
+ CREATE SCHEMA c_search_no_index;
3
+ GRANT USAGE ON SCHEMA c_search_no_index TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_search_no_index.articles (
6
+ id bigserial PRIMARY KEY,
7
+ body text,
8
+ -- The flaw: a tsvector column with no GIN/GiST index. Every full-text
9
+ -- query is a sequential scan that recomputes nothing and reads everything.
10
+ search_doc tsvector
11
+ );
12
+
13
+ GRANT SELECT ON c_search_no_index.articles TO corpus_user;
@@ -0,0 +1,29 @@
1
+ {
2
+ "title": "Anonymous role can SET ROLE to a more-privileged role",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_anon_set_role_escalation"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L7",
19
+ "relation": "c_anon_set_role_escalation.secrets",
20
+ "note": "corpus_anon inherits nothing from c_priv_role (INHERIT FALSE) but can assume it (SET TRUE), gaining a SELECT it holds no passive grant for"
21
+ }
22
+ ],
23
+ "forbid": [
24
+ "A2",
25
+ "L5"
26
+ ],
27
+ "fix": "GRANT c_priv_role TO corpus_anon WITH SET FALSE (or REVOKE the membership) — the anonymous role must not be able to assume a privileged role.",
28
+ "id": "24-anon-set-role-escalation"
29
+ }