@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
package/cli/audit.js ADDED
@@ -0,0 +1,560 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const logger_1 = require("@pgpmjs/logger");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const baseline_1 = require("../callgraph/baseline");
40
+ const audit_1 = require("../commands/audit");
41
+ const loader_1 = require("../config/loader");
42
+ const baseline_2 = require("../perf/baseline");
43
+ const compare_1 = require("../report/compare");
44
+ const github_1 = require("../report/github");
45
+ const json_1 = require("../report/json");
46
+ const markdown_1 = require("../report/markdown");
47
+ const pretty_1 = require("../report/pretty");
48
+ const sarif_1 = require("../report/sarif");
49
+ const view_1 = require("../report/view");
50
+ const score_1 = require("../score/score");
51
+ const types_1 = require("../types");
52
+ const shared_1 = require("./shared");
53
+ const log = new logger_1.Logger('safegres');
54
+ /**
55
+ * `--pgpm` mode needs the optional peer dependency `pgsql-test`, so the
56
+ * helper module is loaded lazily with a friendly error when it's missing.
57
+ */
58
+ function importPgpmTest() {
59
+ try {
60
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
61
+ return require('../pgpm-test');
62
+ }
63
+ catch (err) {
64
+ if (err.code === 'MODULE_NOT_FOUND') {
65
+ log.error('--pgpm requires the optional peer dependency "pgsql-test" — install it (e.g. `npm i -D pgsql-test`) and retry');
66
+ process.exit(2);
67
+ }
68
+ throw err;
69
+ }
70
+ }
71
+ const usage = `
72
+ safegres audit — Postgres security and performance auditor
73
+
74
+ safegres audit [OPTIONS]
75
+
76
+ Connection (priority order, top wins):
77
+ --pgpm [dir] No connection needed: deploy the pgpm workspace at
78
+ [dir] (default: nearest from cwd) into an ephemeral
79
+ test database and audit it (requires pgsql-test)
80
+ --connection <url> Full PostgreSQL connection string
81
+ --host <host> PostgreSQL host (else PGHOST, default localhost)
82
+ --port <port> PostgreSQL port (else PGPORT, default 5432)
83
+ --user <user> PostgreSQL user (else PGUSER, default postgres)
84
+ --password <pw> PostgreSQL password (else PGPASSWORD,default password)
85
+ --database <db> PostgreSQL database (else PGDATABASE,default postgres)
86
+
87
+ Configuration:
88
+ --config <path> Explicit config file (else discovered: safegres.config.{ts,js,mjs,cjs},
89
+ .safegresrc{,.json,.yaml,.yml,.js}, safegres.json, package.json "safegres")
90
+ --preset <name> Apply a built-in preset. Posture: recommended, strict, minimal.
91
+ Stack: constructive, postgrest, supabase, graphile, hasura.
92
+ Composable: multi-tenant, oltp (extends: [stack, posture]).
93
+ --rule <CODE=SETTING> Retune a rule (repeatable), e.g. --rule A3=off --rule A5=high
94
+ --sealed Grade under a built-in preset ALONE: no config-file
95
+ discovery, and --config/--rule/--exposure-schemas and
96
+ every baseline flag are refused. For evaluating an
97
+ agent, where editing the rules is the cheapest way to
98
+ win. Pair with --preset; the resolved rule set is
99
+ fingerprinted into report.provenance either way
100
+ --verify-fingerprint <f> Exit non-zero unless the run's configuration
101
+ fingerprint is <f> — the harness's proof that the
102
+ score it is reading was produced under its own rules
103
+
104
+ Exposure (what the score is computed against):
105
+ --exposure-schemas <csv> Declare the API-exposed schemas; findings outside
106
+ them become unscored internal advisories
107
+ --exposed-only Hide internal (non-exposed) findings from output
108
+ --plane <name|glob> Expand a secondary access plane (repeatable; '*' for
109
+ all). Planes are declared in exposure.planes and
110
+ graded separately — the headline score is always the
111
+ primary (API) plane and never moves
112
+
113
+ Performance dimension (optional; scored separately from security):
114
+ --perf Also audit index hygiene (X1/X5/X6/X7/X8), policy-aware
115
+ index coverage (X2/X3/X4) and policy cost (P1/P1b),
116
+ scored on its own 0-100 axis (report.perf)
117
+ --fail-on-perf-score <n> Exit non-zero if the perf score is below n (0-100)
118
+ --fail-on-perf-grade <g> Exit non-zero if the perf grade is below g (A+|A|B|C|D)
119
+ --write-perf-baseline <file>
120
+ Snapshot today's perf findings to <file> as accepted
121
+ debt (implies --perf)
122
+ --perf-baseline <file> Diff perf findings against a committed baseline and
123
+ report only NEW debt (implies --perf)
124
+ --fail-on-new-perf Exit non-zero when --perf-baseline finds new debt
125
+ --stats Also run the runtime-statistics rules (S1-S4) from
126
+ pg_stat_user_tables and, when installed,
127
+ pg_stat_statements (implies --perf). Workload-
128
+ dependent: only meaningful against a database that
129
+ has served representative traffic
130
+ --explain Prove each probeable perf finding with
131
+ EXPLAIN (GENERIC_PLAN) and attach the plan as
132
+ evidence; findings the planner refutes are reported
133
+ but unscored (implies --perf, needs PostgreSQL 16+)
134
+
135
+ Comparison with a previous run (what changed, not just what is):
136
+ --compare <file> Diff scores, severity counts and per-rule counts
137
+ against a previous run and render the delta.
138
+ <file> is any earlier --format json output, or a
139
+ snapshot written by --write-snapshot
140
+ --compare-ref <label> How to name the previous run in the report
141
+ (e.g. "main", a commit sha). Default "previous run"
142
+ --compare-sha <sha> Commit the previous run was produced from
143
+ --compare-run-id <id> CI run that produced it
144
+ --compare-run-url <url> Link to that run
145
+ --compare-age <age> How old it is, pre-formatted ("10.2 hours").
146
+ Defaults to the age of its generatedAt
147
+ --compare-skipped <why> There is no baseline and this is the reason, shown
148
+ in place of the delta so an absent delta does not
149
+ read as "nothing changed"
150
+ --write-snapshot <file> Write the aggregate slice of this run (scores,
151
+ counts, per-rule counts) for a later --compare,
152
+ when keeping the whole report is too much
153
+
154
+ Call graph (unscored; human review):
155
+ --call-graph Analyze the functions reachable from the exposed entry
156
+ points and list trust boundaries: SECURITY DEFINER hops,
157
+ RLS-bypass paths, auth-context mutations, internal-table
158
+ reach, and opaque (dynamic SQL) nodes
159
+ --write-baseline <file> Snapshot the call-graph boundaries to <file>
160
+ (implies --call-graph)
161
+ --baseline <file> Diff the call graph against a committed snapshot and
162
+ report NEW trust boundaries (implies --call-graph)
163
+ --fail-on-new-boundaries Exit non-zero when --baseline finds new boundaries
164
+
165
+ Audit options:
166
+ --schemas <csv> Limit to these schemas (default: all non-system)
167
+ --exclude-schemas <csv> Skip these schemas
168
+ --ignore-extensions <csv> Skip everything in these extensions' schemas, for
169
+ objects an extension creates without registering a
170
+ dependency (e.g. pg_partman's child partitions)
171
+ --audit-extension-owned Audit relations owned by an installed extension too
172
+ (skipped by default — they are not yours to alter)
173
+ --roles <csv> Audit grants only for these roles (default: all)
174
+ --exclude-roles <csv> Skip grants for these roles
175
+ --format <fmt> "pretty" (default) | "json" | "json-pretty" | "markdown" | "sarif"
176
+ (markdown is for CI: a GitHub job summary or PR comment;
177
+ sarif uploads to GitHub code scanning)
178
+ --sarif-sources <dir> With --format sarif: scan <dir> for the CREATE TABLE /
179
+ CREATE POLICY that defines each object, so alerts point
180
+ at the SQL that produced the finding
181
+ --out <dir> Write safegres.json, safegres.md and safegres.sarif
182
+ into <dir> (created as needed)
183
+ --write-json <file> Also write the JSON report to <file>
184
+ --write-markdown <file> Also write the markdown report to <file>
185
+ --write-sarif <file> Also write the SARIF report to <file>
186
+ (one audit, as many outputs as CI needs)
187
+ --summary, -q Print only exposure, score, and severity counts (no findings)
188
+ --verbose Expand internal (non-exposed) advisories (listed as a count otherwise)
189
+ --fail-on <severity> Exit non-zero if any finding >= severity
190
+ (critical|high|medium|low|info; default: none)
191
+ --fail-on-score <n> Exit non-zero if the score is below n (0-100)
192
+ --fail-on-grade <g> Exit non-zero if the grade is below g (A+|A|B|C|D)
193
+ --report-only Evaluate and report every gate, then exit 0 anyway —
194
+ the way to run a gated config as an advisory job
195
+ --skip-ast Skip AST-level anti-pattern checks (faster)
196
+ --no-color Disable ANSI colors in pretty output
197
+ --help, -h Show this help message
198
+
199
+ GitHub Actions:
200
+ --github Write the job summary to $GITHUB_STEP_SUMMARY and
201
+ emit workflow annotations (auto-detected when
202
+ GITHUB_ACTIONS=true). Configure what appears with
203
+ report.github in the config file
204
+ --github-comment Upsert the sticky PR comment (needs GITHUB_TOKEN)
205
+ --write-github-comment <file>
206
+ Write the PR-comment markdown to <file> instead of
207
+ posting it
208
+
209
+ Everything a CI job repeats every run belongs in the config file instead, so
210
+ the job is just \`safegres audit\` (paths are relative to the config file):
211
+
212
+ {
213
+ "extends": "safegres:constructive",
214
+ "source": { "pgpm": "application/app" },
215
+ "callGraph": { "enabled": true },
216
+ "perf": { "enabled": true, "baseline": "ci/perf-baseline.json", "failOnNew": true },
217
+ "outputs": { "dir": "safegres-reports" },
218
+ "failOn": { "grade": "B" }
219
+ }
220
+ `;
221
+ exports.default = async (argv, _prompter, _options) => {
222
+ if (argv.help || argv.h) {
223
+ process.stdout.write(usage);
224
+ return;
225
+ }
226
+ // minimist parses `--no-color` as `color: false`.
227
+ const colorEnabled = argv.color !== false;
228
+ const pgpmCwd = typeof argv.pgpm === 'string' ? argv.pgpm : undefined;
229
+ const sealed = argv.sealed === true;
230
+ if (sealed) {
231
+ // Everything below is a way to change the score without changing the
232
+ // database. A sealed run refuses them outright rather than ignoring them:
233
+ // an evaluation that silently dropped the flag it was handed would be
234
+ // reporting a number for rules nobody chose.
235
+ const banned = [
236
+ 'config',
237
+ 'rule',
238
+ 'baseline',
239
+ 'write-baseline',
240
+ 'perf-baseline',
241
+ 'write-perf-baseline',
242
+ 'exposure-schemas'
243
+ ].filter((flag) => argv[flag] !== undefined);
244
+ if (banned.length > 0) {
245
+ log.error(`--sealed refuses local configuration: remove ${banned.map((f) => `--${f}`).join(', ')}`);
246
+ process.exit(2);
247
+ }
248
+ }
249
+ const loaded = (0, loader_1.loadConfig)({ cwd: pgpmCwd, ...(0, shared_1.configParamsFromArgv)(argv) });
250
+ const config = loaded.config;
251
+ const { pgpm: pgpmSource, usePgpm, perfBaseline, callGraphBaseline, outputs } = (0, shared_1.resolveRunPaths)(argv, config, (0, loader_1.configPathBase)(loaded));
252
+ const exposureSchemas = (0, shared_1.csvList)(argv['exposure-schemas']);
253
+ const auditOptions = {
254
+ schemas: (0, shared_1.csvList)(argv.schemas),
255
+ excludeSchemas: (0, shared_1.csvList)(argv['exclude-schemas']),
256
+ extensions: (0, shared_1.extensionScopeFromArgv)(argv, config),
257
+ includeRoles: (0, shared_1.csvList)(argv.roles),
258
+ excludeRoles: (0, shared_1.csvList)(argv['exclude-roles']),
259
+ skipAstChecks: argv['skip-ast'] === true,
260
+ perf: argv.perf === true
261
+ || perfBaseline !== undefined
262
+ || typeof argv['write-perf-baseline'] === 'string'
263
+ ? true
264
+ : undefined,
265
+ stats: argv.stats === true ? true : undefined,
266
+ explain: argv.explain === true ? true : undefined,
267
+ callGraph: argv['call-graph'] === true
268
+ || config.callGraph?.enabled === true
269
+ || callGraphBaseline !== undefined
270
+ || typeof argv['write-baseline'] === 'string',
271
+ exposure: exposureSchemas
272
+ ? { ...config.exposure, schemas: exposureSchemas }
273
+ : undefined,
274
+ config,
275
+ sealed,
276
+ ...(typeof argv.preset === 'string' ? { preset: argv.preset } : {})
277
+ };
278
+ let report;
279
+ if (usePgpm) {
280
+ const { auditPgpmWorkspace } = importPgpmTest();
281
+ report = await auditPgpmWorkspace({ ...auditOptions, cwd: pgpmSource });
282
+ }
283
+ else {
284
+ const client = (0, shared_1.buildClient)(argv);
285
+ await client.connect();
286
+ try {
287
+ report = await (0, audit_1.audit)(client, auditOptions);
288
+ }
289
+ finally {
290
+ await client.end();
291
+ }
292
+ }
293
+ if (typeof argv['write-baseline'] === 'string' && report.callGraph) {
294
+ fs.writeFileSync(argv['write-baseline'], (0, baseline_1.serializeBaseline)((0, baseline_1.toBaseline)(report.callGraph)));
295
+ log.info(`wrote call-graph baseline: ${argv['write-baseline']}`);
296
+ }
297
+ if (typeof argv['write-perf-baseline'] === 'string' && report.perf) {
298
+ fs.writeFileSync(argv['write-perf-baseline'], (0, baseline_2.serializePerfBaseline)((0, baseline_2.toPerfBaseline)(report.perf.findings)));
299
+ log.info(`wrote perf baseline: ${argv['write-perf-baseline']}`);
300
+ }
301
+ if (perfBaseline !== undefined && report.perf) {
302
+ let raw;
303
+ try {
304
+ raw = fs.readFileSync(perfBaseline, 'utf8');
305
+ }
306
+ catch {
307
+ log.error(`cannot read perf baseline: ${perfBaseline} (create one with --write-perf-baseline)`);
308
+ process.exit(2);
309
+ }
310
+ report.perf.diff = (0, baseline_2.diffPerf)(report.perf.findings, (0, baseline_2.parsePerfBaseline)(raw));
311
+ }
312
+ if (outputs.snapshot !== undefined) {
313
+ writeOut(outputs.snapshot, (0, compare_1.serializeSnapshot)((0, compare_1.toSnapshot)(report, typeof argv['compare-ref'] === 'string' ? { ref: argv['compare-ref'] } : {})));
314
+ log.info(`wrote snapshot: ${outputs.snapshot}`);
315
+ }
316
+ if (typeof argv.compare === 'string') {
317
+ let raw;
318
+ try {
319
+ raw = fs.readFileSync(argv.compare, 'utf8');
320
+ }
321
+ catch {
322
+ log.error(`cannot read --compare file: ${argv.compare} `
323
+ + '(any earlier `--format json` report or `--write-snapshot` output works)');
324
+ process.exit(2);
325
+ }
326
+ let previous;
327
+ try {
328
+ previous = (0, compare_1.parseSnapshot)(raw);
329
+ }
330
+ catch (err) {
331
+ log.error(`--compare file is not a safegres report: ${err.message}`);
332
+ process.exit(2);
333
+ }
334
+ if (typeof argv['compare-ref'] === 'string')
335
+ previous.ref = argv['compare-ref'];
336
+ const provenance = baselineProvenance(argv);
337
+ if (provenance)
338
+ previous.provenance = { ...previous.provenance, ...provenance };
339
+ report.comparison = (0, compare_1.compareReports)(previous, report);
340
+ }
341
+ else if (typeof argv['compare-skipped'] === 'string' && argv['compare-skipped'] !== '') {
342
+ report.comparisonSkipped = argv['compare-skipped'];
343
+ }
344
+ if (callGraphBaseline !== undefined && report.callGraph) {
345
+ let raw;
346
+ try {
347
+ raw = fs.readFileSync(callGraphBaseline, 'utf8');
348
+ }
349
+ catch {
350
+ log.error(`cannot read call-graph baseline: ${callGraphBaseline} (create one with --write-baseline)`);
351
+ process.exit(2);
352
+ }
353
+ report.callGraphDiff = (0, baseline_1.diffCallGraph)(report.callGraph, (0, baseline_1.parseBaseline)(raw));
354
+ }
355
+ if (argv['exposed-only'] === true) {
356
+ report.findings = report.findings.filter((f) => f.exposed !== false);
357
+ report.summary = (0, types_1.summarize)(report.findings);
358
+ }
359
+ const planeSelectors = listArg(argv.plane);
360
+ const viewConfig = {
361
+ ...(0, view_1.viewConfigFromReportConfig)(config.report),
362
+ ...(planeSelectors.length > 0 ? { planes: planeSelectors } : {}),
363
+ ...(argv['exposed-only'] === true ? { exposedOnly: true } : {})
364
+ };
365
+ const fmt = typeof argv.format === 'string' ? argv.format : 'pretty';
366
+ let output;
367
+ switch (fmt) {
368
+ case 'json':
369
+ output = (0, json_1.renderJson)(report);
370
+ break;
371
+ case 'json-pretty':
372
+ output = (0, json_1.renderJson)(report, { pretty: true });
373
+ break;
374
+ case 'sarif':
375
+ output = (0, sarif_1.renderSarif)(report, {
376
+ sources: outputs.sarifSources ? (0, sarif_1.buildSourceIndex)(outputs.sarifSources) : undefined
377
+ });
378
+ break;
379
+ case 'markdown':
380
+ case 'md':
381
+ output = (0, markdown_1.renderMarkdown)(report, {
382
+ summary: argv.summary === true,
383
+ verbose: argv.verbose === true,
384
+ view: viewConfig
385
+ });
386
+ break;
387
+ case 'pretty':
388
+ output = (0, pretty_1.renderPretty)(report, {
389
+ color: colorEnabled,
390
+ summary: argv.summary === true,
391
+ verbose: argv.verbose === true,
392
+ view: viewConfig
393
+ });
394
+ break;
395
+ default:
396
+ log.error(`Unknown --format: ${fmt}`);
397
+ process.exit(2);
398
+ }
399
+ process.stdout.write(output);
400
+ process.stdout.write('\n');
401
+ // One audit, as many renderings as CI asked for.
402
+ if (outputs.json !== undefined) {
403
+ writeOut(outputs.json, `${(0, json_1.renderJson)(report, { pretty: true })}\n`);
404
+ log.info(`wrote json report: ${outputs.json}`);
405
+ }
406
+ if (outputs.markdown !== undefined) {
407
+ writeOut(outputs.markdown, `${(0, markdown_1.renderMarkdown)(report, { verbose: argv.verbose === true, view: viewConfig })}\n`);
408
+ log.info(`wrote markdown report: ${outputs.markdown}`);
409
+ }
410
+ if (outputs.sarif !== undefined) {
411
+ writeOut(outputs.sarif, `${(0, sarif_1.renderSarif)(report, {
412
+ sources: outputs.sarifSources ? (0, sarif_1.buildSourceIndex)(outputs.sarifSources) : undefined
413
+ })}\n`);
414
+ log.info(`wrote sarif report: ${outputs.sarif}`);
415
+ }
416
+ // Gates are evaluated before anything exits, so the GitHub renderers can
417
+ // annotate exactly the findings that failed the build.
418
+ let failed = false;
419
+ const gateFailures = [];
420
+ const fail = (message) => {
421
+ log.error(message);
422
+ failed = true;
423
+ };
424
+ // Checked first: if the ruler is wrong, nothing measured with it is worth
425
+ // gating on.
426
+ const expected = argv['verify-fingerprint'];
427
+ if (typeof expected === 'string' && report.provenance?.fingerprint !== expected) {
428
+ fail(`configuration fingerprint ${report.provenance?.fingerprint ?? 'unknown'} `
429
+ + `does not match --verify-fingerprint ${expected}`);
430
+ }
431
+ const failOnSeverity = typeof argv['fail-on'] === 'string' ? argv['fail-on'] : config.failOn?.severity;
432
+ if (failOnSeverity) {
433
+ if (!(failOnSeverity in types_1.SEVERITY_ORDER)) {
434
+ log.error(`Unknown --fail-on severity: ${failOnSeverity}`);
435
+ process.exit(2);
436
+ }
437
+ const over = report.findings.filter((f) => (0, types_1.meetsThreshold)(f.severity, failOnSeverity));
438
+ if (over.length > 0) {
439
+ gateFailures.push(...over);
440
+ fail(`${over.length} finding(s) at or above --fail-on ${failOnSeverity}`);
441
+ }
442
+ }
443
+ const failOnScore = typeof argv['fail-on-score'] === 'number' ? argv['fail-on-score'] : config.failOn?.score;
444
+ if (failOnScore != null && report.score && report.score.value < failOnScore) {
445
+ fail(`score ${report.score.value} is below --fail-on-score ${failOnScore}`);
446
+ }
447
+ const failOnGrade = typeof argv['fail-on-grade'] === 'string' ? argv['fail-on-grade'] : config.failOn?.grade;
448
+ if (failOnGrade && report.score && !(0, score_1.meetsGrade)(report.score.grade, failOnGrade)) {
449
+ fail(`grade ${report.score.grade} is below --fail-on-grade ${failOnGrade}`);
450
+ }
451
+ const failOnPerfScore = typeof argv['fail-on-perf-score'] === 'number'
452
+ ? argv['fail-on-perf-score']
453
+ : config.failOn?.perfScore;
454
+ if (failOnPerfScore != null && report.perf && report.perf.score.value < failOnPerfScore) {
455
+ fail(`perf score ${report.perf.score.value} is below --fail-on-perf-score ${failOnPerfScore}`);
456
+ }
457
+ const failOnPerfGrade = typeof argv['fail-on-perf-grade'] === 'string'
458
+ ? argv['fail-on-perf-grade']
459
+ : config.failOn?.perfGrade;
460
+ if (failOnPerfGrade && report.perf && !(0, score_1.meetsGrade)(report.perf.score.grade, failOnPerfGrade)) {
461
+ fail(`perf grade ${report.perf.score.grade} is below --fail-on-perf-grade ${failOnPerfGrade}`);
462
+ }
463
+ // Per-plane gates. A secondary plane gates nothing unless the config asks:
464
+ // the direct-connection surface is legitimately worse than the API's, and a
465
+ // parity gate would only get the plane deleted.
466
+ for (const [pattern, gate] of Object.entries(config.failOn?.planes ?? {})) {
467
+ const planes = (report.planes ?? []).filter((p) => !p.skipped && (0, view_1.matchPlane)(pattern, p.name));
468
+ for (const plane of planes) {
469
+ if (gate.score != null && plane.score.value < gate.score) {
470
+ fail(`plane ${plane.name} score ${plane.score.value} is below failOn.planes["${pattern}"].score ${gate.score}`);
471
+ }
472
+ if (gate.grade && !(0, score_1.meetsGrade)(plane.score.grade, gate.grade)) {
473
+ fail(`plane ${plane.name} grade ${plane.score.grade} is below failOn.planes["${pattern}"].grade ${gate.grade}`);
474
+ }
475
+ }
476
+ }
477
+ // Per-scorecard gates. The point of a scorecard is that a team gates on
478
+ // its own question, so this is the gate that matters to them and the
479
+ // headline is someone else's number.
480
+ for (const [pattern, gate] of Object.entries(config.failOn?.scorecards ?? {})) {
481
+ const cards = (report.scorecards ?? []).filter((c) => (0, view_1.matchPlane)(pattern, c.name));
482
+ for (const card of cards) {
483
+ if (gate.score != null && card.score.value < gate.score) {
484
+ fail(`scorecard ${card.name} score ${card.score.value} is below failOn.scorecards["${pattern}"].score ${gate.score}`);
485
+ }
486
+ if (gate.grade && !(0, score_1.meetsGrade)(card.score.grade, gate.grade)) {
487
+ fail(`scorecard ${card.name} grade ${card.score.grade} is below failOn.scorecards["${pattern}"].grade ${gate.grade}`);
488
+ }
489
+ }
490
+ }
491
+ const failOnNewPerf = argv['fail-on-new-perf'] === true || config.perf?.failOnNew === true;
492
+ if (failOnNewPerf && report.perf?.diff && report.perf.diff.added.length > 0) {
493
+ const count = report.perf.diff.added.length;
494
+ gateFailures.push(...report.perf.diff.added);
495
+ fail(`${count} new performance finding${count === 1 ? '' : 's'} since the perf baseline — fix them, or re-baseline with --write-perf-baseline to accept`);
496
+ }
497
+ if ((argv['fail-on-new-boundaries'] === true || config.callGraph?.failOnNew === true)
498
+ && report.callGraphDiff
499
+ && report.callGraphDiff.added.length > 0) {
500
+ fail(`${report.callGraphDiff.added.length} new trust boundar${report.callGraphDiff.added.length === 1 ? 'y' : 'ies'} since the baseline — review and re-baseline to accept`);
501
+ }
502
+ // GitHub output: on by default inside Actions, because a report nobody has
503
+ // to wire up is a report people actually read.
504
+ const githubConfig = config.report?.github;
505
+ const githubMode = argv.github === true || (argv.github !== false && process.env.GITHUB_ACTIONS === 'true');
506
+ const githubOptions = {
507
+ ...(githubConfig ? { config: githubConfig } : {}),
508
+ gateFailures,
509
+ view: viewConfig
510
+ };
511
+ if (githubMode) {
512
+ if (!(0, github_1.emitGithub)(report, githubOptions)) {
513
+ log.warn('--github: no $GITHUB_STEP_SUMMARY in the environment — summary not written');
514
+ process.stdout.write(`${(0, github_1.renderGithubSummary)(report, githubOptions)}\n`);
515
+ }
516
+ }
517
+ if (outputs.githubComment !== undefined) {
518
+ writeOut(outputs.githubComment, `${(0, github_1.renderGithubComment)(report, githubOptions)}\n`);
519
+ log.info(`wrote github comment: ${outputs.githubComment}`);
520
+ }
521
+ if (argv['github-comment'] === true) {
522
+ const result = await (0, github_1.postStickyComment)((0, github_1.renderGithubComment)(report, githubOptions));
523
+ if (result.posted)
524
+ log.info('updated the safegres PR comment');
525
+ else
526
+ log.warn(`--github-comment: not posted — ${result.reason}`);
527
+ }
528
+ if (failed && argv['report-only'] !== true)
529
+ process.exit(1);
530
+ if (failed)
531
+ log.warn('--report-only: gates failed, exiting 0');
532
+ };
533
+ /**
534
+ * Where the baseline came from, as the provider reported it. A provider is
535
+ * whatever chose the file — a CI adapter, a script, a person — so every field
536
+ * is optional and none of them mean anything to safegres beyond being printed.
537
+ */
538
+ function baselineProvenance(argv) {
539
+ const str = (flag) => typeof argv[flag] === 'string' && argv[flag] !== '' ? argv[flag] : undefined;
540
+ const provenance = {
541
+ ...(str('compare-sha') && { sha: str('compare-sha') }),
542
+ ...(str('compare-run-id') && { runId: str('compare-run-id') }),
543
+ ...(str('compare-run-url') && { runUrl: str('compare-run-url') }),
544
+ ...(str('compare-age') && { age: str('compare-age') })
545
+ };
546
+ return Object.keys(provenance).length > 0 ? provenance : undefined;
547
+ }
548
+ /** Write an output file, creating its directory: CI should not have to mkdir. */
549
+ function writeOut(file, contents) {
550
+ fs.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
551
+ fs.writeFileSync(file, contents);
552
+ }
553
+ /** A repeatable flag: `--plane a --plane b` (minimist gives an array). */
554
+ function listArg(value) {
555
+ if (typeof value === 'string')
556
+ return [value];
557
+ if (Array.isArray(value))
558
+ return value.filter((v) => typeof v === 'string');
559
+ return [];
560
+ }
@@ -0,0 +1,3 @@
1
+ import { CLIOptions, Inquirerer, ParsedArgs } from 'inquirerer';
2
+ declare const _default: (argv: ParsedArgs, _prompter: Inquirerer, _options: CLIOptions) => Promise<void>;
3
+ export default _default;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const logger_1 = require("@pgpmjs/logger");
4
+ const fs_1 = require("fs");
5
+ const baseline_1 = require("../ci/baseline");
6
+ const github_1 = require("../ci/github");
7
+ const log = new logger_1.Logger('safegres');
8
+ const usage = `
9
+ safegres baseline — choose the report a delta is measured against
10
+
11
+ safegres baseline --provider github [OPTIONS]
12
+
13
+ Finds the base branch's most recent run that is a sane comparison — succeeded,
14
+ young enough, and audited a commit in this branch's merge-base history — and
15
+ downloads its report. When nothing qualifies it says why and exits 0: the audit
16
+ then runs with no deltas, and the absolute gates still apply.
17
+
18
+ The result is written to $GITHUB_OUTPUT when the runner provides one, as
19
+ compare / compare-ref / compare-sha / compare-run-id / compare-run-url /
20
+ compare-age / compare-skipped — the same names \`safegres audit\` takes as
21
+ flags, so the next step is \`--compare "\${{ steps.<id>.outputs.compare }}"\`.
22
+
23
+ Options:
24
+ --provider <name> Where the runs live. Only "github" today
25
+ --repo <owner/repo> Default $GITHUB_REPOSITORY
26
+ --base <ref> Base branch whose runs are candidates
27
+ (default: the pull request's base)
28
+ --head <sha> Branch tip to take the merge base of
29
+ (default: the pull request's head)
30
+ --workflow <file> Workflow file that produced the reports
31
+ (default: the workflow this runs in)
32
+ --artifact <name> Artifact holding safegres.json
33
+ (default: safegres-reports)
34
+ --dir <dir> Where to unpack it (default: previous)
35
+ --max-age-hours <n> Staleness limit (default: ${baseline_1.MAX_BASELINE_AGE_MS / 3600000})
36
+ --candidates <n> Runs to consider (default: ${baseline_1.CANDIDATE_LIMIT})
37
+ --json Print the selection as JSON
38
+ --help, -h Show this help message
39
+
40
+ Needs \`permissions: actions: read\` to list runs and download artifacts.
41
+ `;
42
+ /** `key=value` into a GitHub Actions file, or stdout when running locally. */
43
+ function emit(file, key, value) {
44
+ const line = `${key}=${value}`;
45
+ if (file)
46
+ (0, fs_1.appendFileSync)(file, `${line}\n`);
47
+ else
48
+ process.stdout.write(`(${key}) ${value}\n`);
49
+ }
50
+ function publish(result) {
51
+ const file = process.env.GITHUB_OUTPUT;
52
+ if (result.chosen) {
53
+ emit(file, 'compare', result.chosen.path);
54
+ emit(file, 'compare-ref', result.base);
55
+ emit(file, 'compare-sha', result.chosen.headSha);
56
+ emit(file, 'compare-run-id', result.chosen.runId);
57
+ if (result.runUrl)
58
+ emit(file, 'compare-run-url', result.runUrl);
59
+ if (result.age)
60
+ emit(file, 'compare-age', result.age);
61
+ }
62
+ else {
63
+ emit(file, 'compare-skipped', result.reason ?? 'no baseline');
64
+ }
65
+ }
66
+ exports.default = async (argv, _prompter, _options) => {
67
+ if (argv.help || argv.h) {
68
+ process.stdout.write(usage);
69
+ return;
70
+ }
71
+ const provider = typeof argv.provider === 'string' ? argv.provider : 'github';
72
+ if (provider !== 'github') {
73
+ log.error(`unknown --provider ${provider} (only "github" is implemented)`);
74
+ process.exit(2);
75
+ }
76
+ const hours = Number(argv['max-age-hours']);
77
+ const candidates = Number(argv.candidates);
78
+ let result;
79
+ try {
80
+ result = (0, github_1.selectGithubBaseline)({
81
+ ...(typeof argv.repo === 'string' && { repo: argv.repo }),
82
+ ...(typeof argv.base === 'string' && { base: argv.base }),
83
+ ...(typeof argv.head === 'string' && { headSha: argv.head }),
84
+ ...(typeof argv.workflow === 'string' && { workflow: argv.workflow }),
85
+ ...(typeof argv.artifact === 'string' && { artifact: argv.artifact }),
86
+ ...(typeof argv.dir === 'string' && { dir: argv.dir }),
87
+ ...(Number.isFinite(hours) && hours > 0 && { maxAgeMs: hours * 3600000 }),
88
+ ...(Number.isFinite(candidates) && candidates > 0 && { candidateLimit: candidates })
89
+ });
90
+ }
91
+ catch (err) {
92
+ // Fail-soft, loudly: a missing PR context, a token without `actions: read`
93
+ // or a `gh` that is not installed must not fail an audit that is perfectly
94
+ // able to run without deltas — but the reason travels into the report as
95
+ // the skip message rather than being swallowed.
96
+ const message = err.message.trim();
97
+ log.error(`no comparison baseline: ${message}`);
98
+ publish({ chosen: null, rejected: [], reason: message, base: '', mergeBaseSha: '' });
99
+ return;
100
+ }
101
+ publish(result);
102
+ if (argv.json === true)
103
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
104
+ };
@@ -0,0 +1,2 @@
1
+ import { CLIOptions, Inquirerer, ParsedArgs } from 'inquirerer';
2
+ export declare const commands: (argv: ParsedArgs, prompter: Inquirerer, options: CLIOptions) => Promise<void>;