@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,29 @@
1
+ DROP SCHEMA IF EXISTS c_leaky_filter_view CASCADE;
2
+ CREATE SCHEMA c_leaky_filter_view;
3
+
4
+ DO $$ BEGIN
5
+ CREATE ROLE c_leaky_view_owner NOLOGIN;
6
+ EXCEPTION WHEN duplicate_object THEN NULL;
7
+ END $$;
8
+
9
+ GRANT USAGE ON SCHEMA c_leaky_filter_view TO corpus_anon, corpus_user, c_leaky_view_owner;
10
+
11
+ CREATE TABLE c_leaky_filter_view.documents (
12
+ id bigserial PRIMARY KEY,
13
+ visibility text NOT NULL,
14
+ title text NOT NULL,
15
+ body text NOT NULL
16
+ );
17
+ ALTER TABLE c_leaky_filter_view.documents OWNER TO c_leaky_view_owner;
18
+
19
+ -- The flaw: the view's WHERE is the only thing separating corpus_anon from the
20
+ -- private documents, and without `security_barrier` the planner may evaluate a
21
+ -- caller-supplied qual before it. A cheap leaky function
22
+ -- (`... WHERE leak(body)`) then runs against every row, private ones included,
23
+ -- and reports what it sees through a notice, an error or a timing difference.
24
+ CREATE VIEW c_leaky_filter_view.public_documents AS
25
+ SELECT id, title, body
26
+ FROM c_leaky_filter_view.documents
27
+ WHERE visibility = 'public';
28
+ ALTER VIEW c_leaky_filter_view.public_documents OWNER TO c_leaky_view_owner;
29
+ GRANT SELECT ON c_leaky_filter_view.public_documents TO corpus_anon, corpus_user;
@@ -0,0 +1,32 @@
1
+ {
2
+ "title": "The same filtering view with security_barrier is a boundary, and L12 stays silent",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_barrier_view"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L8",
19
+ "relation": "c_barrier_view.documents",
20
+ "note": "the view still executes as its owner — that is what it is for — so the definer-view reach edge is still reported; only the question of *which* rows is settled by the barrier"
21
+ }
22
+ ],
23
+ "forbid": [
24
+ "L9",
25
+ "L10",
26
+ "L11",
27
+ "L12"
28
+ ],
29
+ "worstSeverity": "info",
30
+ "fix": "Nothing on the barrier. This case is case 33 after its fix, and exists to pin that L12 stops firing once the row filter is a boundary rather than a convenience.",
31
+ "id": "34-barrier-view-no-leak"
32
+ }
@@ -0,0 +1,29 @@
1
+ DROP SCHEMA IF EXISTS c_barrier_view CASCADE;
2
+ CREATE SCHEMA c_barrier_view;
3
+
4
+ DO $$ BEGIN
5
+ CREATE ROLE c_barrier_view_owner NOLOGIN;
6
+ EXCEPTION WHEN duplicate_object THEN NULL;
7
+ END $$;
8
+
9
+ GRANT USAGE ON SCHEMA c_barrier_view TO corpus_anon, corpus_user, c_barrier_view_owner;
10
+
11
+ CREATE TABLE c_barrier_view.documents (
12
+ id bigserial PRIMARY KEY,
13
+ visibility text NOT NULL,
14
+ title text NOT NULL,
15
+ body text NOT NULL
16
+ );
17
+ ALTER TABLE c_barrier_view.documents OWNER TO c_barrier_view_owner;
18
+
19
+ -- Case 33 with the one option that makes the filter a boundary: the planner
20
+ -- may not push a caller's qual below the view's own, so the excluded rows are
21
+ -- never evaluated. L12 must stay silent. L8 still fires — the view does hand
22
+ -- corpus_anon rows of a table it holds no grant on, which is what the view is
23
+ -- for; the barrier only settles *which* rows.
24
+ CREATE VIEW c_barrier_view.public_documents WITH (security_barrier = true) AS
25
+ SELECT id, title, body
26
+ FROM c_barrier_view.documents
27
+ WHERE visibility = 'public';
28
+ ALTER VIEW c_barrier_view.public_documents OWNER TO c_barrier_view_owner;
29
+ GRANT SELECT ON c_barrier_view.public_documents TO corpus_anon, corpus_user;
@@ -0,0 +1,27 @@
1
+ {
2
+ "title": "A column grant is reach that lives only in pg_attribute.attacl",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_column_grant"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L13",
19
+ "relation": "c_column_grant.profiles",
20
+ "role": "corpus_anon",
21
+ "note": "column-level SELECT on an RLS-off table: every row of handle and display_name, and until this rule existed the audit reported the relation as unreachable"
22
+ }
23
+ ],
24
+ "worstSeverity": "info",
25
+ "fix": "Not a revoke. Either the projection is intended and public — in which case this is correct as written — or the relation needs RLS, because a column grant restricts which columns, never which rows.",
26
+ "id": "35-column-grant-projection"
27
+ }
@@ -0,0 +1,21 @@
1
+ DROP SCHEMA IF EXISTS c_column_grant CASCADE;
2
+ CREATE SCHEMA c_column_grant;
3
+
4
+ GRANT USAGE ON SCHEMA c_column_grant TO corpus_anon, corpus_user;
5
+
6
+ CREATE TABLE c_column_grant.profiles (
7
+ id bigserial PRIMARY KEY,
8
+ handle text NOT NULL,
9
+ display_name text NOT NULL,
10
+ email text NOT NULL,
11
+ password_reset_token text
12
+ );
13
+
14
+ -- The flaw is not the projection — exposing a handle and a display name
15
+ -- anonymously is a reasonable thing to want. The flaw is that this grant is
16
+ -- written to `pg_attribute.attacl` and nothing at all appears in the table's
17
+ -- `relacl`, so every relacl-only rule concludes corpus_anon reaches nothing
18
+ -- here. RLS is off, so the grant reads every row of those columns.
19
+ GRANT SELECT (handle, display_name) ON c_column_grant.profiles TO corpus_anon;
20
+ GRANT SELECT (handle, display_name, email) ON c_column_grant.profiles TO corpus_user;
21
+ GRANT UPDATE (display_name) ON c_column_grant.profiles TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Policy calls a non-LEAKPROOF function",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_policy_fn_leaky"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X4",
19
+ "relation": "c_policy_fn_leaky.invoices",
20
+ "note": "a non-leakproof qual cannot be pushed below a join or subquery scan, so the security filter runs late and on more rows than it needs to"
21
+ }
22
+ ],
23
+ "fix": "Mark c_policy_fn_leaky.tenant_of LEAKPROOF — it raises no error carrying its argument — or inline the cast so the policy calls nothing at all."
24
+ }
@@ -0,0 +1,27 @@
1
+ DROP SCHEMA IF EXISTS c_policy_fn_leaky CASCADE;
2
+ CREATE SCHEMA c_policy_fn_leaky;
3
+ GRANT USAGE ON SCHEMA c_policy_fn_leaky TO corpus_user, corpus_anon;
4
+
5
+ -- IMMUTABLE, so this is not the per-row-evaluation flaw of case 20: the only
6
+ -- thing wrong with it is that nothing has promised it doesn't leak its
7
+ -- argument, and the planner takes that promise seriously.
8
+ CREATE FUNCTION c_policy_fn_leaky.tenant_of(claim text) RETURNS uuid
9
+ LANGUAGE sql IMMUTABLE AS $$
10
+ SELECT nullif(claim, '')::uuid
11
+ $$;
12
+
13
+ CREATE TABLE c_policy_fn_leaky.invoices (
14
+ id bigserial PRIMARY KEY,
15
+ tenant_id uuid NOT NULL,
16
+ amount numeric
17
+ );
18
+ CREATE INDEX invoices_tenant_idx ON c_policy_fn_leaky.invoices (tenant_id);
19
+ ALTER TABLE c_policy_fn_leaky.invoices ENABLE ROW LEVEL SECURITY;
20
+ ALTER TABLE c_policy_fn_leaky.invoices FORCE ROW LEVEL SECURITY;
21
+
22
+ -- Hoisted into an InitPlan and comparing an indexed column: everything else
23
+ -- about this predicate is right. The flaw is the missing LEAKPROOF.
24
+ CREATE POLICY invoices_tenant ON c_policy_fn_leaky.invoices FOR SELECT TO corpus_user
25
+ USING (tenant_id = (SELECT c_policy_fn_leaky.tenant_of(current_setting('jwt.claims.tenant_id', true))));
26
+
27
+ GRANT SELECT ON c_policy_fn_leaky.invoices TO corpus_user;
@@ -0,0 +1,27 @@
1
+ {
2
+ "title": "A column grant under RLS is reported as reach, but as mediated reach",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_column_grant_rls"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L13",
19
+ "relation": "c_column_grant_rls.notes",
20
+ "role": "corpus_anon",
21
+ "note": "policies mediate the rows and the grant narrows the columns — reported because nothing else in the audit sees the grant at all, not because it is wrong"
22
+ }
23
+ ],
24
+ "worstSeverity": "info",
25
+ "fix": "Nothing. This case pins two things: that a mediated column grant is still surfaced, and that corpus_user — which holds SELECT on the whole relation — is not reported a second time for the redundant column grant.",
26
+ "id": "36-column-grant-rls-mediated"
27
+ }
@@ -0,0 +1,32 @@
1
+ DROP SCHEMA IF EXISTS c_column_grant_rls CASCADE;
2
+ CREATE SCHEMA c_column_grant_rls;
3
+
4
+ GRANT USAGE ON SCHEMA c_column_grant_rls TO corpus_anon, corpus_user;
5
+
6
+ CREATE TABLE c_column_grant_rls.notes (
7
+ id bigserial PRIMARY KEY,
8
+ author text NOT NULL,
9
+ title text NOT NULL,
10
+ body text NOT NULL
11
+ );
12
+ ALTER TABLE c_column_grant_rls.notes ENABLE ROW LEVEL SECURITY;
13
+ ALTER TABLE c_column_grant_rls.notes FORCE ROW LEVEL SECURITY;
14
+
15
+ -- The column grant is mediated: only published rows are visible, and the
16
+ -- column list narrows that further. The point of this case is the pairing —
17
+ -- L13 still reports the reach, because nothing else in the audit can see it,
18
+ -- but it reports it as mediated and recommends nothing.
19
+ CREATE POLICY notes_published ON c_column_grant_rls.notes
20
+ FOR SELECT TO corpus_anon
21
+ USING (author = (SELECT current_setting('app.author', true)));
22
+ CREATE INDEX notes_author_idx ON c_column_grant_rls.notes (author);
23
+
24
+ GRANT SELECT (title) ON c_column_grant_rls.notes TO corpus_anon;
25
+
26
+ -- The negative half: corpus_user holds the whole relation, so its column
27
+ -- grant adds no reach and must not be reported twice.
28
+ GRANT SELECT ON c_column_grant_rls.notes TO corpus_user;
29
+ GRANT SELECT (title) ON c_column_grant_rls.notes TO corpus_user;
30
+ CREATE POLICY notes_own ON c_column_grant_rls.notes
31
+ FOR SELECT TO corpus_user
32
+ USING (author = (SELECT current_setting('app.author', true)));
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "A LEAKPROOF policy function is not a planner obstacle",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_policy_fn_leakproof"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [],
17
+ "forbid": [
18
+ "X2",
19
+ "X3",
20
+ "X4",
21
+ "X9"
22
+ ],
23
+ "fix": "Nothing to fix — this is case 35 fixed, and a rule firing here would be telling you to undo the fix."
24
+ }
@@ -0,0 +1,22 @@
1
+ DROP SCHEMA IF EXISTS c_policy_fn_leakproof CASCADE;
2
+ CREATE SCHEMA c_policy_fn_leakproof;
3
+ GRANT USAGE ON SCHEMA c_policy_fn_leakproof TO corpus_user, corpus_anon;
4
+
5
+ CREATE FUNCTION c_policy_fn_leakproof.tenant_of(claim text) RETURNS uuid
6
+ LANGUAGE sql IMMUTABLE LEAKPROOF AS $$
7
+ SELECT nullif(claim, '')::uuid
8
+ $$;
9
+
10
+ CREATE TABLE c_policy_fn_leakproof.invoices (
11
+ id bigserial PRIMARY KEY,
12
+ tenant_id uuid NOT NULL,
13
+ amount numeric
14
+ );
15
+ CREATE INDEX invoices_tenant_idx ON c_policy_fn_leakproof.invoices (tenant_id);
16
+ ALTER TABLE c_policy_fn_leakproof.invoices ENABLE ROW LEVEL SECURITY;
17
+ ALTER TABLE c_policy_fn_leakproof.invoices FORCE ROW LEVEL SECURITY;
18
+
19
+ CREATE POLICY invoices_tenant ON c_policy_fn_leakproof.invoices FOR SELECT TO corpus_user
20
+ USING (tenant_id = (SELECT c_policy_fn_leakproof.tenant_of(current_setting('jwt.claims.tenant_id', true))));
21
+
22
+ GRANT SELECT ON c_policy_fn_leakproof.invoices TO corpus_user;
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Sort-shaped column leading no index",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_sort_no_index"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "X8",
19
+ "relation": "c_sort_no_index.feed_items",
20
+ "note": "every page of a feed ordered by created_at sorts the whole RLS-filtered result; keyset pagination degenerates into scan-and-discard"
21
+ }
22
+ ],
23
+ "fix": "CREATE INDEX ON c_sort_no_index.feed_items (created_at DESC) — or turn X8 off if nothing orders by it, which is why the rule ships as info."
24
+ }
@@ -0,0 +1,14 @@
1
+ DROP SCHEMA IF EXISTS c_sort_no_index CASCADE;
2
+ CREATE SCHEMA c_sort_no_index;
3
+ GRANT USAGE ON SCHEMA c_sort_no_index TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_sort_no_index.feed_items (
6
+ id bigserial PRIMARY KEY,
7
+ body text,
8
+ -- The flaw, such as it is: a timestamp column that leads no index. A
9
+ -- heuristic — nothing here proves anything orders by it — so the rule is
10
+ -- info and costs the score nothing.
11
+ created_at timestamptz NOT NULL DEFAULT now()
12
+ );
13
+
14
+ GRANT SELECT ON c_sort_no_index.feed_items TO corpus_user;
@@ -0,0 +1,30 @@
1
+ {
2
+ "title": "A definer view reads a relation in a schema the audit never introspected",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_unaudited_view"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L14",
19
+ "relation": "c_unaudited_hidden.credentials",
20
+ "role": "corpus_anon",
21
+ "note": "the reach is proven from the view body; nothing on the far end was graded, because c_unaudited_hidden is outside the audited schemas"
22
+ }
23
+ ],
24
+ "forbid": [
25
+ "L8"
26
+ ],
27
+ "worstSeverity": "info",
28
+ "fix": "Bring c_unaudited_hidden into the audit (add it to `schemas`, or drop it from `excludeSchemas`) so the base relation's ACL and policies are graded — then L8 can answer whether this is a bypass. Excluding a schema scopes what is scored, never what is reachable.",
29
+ "id": "37-unaudited-base-relation"
30
+ }
@@ -0,0 +1,45 @@
1
+ DROP SCHEMA IF EXISTS c_unaudited_view CASCADE;
2
+ DROP SCHEMA IF EXISTS c_unaudited_hidden CASCADE;
3
+ CREATE SCHEMA c_unaudited_view;
4
+ CREATE SCHEMA c_unaudited_hidden;
5
+
6
+ DO $$ BEGIN
7
+ CREATE ROLE c_unaudited_owner NOLOGIN;
8
+ EXCEPTION WHEN duplicate_object THEN NULL;
9
+ END $$;
10
+
11
+ GRANT USAGE ON SCHEMA c_unaudited_view TO corpus_anon, corpus_user, c_unaudited_owner;
12
+
13
+ -- Deliberately *not* in the case's exposure surface, so the audit never
14
+ -- introspects it: no ACL, no policy and no owner of this table is ever read.
15
+ CREATE TABLE c_unaudited_hidden.credentials (
16
+ id bigserial PRIMARY KEY,
17
+ subject text NOT NULL,
18
+ secret text NOT NULL
19
+ );
20
+ ALTER TABLE c_unaudited_hidden.credentials OWNER TO c_unaudited_owner;
21
+ ALTER TABLE c_unaudited_hidden.credentials ENABLE ROW LEVEL SECURITY;
22
+ ALTER TABLE c_unaudited_hidden.credentials FORCE ROW LEVEL SECURITY;
23
+
24
+ -- A table in scope, so the case has an audited surface of its own and the
25
+ -- finding below is clearly about the *other* schema.
26
+ CREATE TABLE c_unaudited_view.sessions (
27
+ id bigserial PRIMARY KEY,
28
+ subject text NOT NULL
29
+ );
30
+ ALTER TABLE c_unaudited_view.sessions ENABLE ROW LEVEL SECURITY;
31
+ ALTER TABLE c_unaudited_view.sessions FORCE ROW LEVEL SECURITY;
32
+ CREATE POLICY sessions_own ON c_unaudited_view.sessions
33
+ FOR SELECT TO corpus_user
34
+ USING (subject = (SELECT current_setting('app.subject', true)));
35
+ CREATE INDEX sessions_subject_idx ON c_unaudited_view.sessions (subject);
36
+ GRANT SELECT ON c_unaudited_view.sessions TO corpus_user;
37
+
38
+ -- The flaw: the view is in scope and its body names a relation that is not.
39
+ -- It executes as its owner, so corpus_anon reads a table the audit never
40
+ -- graded — every rule that needs the base relation's ACL or policies drops
41
+ -- the edge, and the view scans clean.
42
+ CREATE VIEW c_unaudited_view.subject_directory AS
43
+ SELECT subject FROM c_unaudited_hidden.credentials;
44
+ ALTER VIEW c_unaudited_view.subject_directory OWNER TO c_unaudited_owner;
45
+ GRANT SELECT ON c_unaudited_view.subject_directory TO corpus_anon, corpus_user;
@@ -0,0 +1,22 @@
1
+ {
2
+ "title": "A foreign key with a covering index is not a finding",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_fk_indexed"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [],
17
+ "forbid": [
18
+ "X1",
19
+ "X5"
20
+ ],
21
+ "fix": "Nothing to fix — this is case 17 fixed. X5 is forbidden too: a single-column index on a foreign key is not made redundant by the primary key it references."
22
+ }
@@ -0,0 +1,17 @@
1
+ DROP SCHEMA IF EXISTS c_fk_indexed CASCADE;
2
+ CREATE SCHEMA c_fk_indexed;
3
+ GRANT USAGE ON SCHEMA c_fk_indexed TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_fk_indexed.authors (
6
+ id bigserial PRIMARY KEY,
7
+ name text NOT NULL
8
+ );
9
+
10
+ CREATE TABLE c_fk_indexed.books (
11
+ id bigserial PRIMARY KEY,
12
+ author_id bigint NOT NULL REFERENCES c_fk_indexed.authors (id) ON DELETE CASCADE,
13
+ title text NOT NULL
14
+ );
15
+ CREATE INDEX books_author_idx ON c_fk_indexed.books (author_id);
16
+
17
+ GRANT SELECT ON c_fk_indexed.authors, c_fk_indexed.books TO corpus_user;
@@ -0,0 +1,30 @@
1
+ {
2
+ "title": "An invoker view over the same out-of-scope table confers nothing, so nothing is reported",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_invoker_unaudited"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "A3",
19
+ "relation": "c_invoker_unaudited.sessions",
20
+ "note": "the only finding left, and it is about the in-scope table: RLS is not FORCEd, so the owner still reads every row"
21
+ }
22
+ ],
23
+ "forbid": [
24
+ "L14",
25
+ "L8"
26
+ ],
27
+ "worstSeverity": "low",
28
+ "fix": "Nothing on the view. This case pins the boundary: L14 reports an out-of-scope *reach*, not an out-of-scope *reference*. `security_invoker` means the caller reads with its own privileges, so no edge crosses the schema boundary and reporting one would be noise on every view that touches a system catalog.",
29
+ "id": "38-invoker-view-unaudited-base"
30
+ }
@@ -0,0 +1,39 @@
1
+ DROP SCHEMA IF EXISTS c_invoker_unaudited CASCADE;
2
+ DROP SCHEMA IF EXISTS c_invoker_hidden CASCADE;
3
+ CREATE SCHEMA c_invoker_unaudited;
4
+ CREATE SCHEMA c_invoker_hidden;
5
+
6
+ DO $$ BEGIN
7
+ CREATE ROLE c_invoker_unaudited_owner NOLOGIN;
8
+ EXCEPTION WHEN duplicate_object THEN NULL;
9
+ END $$;
10
+
11
+ GRANT USAGE ON SCHEMA c_invoker_unaudited TO corpus_anon, corpus_user, c_invoker_unaudited_owner;
12
+
13
+ CREATE TABLE c_invoker_hidden.credentials (
14
+ id bigserial PRIMARY KEY,
15
+ subject text NOT NULL,
16
+ secret text NOT NULL
17
+ );
18
+ ALTER TABLE c_invoker_hidden.credentials OWNER TO c_invoker_unaudited_owner;
19
+
20
+ CREATE TABLE c_invoker_unaudited.sessions (
21
+ id bigserial PRIMARY KEY,
22
+ subject text NOT NULL
23
+ );
24
+ ALTER TABLE c_invoker_unaudited.sessions ENABLE ROW LEVEL SECURITY;
25
+ CREATE POLICY sessions_own ON c_invoker_unaudited.sessions
26
+ FOR SELECT TO corpus_user
27
+ USING (subject = (SELECT current_setting('app.subject', true)));
28
+ CREATE INDEX sessions_subject_idx ON c_invoker_unaudited.sessions (subject);
29
+ GRANT SELECT ON c_invoker_unaudited.sessions TO corpus_user;
30
+
31
+ -- The same cross-schema shape as case 37, with `security_invoker`. The view
32
+ -- confers nothing: corpus_anon reads the hidden table only if it holds its own
33
+ -- grant on it (it does not, and it has no USAGE on the schema either), so
34
+ -- there is no reach to report and L14 must stay silent. An out-of-scope
35
+ -- *reference* is not a finding; an out-of-scope *reach* is.
36
+ CREATE VIEW c_invoker_unaudited.subject_directory WITH (security_invoker = true) AS
37
+ SELECT subject FROM c_invoker_hidden.credentials;
38
+ ALTER VIEW c_invoker_unaudited.subject_directory OWNER TO c_invoker_unaudited_owner;
39
+ GRANT SELECT ON c_invoker_unaudited.subject_directory TO corpus_anon, corpus_user;
@@ -0,0 +1,30 @@
1
+ {
2
+ "title": "A definer view exposing only the columns the caller already holds is not a bypass",
3
+ "dimension": "security",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_view_columns"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [
17
+ {
18
+ "code": "L13",
19
+ "relation": "c_view_columns.people",
20
+ "role": "corpus_anon",
21
+ "note": "the column grant itself is real reach, and L13 reports it — that is the baseline the definer view adds nothing to"
22
+ }
23
+ ],
24
+ "forbid": [
25
+ "L8"
26
+ ],
27
+ "worstSeverity": "info",
28
+ "fix": "Nothing. corpus_anon reads (id, handle) by column grant and the view returns exactly those columns of exactly those rows, so the owner's read launders nothing. Reporting L8 here would recommend rewriting a view that gives away nothing the ACL did not already give away.",
29
+ "id": "39-definer-view-column-subset"
30
+ }
@@ -0,0 +1,29 @@
1
+ DROP SCHEMA IF EXISTS c_view_columns CASCADE;
2
+ CREATE SCHEMA c_view_columns;
3
+
4
+ DO $$ BEGIN
5
+ CREATE ROLE c_view_columns_owner NOLOGIN;
6
+ EXCEPTION WHEN duplicate_object THEN NULL;
7
+ END $$;
8
+
9
+ GRANT USAGE ON SCHEMA c_view_columns TO corpus_anon, corpus_user, c_view_columns_owner;
10
+
11
+ CREATE TABLE c_view_columns.people (
12
+ id bigserial PRIMARY KEY,
13
+ handle text NOT NULL,
14
+ email text NOT NULL,
15
+ ssn text NOT NULL
16
+ );
17
+ ALTER TABLE c_view_columns.people OWNER TO c_view_columns_owner;
18
+
19
+ -- corpus_anon already reads these two columns, by column-level grant. No RLS,
20
+ -- so the owner's read returns exactly the rows corpus_anon's own read would.
21
+ GRANT SELECT (id, handle) ON c_view_columns.people TO corpus_anon;
22
+
23
+ -- A definer view over the *same two columns* launders nothing: every value it
24
+ -- returns was already readable by the caller. L8 must stay silent, which it
25
+ -- can only do by knowing the projection, not just the relation.
26
+ CREATE VIEW c_view_columns.handles AS
27
+ SELECT id, handle FROM c_view_columns.people;
28
+ ALTER VIEW c_view_columns.handles OWNER TO c_view_columns_owner;
29
+ GRANT SELECT ON c_view_columns.handles TO corpus_anon;
@@ -0,0 +1,23 @@
1
+ {
2
+ "title": "A cast in a policy is fine when a matching expression index exists",
3
+ "dimension": "perf",
4
+ "exposure": {
5
+ "schemas": [
6
+ "c_policy_cast_indexed"
7
+ ],
8
+ "roles": [
9
+ "corpus_anon",
10
+ "corpus_user"
11
+ ],
12
+ "anonRoles": [
13
+ "corpus_anon"
14
+ ]
15
+ },
16
+ "expect": [],
17
+ "forbid": [
18
+ "X2",
19
+ "X3",
20
+ "X9"
21
+ ],
22
+ "fix": "Nothing to fix — this is case 19 fixed with an expression index rather than by moving the cast, and X3 must recognize the index it asked for."
23
+ }
@@ -0,0 +1,19 @@
1
+ DROP SCHEMA IF EXISTS c_policy_cast_indexed CASCADE;
2
+ CREATE SCHEMA c_policy_cast_indexed;
3
+ GRANT USAGE ON SCHEMA c_policy_cast_indexed TO corpus_user, corpus_anon;
4
+
5
+ CREATE TABLE c_policy_cast_indexed.accounts (
6
+ id bigserial PRIMARY KEY,
7
+ tenant_id uuid NOT NULL,
8
+ email text
9
+ );
10
+ -- The expression index X3 asks for: it serves the cast comparison below,
11
+ -- which a plain index on tenant_id could not.
12
+ CREATE INDEX accounts_tenant_text_idx ON c_policy_cast_indexed.accounts ((tenant_id::text));
13
+ ALTER TABLE c_policy_cast_indexed.accounts ENABLE ROW LEVEL SECURITY;
14
+ ALTER TABLE c_policy_cast_indexed.accounts FORCE ROW LEVEL SECURITY;
15
+
16
+ CREATE POLICY accounts_tenant ON c_policy_cast_indexed.accounts FOR SELECT TO corpus_user
17
+ USING (tenant_id::text = (SELECT current_setting('jwt.claims.tenant_id', true)));
18
+
19
+ GRANT SELECT ON c_policy_cast_indexed.accounts TO corpus_user;