pr-checkmate 3.0.4 → 3.2.0

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 (384) hide show
  1. package/dist/{cli.js → cjs/cli.js} +30 -30
  2. package/dist/{config → cjs/config}/index.d.ts +2 -2
  3. package/dist/{config → cjs/config}/index.js +8 -7
  4. package/dist/{config → cjs/config}/loader.d.ts +1 -1
  5. package/dist/{config → cjs/config}/loader.js +8 -8
  6. package/dist/{core → cjs/core}/checks/dependencies/banned-imports.d.ts +1 -1
  7. package/dist/{core → cjs/core}/checks/dependencies/banned-imports.js +10 -10
  8. package/dist/{core → cjs/core}/checks/dependencies/circular-deps.d.ts +1 -1
  9. package/dist/{core → cjs/core}/checks/dependencies/circular-deps.js +27 -27
  10. package/dist/{core → cjs/core}/checks/dependencies/dependency-check.d.ts +1 -1
  11. package/dist/{core → cjs/core}/checks/dependencies/dependency-check.js +17 -17
  12. package/dist/{core → cjs/core}/checks/dependencies/grype-scan.d.ts +1 -1
  13. package/dist/{core → cjs/core}/checks/dependencies/grype-scan.js +18 -18
  14. package/dist/{core → cjs/core}/checks/dependencies/license-check.d.ts +1 -1
  15. package/dist/{core → cjs/core}/checks/dependencies/license-check.js +12 -12
  16. package/dist/{core → cjs/core}/checks/dependencies/npm-audit.d.ts +1 -1
  17. package/dist/{core → cjs/core}/checks/dependencies/npm-audit.js +14 -14
  18. package/dist/{core → cjs/core}/checks/dependencies/outdated-deps.d.ts +1 -1
  19. package/dist/{core → cjs/core}/checks/dependencies/outdated-deps.js +14 -14
  20. package/dist/{core → cjs/core}/checks/dependencies/vuln-scan.d.ts +1 -1
  21. package/dist/{core → cjs/core}/checks/dependencies/vuln-scan.js +14 -14
  22. package/dist/{core → cjs/core}/checks/findings.d.ts +1 -1
  23. package/dist/{core → cjs/core}/checks/findings.js +16 -16
  24. package/dist/{core → cjs/core}/checks/git/custom-rules.d.ts +1 -1
  25. package/dist/{core → cjs/core}/checks/git/custom-rules.js +19 -19
  26. package/dist/{core → cjs/core}/checks/git/diff-smell.d.ts +2 -2
  27. package/dist/{core → cjs/core}/checks/git/diff-smell.js +13 -13
  28. package/dist/{core → cjs/core}/checks/git/large-files.d.ts +1 -1
  29. package/dist/{core → cjs/core}/checks/git/large-files.js +15 -15
  30. package/dist/{core → cjs/core}/checks/git/leftover-debug.d.ts +1 -1
  31. package/dist/{core → cjs/core}/checks/git/leftover-debug.js +13 -13
  32. package/dist/{core → cjs/core}/checks/git/lockfile-drift.d.ts +1 -1
  33. package/dist/{core → cjs/core}/checks/git/lockfile-drift.js +12 -12
  34. package/dist/{core → cjs/core}/checks/git/merge-conflict.d.ts +1 -1
  35. package/dist/{core → cjs/core}/checks/git/merge-conflict.js +15 -15
  36. package/dist/{core → cjs/core}/checks/git/missing-tests.d.ts +1 -1
  37. package/dist/{core → cjs/core}/checks/git/missing-tests.js +15 -15
  38. package/dist/{core → cjs/core}/checks/git/todo-fixme.d.ts +1 -1
  39. package/dist/{core → cjs/core}/checks/git/todo-fixme.js +11 -11
  40. package/dist/{core → cjs/core}/checks/languages/cpp-format.d.ts +1 -1
  41. package/dist/{core → cjs/core}/checks/languages/cpp-format.js +18 -18
  42. package/dist/{core → cjs/core}/checks/languages/external-checks.d.ts +9 -9
  43. package/dist/{core → cjs/core}/checks/languages/external-checks.js +10 -10
  44. package/dist/{core → cjs/core}/checks/languages/external-runner.d.ts +2 -2
  45. package/dist/{core → cjs/core}/checks/languages/external-runner.js +21 -21
  46. package/dist/{core → cjs/core}/checks/languages/go-format.d.ts +1 -1
  47. package/dist/{core → cjs/core}/checks/languages/go-format.js +21 -21
  48. package/dist/cjs/core/checks/languages/lint.d.ts +2 -0
  49. package/dist/{core → cjs/core}/checks/languages/lint.js +21 -21
  50. package/dist/{core → cjs/core}/checks/languages/prettier.d.ts +1 -1
  51. package/dist/{core → cjs/core}/checks/languages/prettier.js +18 -18
  52. package/dist/{core → cjs/core}/checks/languages/python-format.d.ts +1 -1
  53. package/dist/{core → cjs/core}/checks/languages/python-format.js +14 -14
  54. package/dist/{core → cjs/core}/checks/languages/python-lint.d.ts +1 -1
  55. package/dist/{core → cjs/core}/checks/languages/python-lint.js +13 -13
  56. package/dist/{core → cjs/core}/checks/languages/python-typecheck.d.ts +1 -1
  57. package/dist/{core → cjs/core}/checks/languages/python-typecheck.js +29 -29
  58. package/dist/{core → cjs/core}/checks/languages/typecheck.d.ts +1 -1
  59. package/dist/{core → cjs/core}/checks/languages/typecheck.js +13 -13
  60. package/dist/{core → cjs/core}/checks/pr/commitlint.d.ts +1 -1
  61. package/dist/{core → cjs/core}/checks/pr/commitlint.js +12 -12
  62. package/dist/{core → cjs/core}/checks/pr/pr-body.d.ts +1 -1
  63. package/dist/{core → cjs/core}/checks/pr/pr-body.js +8 -8
  64. package/dist/{core → cjs/core}/checks/pr/pr-size-check.d.ts +1 -1
  65. package/dist/{core → cjs/core}/checks/pr/pr-size-check.js +10 -10
  66. package/dist/{core → cjs/core}/checks/pr/pr-title-lint.d.ts +1 -1
  67. package/dist/{core → cjs/core}/checks/pr/pr-title-lint.js +8 -8
  68. package/dist/{core → cjs/core}/checks/quality/case-collision.d.ts +1 -1
  69. package/dist/{core → cjs/core}/checks/quality/case-collision.js +7 -7
  70. package/dist/{core → cjs/core}/checks/quality/config-validation.d.ts +1 -1
  71. package/dist/{core → cjs/core}/checks/quality/config-validation.js +9 -9
  72. package/dist/{core → cjs/core}/checks/quality/coverage.d.ts +1 -1
  73. package/dist/{core → cjs/core}/checks/quality/coverage.js +4 -4
  74. package/dist/{core → cjs/core}/checks/quality/dead-code.d.ts +1 -1
  75. package/dist/{core → cjs/core}/checks/quality/dead-code.js +23 -23
  76. package/dist/{core → cjs/core}/checks/quality/duplicate-check.d.ts +1 -1
  77. package/dist/{core → cjs/core}/checks/quality/duplicate-check.js +22 -22
  78. package/dist/{core → cjs/core}/checks/quality/license-header.d.ts +1 -1
  79. package/dist/{core → cjs/core}/checks/quality/license-header.js +16 -16
  80. package/dist/{core → cjs/core}/checks/quality/markdown-lint.d.ts +1 -1
  81. package/dist/cjs/core/checks/quality/markdown-lint.js +147 -0
  82. package/dist/{core → cjs/core}/checks/quality/package-size.d.ts +1 -1
  83. package/dist/{core → cjs/core}/checks/quality/package-size.js +12 -12
  84. package/dist/{core → cjs/core}/checks/quality/spellcheck.d.ts +2 -2
  85. package/dist/{core → cjs/core}/checks/quality/spellcheck.js +18 -18
  86. package/dist/{core → cjs/core}/checks/quality/symlinks.d.ts +1 -1
  87. package/dist/{core → cjs/core}/checks/quality/symlinks.js +8 -8
  88. package/dist/{core → cjs/core}/checks/quality/yaml-lint.d.ts +1 -1
  89. package/dist/{core → cjs/core}/checks/quality/yaml-lint.js +12 -12
  90. package/dist/{core → cjs/core}/checks/security/diff-security.d.ts +1 -1
  91. package/dist/{core → cjs/core}/checks/security/diff-security.js +15 -15
  92. package/dist/{core → cjs/core}/checks/security/dockerfile-security.d.ts +1 -1
  93. package/dist/{core → cjs/core}/checks/security/dockerfile-security.js +16 -16
  94. package/dist/{core → cjs/core}/checks/security/migration-safety.d.ts +1 -1
  95. package/dist/{core → cjs/core}/checks/security/migration-safety.js +17 -17
  96. package/dist/{core → cjs/core}/checks/security/security.d.ts +1 -1
  97. package/dist/{core → cjs/core}/checks/security/security.js +21 -21
  98. package/dist/{core → cjs/core}/checks/security/sensitive-file-guard.d.ts +1 -1
  99. package/dist/{core → cjs/core}/checks/security/sensitive-file-guard.js +14 -14
  100. package/dist/{core → cjs/core}/checks/security/workflow-security.d.ts +2 -2
  101. package/dist/{core → cjs/core}/checks/security/workflow-security.js +14 -14
  102. package/dist/{core → cjs/core}/checks/tool-install.d.ts +1 -1
  103. package/dist/{core → cjs/core}/checks/tool-install.js +2 -2
  104. package/dist/{core → cjs/core}/context.d.ts +1 -1
  105. package/dist/{core → cjs/core}/context.js +16 -16
  106. package/dist/cjs/core/index.d.ts +4 -0
  107. package/dist/{core → cjs/core}/index.js +7 -7
  108. package/dist/{core → cjs/core}/install/cache.js +2 -2
  109. package/dist/{core → cjs/core}/install/doctor.d.ts +4 -4
  110. package/dist/{core → cjs/core}/install/doctor.js +34 -34
  111. package/dist/{core → cjs/core}/install/ensure.d.ts +2 -2
  112. package/dist/{core → cjs/core}/install/ensure.js +15 -14
  113. package/dist/{core → cjs/core}/install/lock.js +4 -4
  114. package/dist/{core → cjs/core}/install/prepare.d.ts +4 -4
  115. package/dist/{core → cjs/core}/install/prepare.js +13 -13
  116. package/dist/{core → cjs/core}/install/profiles.d.ts +2 -2
  117. package/dist/{core → cjs/core}/install/profiles.js +1 -1
  118. package/dist/{core → cjs/core}/install/reuse.d.ts +1 -1
  119. package/dist/{core → cjs/core}/install/reuse.js +4 -4
  120. package/dist/{core → cjs/core}/registry.d.ts +1 -1
  121. package/dist/cjs/core/registry.js +112 -0
  122. package/dist/{core → cjs/core}/reporters.d.ts +1 -1
  123. package/dist/{core → cjs/core}/reporters.js +28 -28
  124. package/dist/{core → cjs/core}/run-checks.d.ts +2 -2
  125. package/dist/{core → cjs/core}/run-checks.js +31 -31
  126. package/dist/{core → cjs/core}/targets.d.ts +1 -1
  127. package/dist/{core → cjs/core}/targets.js +20 -20
  128. package/dist/{core → cjs/core}/types.d.ts +3 -3
  129. package/dist/cjs/index.d.ts +12 -0
  130. package/dist/{index.js → cjs/index.js} +18 -18
  131. package/dist/{init.js → cjs/init.js} +68 -56
  132. package/dist/{interfaces → cjs/interfaces}/index.d.ts +3 -3
  133. package/dist/cjs/package.json +1 -0
  134. package/dist/{scripts → cjs/scripts}/copy-configs.js +22 -7
  135. package/dist/{scripts → cjs/scripts}/generate-install-manifests.js +2 -2
  136. package/dist/{scripts → cjs/scripts}/publish-github.js +4 -4
  137. package/dist/{utils → cjs/utils}/detect-language.js +12 -12
  138. package/dist/{utils → cjs/utils}/git.js +2 -2
  139. package/dist/{utils → cjs/utils}/http.js +2 -2
  140. package/dist/cjs/utils/index.d.ts +15 -0
  141. package/dist/{utils → cjs/utils}/index.js +13 -13
  142. package/dist/{utils → cjs/utils}/own-deps.d.ts +2 -0
  143. package/dist/{utils → cjs/utils}/own-deps.js +1 -0
  144. package/dist/{utils → cjs/utils}/package-version.js +2 -1
  145. package/dist/{utils → cjs/utils}/resolve-bin.js +2 -1
  146. package/dist/{utils → cjs/utils}/update-notifier.js +4 -4
  147. package/dist/esm/cli.d.ts +2 -0
  148. package/dist/esm/cli.js +137 -0
  149. package/dist/esm/config/constants.d.ts +153 -0
  150. package/dist/esm/config/constants.js +360 -0
  151. package/dist/esm/config/index.d.ts +40 -0
  152. package/dist/esm/config/index.js +60 -0
  153. package/dist/esm/config/loader.d.ts +6 -0
  154. package/dist/esm/config/loader.js +191 -0
  155. package/dist/esm/config/pr-checkmate-workflow-docker.yml +79 -0
  156. package/dist/esm/config/pr-checkmate-workflow.yml +92 -0
  157. package/dist/esm/core/checks/dependencies/banned-imports.d.ts +2 -0
  158. package/dist/esm/core/checks/dependencies/banned-imports.js +70 -0
  159. package/dist/esm/core/checks/dependencies/circular-deps.d.ts +2 -0
  160. package/dist/esm/core/checks/dependencies/circular-deps.js +155 -0
  161. package/dist/esm/core/checks/dependencies/dependency-check.d.ts +43 -0
  162. package/dist/esm/core/checks/dependencies/dependency-check.js +174 -0
  163. package/dist/esm/core/checks/dependencies/grype-scan.d.ts +22 -0
  164. package/dist/esm/core/checks/dependencies/grype-scan.js +112 -0
  165. package/dist/esm/core/checks/dependencies/license-check.d.ts +2 -0
  166. package/dist/esm/core/checks/dependencies/license-check.js +61 -0
  167. package/dist/esm/core/checks/dependencies/npm-audit.d.ts +6 -0
  168. package/dist/esm/core/checks/dependencies/npm-audit.js +80 -0
  169. package/dist/esm/core/checks/dependencies/outdated-deps.d.ts +2 -0
  170. package/dist/esm/core/checks/dependencies/outdated-deps.js +66 -0
  171. package/dist/esm/core/checks/dependencies/vuln-scan.d.ts +2 -0
  172. package/dist/esm/core/checks/dependencies/vuln-scan.js +54 -0
  173. package/dist/esm/core/checks/findings.d.ts +299 -0
  174. package/dist/esm/core/checks/findings.js +701 -0
  175. package/dist/esm/core/checks/git/custom-rules.d.ts +2 -0
  176. package/dist/esm/core/checks/git/custom-rules.js +106 -0
  177. package/dist/esm/core/checks/git/diff-smell.d.ts +19 -0
  178. package/dist/esm/core/checks/git/diff-smell.js +69 -0
  179. package/dist/esm/core/checks/git/large-files.d.ts +2 -0
  180. package/dist/esm/core/checks/git/large-files.js +112 -0
  181. package/dist/esm/core/checks/git/leftover-debug.d.ts +2 -0
  182. package/dist/esm/core/checks/git/leftover-debug.js +89 -0
  183. package/dist/esm/core/checks/git/lockfile-drift.d.ts +2 -0
  184. package/dist/esm/core/checks/git/lockfile-drift.js +114 -0
  185. package/dist/esm/core/checks/git/merge-conflict.d.ts +2 -0
  186. package/dist/esm/core/checks/git/merge-conflict.js +117 -0
  187. package/dist/esm/core/checks/git/missing-tests.d.ts +2 -0
  188. package/dist/esm/core/checks/git/missing-tests.js +105 -0
  189. package/dist/esm/core/checks/git/todo-fixme.d.ts +7 -0
  190. package/dist/esm/core/checks/git/todo-fixme.js +80 -0
  191. package/dist/esm/core/checks/languages/cpp-format.d.ts +7 -0
  192. package/dist/esm/core/checks/languages/cpp-format.js +91 -0
  193. package/dist/esm/core/checks/languages/external-checks.d.ts +77 -0
  194. package/dist/esm/core/checks/languages/external-checks.js +197 -0
  195. package/dist/esm/core/checks/languages/external-runner.d.ts +119 -0
  196. package/dist/esm/core/checks/languages/external-runner.js +138 -0
  197. package/dist/esm/core/checks/languages/go-format.d.ts +8 -0
  198. package/dist/esm/core/checks/languages/go-format.js +163 -0
  199. package/dist/esm/core/checks/languages/lint.d.ts +2 -0
  200. package/dist/esm/core/checks/languages/lint.js +92 -0
  201. package/dist/esm/core/checks/languages/prettier.d.ts +2 -0
  202. package/dist/esm/core/checks/languages/prettier.js +85 -0
  203. package/dist/esm/core/checks/languages/python-format.d.ts +7 -0
  204. package/dist/esm/core/checks/languages/python-format.js +61 -0
  205. package/dist/esm/core/checks/languages/python-lint.d.ts +9 -0
  206. package/dist/esm/core/checks/languages/python-lint.js +55 -0
  207. package/dist/esm/core/checks/languages/python-typecheck.d.ts +14 -0
  208. package/dist/esm/core/checks/languages/python-typecheck.js +184 -0
  209. package/dist/esm/core/checks/languages/ruff-shared.d.ts +10 -0
  210. package/dist/esm/core/checks/languages/ruff-shared.js +35 -0
  211. package/dist/esm/core/checks/languages/typecheck.d.ts +2 -0
  212. package/dist/esm/core/checks/languages/typecheck.js +61 -0
  213. package/dist/esm/core/checks/pr/commitlint.d.ts +2 -0
  214. package/dist/esm/core/checks/pr/commitlint.js +102 -0
  215. package/dist/esm/core/checks/pr/pr-body.d.ts +2 -0
  216. package/dist/esm/core/checks/pr/pr-body.js +43 -0
  217. package/dist/esm/core/checks/pr/pr-size-check.d.ts +2 -0
  218. package/dist/esm/core/checks/pr/pr-size-check.js +42 -0
  219. package/dist/esm/core/checks/pr/pr-title-lint.d.ts +2 -0
  220. package/dist/esm/core/checks/pr/pr-title-lint.js +31 -0
  221. package/dist/esm/core/checks/quality/case-collision.d.ts +8 -0
  222. package/dist/esm/core/checks/quality/case-collision.js +39 -0
  223. package/dist/esm/core/checks/quality/config-validation.d.ts +11 -0
  224. package/dist/esm/core/checks/quality/config-validation.js +188 -0
  225. package/dist/esm/core/checks/quality/coverage.d.ts +2 -0
  226. package/dist/esm/core/checks/quality/coverage.js +62 -0
  227. package/dist/esm/core/checks/quality/dead-code.d.ts +2 -0
  228. package/dist/esm/core/checks/quality/dead-code.js +124 -0
  229. package/dist/esm/core/checks/quality/duplicate-check.d.ts +2 -0
  230. package/dist/esm/core/checks/quality/duplicate-check.js +141 -0
  231. package/dist/esm/core/checks/quality/license-header.d.ts +7 -0
  232. package/dist/esm/core/checks/quality/license-header.js +81 -0
  233. package/dist/esm/core/checks/quality/markdown-lint.d.ts +2 -0
  234. package/dist/esm/core/checks/quality/markdown-lint.js +141 -0
  235. package/dist/esm/core/checks/quality/package-size.d.ts +36 -0
  236. package/dist/esm/core/checks/quality/package-size.js +196 -0
  237. package/dist/esm/core/checks/quality/spellcheck.d.ts +77 -0
  238. package/dist/esm/core/checks/quality/spellcheck.js +296 -0
  239. package/dist/esm/core/checks/quality/symlinks.d.ts +16 -0
  240. package/dist/esm/core/checks/quality/symlinks.js +57 -0
  241. package/dist/esm/core/checks/quality/yaml-lint.d.ts +20 -0
  242. package/dist/esm/core/checks/quality/yaml-lint.js +116 -0
  243. package/dist/esm/core/checks/security/diff-security.d.ts +2 -0
  244. package/dist/esm/core/checks/security/diff-security.js +354 -0
  245. package/dist/esm/core/checks/security/dockerfile-security.d.ts +2 -0
  246. package/dist/esm/core/checks/security/dockerfile-security.js +98 -0
  247. package/dist/esm/core/checks/security/migration-safety.d.ts +2 -0
  248. package/dist/esm/core/checks/security/migration-safety.js +137 -0
  249. package/dist/esm/core/checks/security/security.d.ts +63 -0
  250. package/dist/esm/core/checks/security/security.js +201 -0
  251. package/dist/esm/core/checks/security/sensitive-file-guard.d.ts +2 -0
  252. package/dist/esm/core/checks/security/sensitive-file-guard.js +160 -0
  253. package/dist/esm/core/checks/security/workflow-security.d.ts +12 -0
  254. package/dist/esm/core/checks/security/workflow-security.js +100 -0
  255. package/dist/esm/core/checks/tool-install.d.ts +102 -0
  256. package/dist/esm/core/checks/tool-install.js +66 -0
  257. package/dist/esm/core/commands.d.ts +27 -0
  258. package/dist/esm/core/commands.js +89 -0
  259. package/dist/esm/core/context.d.ts +62 -0
  260. package/dist/esm/core/context.js +241 -0
  261. package/dist/esm/core/index.d.ts +4 -0
  262. package/dist/esm/core/index.js +4 -0
  263. package/dist/esm/core/install/cache.d.ts +58 -0
  264. package/dist/esm/core/install/cache.js +142 -0
  265. package/dist/esm/core/install/doctor.d.ts +35 -0
  266. package/dist/esm/core/install/doctor.js +164 -0
  267. package/dist/esm/core/install/ensure.d.ts +77 -0
  268. package/dist/esm/core/install/ensure.js +269 -0
  269. package/dist/esm/core/install/lock.d.ts +34 -0
  270. package/dist/esm/core/install/lock.js +92 -0
  271. package/dist/esm/core/install/merge.d.ts +47 -0
  272. package/dist/esm/core/install/merge.js +85 -0
  273. package/dist/esm/core/install/prepare.d.ts +76 -0
  274. package/dist/esm/core/install/prepare.js +142 -0
  275. package/dist/esm/core/install/profiles.d.ts +78 -0
  276. package/dist/esm/core/install/profiles.js +192 -0
  277. package/dist/esm/core/install/reuse.d.ts +41 -0
  278. package/dist/esm/core/install/reuse.js +100 -0
  279. package/dist/esm/core/registry.d.ts +6 -0
  280. package/dist/esm/core/registry.js +109 -0
  281. package/dist/esm/core/reporters.d.ts +18 -0
  282. package/dist/esm/core/reporters.js +266 -0
  283. package/dist/esm/core/run-checks.d.ts +33 -0
  284. package/dist/esm/core/run-checks.js +293 -0
  285. package/dist/esm/core/targets.d.ts +140 -0
  286. package/dist/esm/core/targets.js +213 -0
  287. package/dist/esm/core/types.d.ts +195 -0
  288. package/dist/esm/core/types.js +38 -0
  289. package/dist/esm/index.d.ts +12 -0
  290. package/dist/esm/index.js +15 -0
  291. package/dist/esm/init.d.ts +38 -0
  292. package/dist/esm/init.js +421 -0
  293. package/dist/esm/interfaces/index.d.ts +432 -0
  294. package/dist/esm/interfaces/index.js +2 -0
  295. package/dist/esm/package.json +1 -0
  296. package/dist/esm/scripts/copy-configs.d.ts +1 -0
  297. package/dist/esm/scripts/copy-configs.js +64 -0
  298. package/dist/esm/scripts/generate-install-manifests.d.ts +39 -0
  299. package/dist/esm/scripts/generate-install-manifests.js +115 -0
  300. package/dist/esm/scripts/publish-github.d.ts +1 -0
  301. package/dist/esm/scripts/publish-github.js +45 -0
  302. package/dist/esm/utils/ci.d.ts +11 -0
  303. package/dist/esm/utils/ci.js +40 -0
  304. package/dist/esm/utils/detect-language.d.ts +48 -0
  305. package/dist/esm/utils/detect-language.js +234 -0
  306. package/dist/esm/utils/direct-deps.d.ts +21 -0
  307. package/dist/esm/utils/direct-deps.js +220 -0
  308. package/dist/esm/utils/execa.d.ts +22 -0
  309. package/dist/esm/utils/execa.js +10 -0
  310. package/dist/esm/utils/git.d.ts +14 -0
  311. package/dist/esm/utils/git.js +21 -0
  312. package/dist/esm/utils/glob.d.ts +24 -0
  313. package/dist/esm/utils/glob.js +40 -0
  314. package/dist/esm/utils/http.d.ts +59 -0
  315. package/dist/esm/utils/http.js +115 -0
  316. package/dist/esm/utils/index.d.ts +15 -0
  317. package/dist/esm/utils/index.js +26 -0
  318. package/dist/esm/utils/logger.d.ts +65 -0
  319. package/dist/esm/utils/logger.js +157 -0
  320. package/dist/esm/utils/own-deps.d.ts +16 -0
  321. package/dist/esm/utils/own-deps.js +107 -0
  322. package/dist/esm/utils/package-version.d.ts +4 -0
  323. package/dist/esm/utils/package-version.js +38 -0
  324. package/dist/esm/utils/pool.d.ts +49 -0
  325. package/dist/esm/utils/pool.js +113 -0
  326. package/dist/esm/utils/resolve-bin.d.ts +67 -0
  327. package/dist/esm/utils/resolve-bin.js +156 -0
  328. package/dist/esm/utils/result-collector.d.ts +33 -0
  329. package/dist/esm/utils/result-collector.js +44 -0
  330. package/dist/esm/utils/update-notifier.d.ts +19 -0
  331. package/dist/esm/utils/update-notifier.js +108 -0
  332. package/install-manifests/core/package-lock.json +4 -4
  333. package/install-manifests/core/package.json +1 -1
  334. package/install-manifests/duplicate/package-lock.json +0 -12
  335. package/install-manifests/js/package-lock.json +17 -41
  336. package/install-manifests/js/package.json +1 -1
  337. package/install-manifests/markdown/package-lock.json +1 -492
  338. package/install-manifests/markdown/package.json +1 -1
  339. package/package.json +17 -15
  340. package/dist/core/checks/languages/lint.d.ts +0 -2
  341. package/dist/core/checks/quality/markdown-lint.js +0 -110
  342. package/dist/core/index.d.ts +0 -4
  343. package/dist/core/registry.js +0 -112
  344. package/dist/index.d.ts +0 -12
  345. package/dist/utils/index.d.ts +0 -15
  346. /package/dist/{cli.d.ts → cjs/cli.d.ts} +0 -0
  347. /package/dist/{config → cjs/config}/constants.d.ts +0 -0
  348. /package/dist/{config → cjs/config}/constants.js +0 -0
  349. /package/dist/{config → cjs/config}/pr-checkmate-workflow-docker.yml +0 -0
  350. /package/dist/{config → cjs/config}/pr-checkmate-workflow.yml +0 -0
  351. /package/dist/{core → cjs/core}/checks/languages/ruff-shared.d.ts +0 -0
  352. /package/dist/{core → cjs/core}/checks/languages/ruff-shared.js +0 -0
  353. /package/dist/{core → cjs/core}/commands.d.ts +0 -0
  354. /package/dist/{core → cjs/core}/commands.js +0 -0
  355. /package/dist/{core → cjs/core}/install/cache.d.ts +0 -0
  356. /package/dist/{core → cjs/core}/install/lock.d.ts +0 -0
  357. /package/dist/{core → cjs/core}/install/merge.d.ts +0 -0
  358. /package/dist/{core → cjs/core}/install/merge.js +0 -0
  359. /package/dist/{core → cjs/core}/types.js +0 -0
  360. /package/dist/{init.d.ts → cjs/init.d.ts} +0 -0
  361. /package/dist/{interfaces → cjs/interfaces}/index.js +0 -0
  362. /package/dist/{scripts → cjs/scripts}/copy-configs.d.ts +0 -0
  363. /package/dist/{scripts → cjs/scripts}/generate-install-manifests.d.ts +0 -0
  364. /package/dist/{scripts → cjs/scripts}/publish-github.d.ts +0 -0
  365. /package/dist/{utils → cjs/utils}/ci.d.ts +0 -0
  366. /package/dist/{utils → cjs/utils}/ci.js +0 -0
  367. /package/dist/{utils → cjs/utils}/detect-language.d.ts +0 -0
  368. /package/dist/{utils → cjs/utils}/direct-deps.d.ts +0 -0
  369. /package/dist/{utils → cjs/utils}/direct-deps.js +0 -0
  370. /package/dist/{utils → cjs/utils}/execa.d.ts +0 -0
  371. /package/dist/{utils → cjs/utils}/execa.js +0 -0
  372. /package/dist/{utils → cjs/utils}/git.d.ts +0 -0
  373. /package/dist/{utils → cjs/utils}/glob.d.ts +0 -0
  374. /package/dist/{utils → cjs/utils}/glob.js +0 -0
  375. /package/dist/{utils → cjs/utils}/http.d.ts +0 -0
  376. /package/dist/{utils → cjs/utils}/logger.d.ts +0 -0
  377. /package/dist/{utils → cjs/utils}/logger.js +0 -0
  378. /package/dist/{utils → cjs/utils}/package-version.d.ts +0 -0
  379. /package/dist/{utils → cjs/utils}/pool.d.ts +0 -0
  380. /package/dist/{utils → cjs/utils}/pool.js +0 -0
  381. /package/dist/{utils → cjs/utils}/resolve-bin.d.ts +0 -0
  382. /package/dist/{utils → cjs/utils}/result-collector.d.ts +0 -0
  383. /package/dist/{utils → cjs/utils}/result-collector.js +0 -0
  384. /package/dist/{utils → cjs/utils}/update-notifier.d.ts +0 -0
@@ -0,0 +1,201 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { getPackageConfigPath, hasLocalConfig } from '../../../config/index.js';
5
+ import { execa } from '../../../utils/execa.js';
6
+ import { logger, resolveGitRef, resolvePackageBin } from '../../../utils/index.js';
7
+ import { pass, fail, warn, skip } from '../../types.js';
8
+ /**
9
+ * A HOME the scanner can actually write to.
10
+ *
11
+ * The bundled wrapper caches the gitleaks binary in `os.homedir()/.gitleaks-cache`
12
+ * and offers no way to move it — the path is hardcoded. So in any environment
13
+ * where HOME is not writable it crashes before scanning a single commit. That is
14
+ * not exotic: it is what the container does under `--user "$(id -u):$(id -g)"`,
15
+ * the invocation our own entrypoint and docs recommend, where HOME is `/`.
16
+ *
17
+ * Redirecting to the temp directory costs nothing that was available anyway — if
18
+ * HOME cannot be written to, there was no `~/.gitconfig` of ours to lose either.
19
+ */
20
+ /**
21
+ * How many secrets the scan actually found.
22
+ *
23
+ * The row used to read `❌ Security Scan: potential secrets detected` whether the
24
+ * number was one or thirty-nine, and that is not a cosmetic gap. It is the reason
25
+ * a real regression nearly shipped: an allowlist entry removed six genuine
26
+ * `Generic API Key` findings from a client repository, 39 → 33, and the A/B sweep
27
+ * that compares report rows across 28 repositories saw two identical lines
28
+ * (ticket 085). A check that finds things has to say how many, or nothing can
29
+ * tell whether it started finding fewer.
30
+ *
31
+ * Read from gitleaks' JSON report rather than counted out of its console output —
32
+ * the same lesson as ticket 078, where the clone count was parsed from an English
33
+ * sentence and would have reported zero on a repository full of duplication.
34
+ *
35
+ * @returns The count, or `null` when the report could not be read — in which case
36
+ * the caller keeps its old wording rather than claiming a number it does not have.
37
+ */
38
+ export function countFindings(reportPath) {
39
+ try {
40
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf-8'));
41
+ return Array.isArray(report) ? report.length : null;
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * How the outcome names what was found.
49
+ *
50
+ * Its own function so both halves are reachable without running the scanner. The
51
+ * corpus for this check deliberately does not: the wrapper downloads a gitleaks
52
+ * binary on first use, and a unit suite that reaches the network is the thing the
53
+ * `installer` seam in `ensure.ts` exists to avoid. What broke here was the wording
54
+ * — a row that read the same for one secret and for thirty-nine — and the wording
55
+ * is testable on its own.
56
+ *
57
+ * `null` keeps the old sentence rather than inventing a number: a report we could
58
+ * not read is not a reason to claim a count.
59
+ */
60
+ export function secretsSummary(found) {
61
+ return found === null ? 'potential secrets detected' : `${found} potential secret(s)`;
62
+ }
63
+ function writableHome() {
64
+ const home = os.homedir();
65
+ try {
66
+ fs.accessSync(home, fs.constants.W_OK);
67
+ return home;
68
+ }
69
+ catch { }
70
+ const fallback = os.tmpdir();
71
+ try {
72
+ fs.accessSync(fallback, fs.constants.W_OK);
73
+ logger.info(`[Security Scan]: HOME (${home}) is not writable — caching the scanner in ${fallback}`);
74
+ return fallback;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * Whether the wrapper died instead of reporting, judged from its stderr.
82
+ *
83
+ * The wrapper is a Node script, and Node exits **1** on an unhandled exception —
84
+ * the same code gitleaks itself uses for "secrets found". Left undistinguished,
85
+ * a scanner that never ran is reported as `potential secrets detected`, pointing
86
+ * at a report file that was never written. A gate accusing a clean repository is
87
+ * the same class of failure as one that stays quiet about a real leak, and this
88
+ * is the only signal available to tell them apart.
89
+ */
90
+ export function looksLikeCrash(stderr) {
91
+ // The errno names are written out rather than factored to a shared `E` prefix:
92
+ // `E(?:ACCES|PERM|…)` saves four characters and costs the reader the ability to
93
+ // grep for the thing they are debugging.
94
+ return /^\s*(?:[A-Za-z]*Error|Uncaught|node:internal)|\b(?:EACCES|EPERM|ENOENT|EROFS)\b/m.test(stderr);
95
+ }
96
+ /**
97
+ * Secret scanning via the bundled gitleaks package. Language-agnostic — runs on
98
+ * every repo (Node, Python, C++, mobile). Secrets found → fail (blocking).
99
+ * Tooling/infra errors → warn, so a scanner hiccup never blocks an unrelated PR.
100
+ */
101
+ export const securityCheck = {
102
+ name: 'Security Scan',
103
+ phase: 'blocking',
104
+ applies(ctx) {
105
+ return ctx.config.security?.gitleaks?.enabled !== false;
106
+ },
107
+ async run(ctx) {
108
+ // A binary that cannot be resolved throws, and a throw in a blocking check is
109
+ // a failed PR — the client would see "Security Scan threw: Cannot resolve
110
+ // binary" as a red gate on a repo with no secrets in it. Every other bundled
111
+ // tool (knip, license-checker) already skips here; this one did not.
112
+ let gitleaksBin;
113
+ try {
114
+ gitleaksBin = resolvePackageBin('gitleaks-secret-scanner-fixed', 'dist/bin/cli.js');
115
+ }
116
+ catch {
117
+ logger.warn('[Security Scan]: ⚠️ bundled gitleaks scanner could not be resolved — skipping');
118
+ return skip('gitleaks unavailable');
119
+ }
120
+ const hasClientConfig = hasLocalConfig(['.gitleaks.toml', 'gitleaks.toml', '.gitleaks/config.toml'], ctx.cwd);
121
+ // Pin the gitleaks binary version so the scanner never reaches out to the
122
+ // GitHub API for the "latest" version — that network call adds latency and
123
+ // times out in locked-down/offline CI (the scanner already falls back to
124
+ // this exact version). Overridable via `security.gitleaks.version`.
125
+ const gitleaksVersion = ctx.config.security?.gitleaks?.version ?? '8.30.0';
126
+ // The scan scope has to follow the run's scope. `--diff-mode ci` REQUIRES
127
+ // HEAD_SHA and aborts without it, so on a full-scan run (no diff range — the
128
+ // local default) it exited 2 and the secret scan silently became a warning
129
+ // about the scanner instead of a review of the code. `history` is the only
130
+ // whole-repository mode the scanner offers; it walks the full commit history,
131
+ // which took ~1s over 700 commits here but is worth bounding on a large
132
+ // monorepo via `security.gitleaks.depth`.
133
+ const isDelta = Boolean(ctx.baseSha);
134
+ const depth = ctx.config.security?.gitleaks?.depth;
135
+ // History mode needs a repository to walk. Without one the scanner exits 2
136
+ // after printing `fatal: not a git repository` and "An unexpected error
137
+ // occurred" — alarming output about the tool, for a directory it was never
138
+ // able to review. Every other check skips when git is unusable; so does this.
139
+ if (!isDelta && !(await resolveGitRef(ctx.cwd, 'HEAD'))) {
140
+ return skip('git history unavailable');
141
+ }
142
+ const args = isDelta
143
+ ? ['--diff-mode', 'ci']
144
+ : ['--diff-mode', 'history', ...(depth ? ['--depth', String(depth)] : [])];
145
+ args.push('--gitleaks-version', gitleaksVersion);
146
+ if (!hasClientConfig) {
147
+ args.push('--config', getPackageConfigPath('gitleaks.toml'));
148
+ }
149
+ // A machine-readable copy of the findings, purely so the outcome can carry a
150
+ // number. Written to a temp directory, never into the repository under review:
151
+ // a check whose side effect is a new untracked file shows up in the client's
152
+ // next commit. The console output is untouched — `stdio: inherit` below still
153
+ // streams the scanner's own report, which is what a reader actually looks at.
154
+ const reportDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pr-checkmate-gitleaks-'));
155
+ const reportPath = path.join(reportDir, 'findings.json');
156
+ args.push('--report-format', 'json', '--report-path', reportPath);
157
+ logger.info(`[Security Scan]: scanning ${isDelta ? 'the diff range' : `full history${depth ? ` (last ${depth} commits)` : ''}`}`);
158
+ const home = writableHome();
159
+ if (!home)
160
+ return skip('no writable directory for the scanner cache');
161
+ const env = {
162
+ ...process.env,
163
+ HOME: home,
164
+ ...(ctx.headSha ? { HEAD_SHA: ctx.headSha } : {}),
165
+ ...(ctx.baseSha ? { BASE_SHA: ctx.baseSha } : {}),
166
+ };
167
+ // stdout stays inherited so the scanner's own report streams to the log as it
168
+ // always has; stderr is captured because it is the only thing that separates
169
+ // a crash from a finding — see `crashed`.
170
+ const result = await execa(process.execPath, [gitleaksBin, ...args], {
171
+ cwd: ctx.cwd,
172
+ env,
173
+ stdio: ['ignore', 'inherit', 'pipe'],
174
+ reject: false,
175
+ });
176
+ const stderr = String(result.stderr ?? '');
177
+ if (stderr.trim())
178
+ logger.warn(stderr.trimEnd());
179
+ const found = countFindings(reportPath);
180
+ fs.rmSync(reportDir, { recursive: true, force: true });
181
+ if (result.exitCode === 0)
182
+ return pass();
183
+ // gitleaks exits 1 specifically when it finds secrets — but so does Node when
184
+ // the wrapper around it throws. Only the second one leaves a stack trace.
185
+ if (result.exitCode === 1 && !looksLikeCrash(stderr)) {
186
+ // The findings themselves are already above, streamed from the scanner. This
187
+ // used to say "review gitleaks-report.html" — a file the check never wrote:
188
+ // `--html-report` is passed by our own `scan:secrets` npm script and by
189
+ // nothing else, so a blocking check told the client to open something that
190
+ // does not exist (ticket 085).
191
+ const summary = secretsSummary(found);
192
+ logger.warn(`⚠️ gitleaks found ${summary} — see the findings above`);
193
+ return fail(summary);
194
+ }
195
+ const detail = looksLikeCrash(stderr)
196
+ ? 'scanner failed to start — nothing was scanned'
197
+ : `scanner error (exit ${result.exitCode})`;
198
+ logger.warn(`[Security Scan]: ⚠️ ${detail}`);
199
+ return warn(detail);
200
+ },
201
+ };
@@ -0,0 +1,2 @@
1
+ import { Check } from '../../types.js';
2
+ export declare const sensitiveFileGuardCheck: Check;
@@ -0,0 +1,160 @@
1
+ import { execa } from '../../../utils/execa.js';
2
+ import { logger, matchesAnyGlob } from '../../../utils/index.js';
3
+ import { excludeClientPaths } from '../../targets.js';
4
+ import { pass, skip } from '../../types.js';
5
+ import { clientFindingOptions, reportSeverityBuckets } from '../findings.js';
6
+ /**
7
+ * The committed template, in the names every ecosystem uses for it.
8
+ *
9
+ * `.env.example` holds placeholders, is checked in on purpose, and is what the
10
+ * `.env` convention tells people to commit — a repository without one is the
11
+ * unusual case. Flagging it as a secret is not a stylistic quibble now that
12
+ * ticket 055 makes this category block: it turns the recommended practice into a
13
+ * red build, and a gate that fires on correct code is a gate people switch off.
14
+ *
15
+ * Confirmed in the field before it was fixed here: a client shipped
16
+ * `"sensitiveFileGuard": { "ignorePaths": [".env.example"] }` in their config to
17
+ * work around it. Needing a hand-written exclusion for the file the ecosystem
18
+ * mandates is the definition of a false positive.
19
+ *
20
+ * Matched only at the end of the path, so `.env.example.local` — a real file with
21
+ * real values, named to sort next to the template — is still flagged.
22
+ */
23
+ const TEMPLATE_SUFFIX = /\.(?:example|sample|template|dist|defaults)$/i;
24
+ const SENSITIVE_CATEGORIES = [
25
+ {
26
+ label: 'Environment files (.env)',
27
+ // The dots are escaped. They were not, and `\/.env$` therefore matched ANY
28
+ // single character before `env`, so a nested file whose name merely ended in
29
+ // those three letters was reported as an environment file. Harmless while
30
+ // this category only warned; a red build over a filename once it blocks.
31
+ patterns: [/^\.env$/, /^\.env\..+/, /\/\.env$/, /\/\.env\..+/],
32
+ except: [TEMPLATE_SUFFIX],
33
+ severity: 'error',
34
+ },
35
+ {
36
+ label: 'Private keys and certificates',
37
+ patterns: [/\.(pem|key|p12|pfx|crt|cer|der|jks|keystore)$/i],
38
+ severity: 'error',
39
+ },
40
+ {
41
+ label: 'CI/CD pipeline config',
42
+ patterns: [
43
+ /^\.github\/workflows\//,
44
+ /^\.gitlab-ci\.yml$/,
45
+ /^\.circleci\//,
46
+ /^\.travis\.yml$/,
47
+ /^Jenkinsfile$/,
48
+ /^\.buildkite\//,
49
+ ],
50
+ severity: 'warn',
51
+ },
52
+ {
53
+ label: 'Authentication / authorization middleware',
54
+ patterns: [
55
+ /\/(middleware|guards?|interceptors?)\/(auth|jwt|session|token|oauth|rbac)/i,
56
+ /\/(auth|jwt|session)\.(ts|js|mjs|cjs)$/i,
57
+ ],
58
+ severity: 'warn',
59
+ },
60
+ {
61
+ label: 'Infrastructure and deployment config',
62
+ patterns: [
63
+ /^(docker-compose|compose)\.(yml|yaml)$/,
64
+ /^Dockerfile(\..+)?$/,
65
+ /^terraform\//,
66
+ /\.tf$/,
67
+ /^\.?kubeconfig$/,
68
+ /^helm\//,
69
+ ],
70
+ severity: 'warn',
71
+ },
72
+ {
73
+ label: 'Package manager lockfiles (dependency supply chain)',
74
+ patterns: [/^package-lock\.json$/, /^yarn\.lock$/, /^pnpm-lock\.yaml$/],
75
+ severity: 'warn',
76
+ },
77
+ ];
78
+ /** Bucket changed files by category severity, keyed by category label. */
79
+ function categorizeFiles(changedFiles) {
80
+ var _a;
81
+ const errors = {};
82
+ const warnings = {};
83
+ for (const filePath of changedFiles) {
84
+ for (const category of SENSITIVE_CATEGORIES) {
85
+ if (category.except?.some(p => p.test(filePath)))
86
+ continue;
87
+ if (category.patterns.some(p => p.test(filePath))) {
88
+ const bucket = category.severity === 'error' ? errors : warnings;
89
+ (bucket[_a = category.label] ?? (bucket[_a] = [])).push(filePath);
90
+ }
91
+ }
92
+ }
93
+ return { errors, warnings };
94
+ }
95
+ export const sensitiveFileGuardCheck = {
96
+ name: 'Sensitive Files',
97
+ phase: 'informational',
98
+ // No `requiresDiff`: a committed `.env` or private key is committed whether
99
+ // this PR touched it or not (ticket 067). In a full scan the question becomes
100
+ // "what sensitive files does this repository contain", which is the more
101
+ // useful one when auditing a repo rather than reviewing a change.
102
+ applies(ctx) {
103
+ return ctx.config.sensitiveFileGuard?.enabled !== false;
104
+ },
105
+ async run(ctx) {
106
+ try {
107
+ const args = ctx.baseSha
108
+ ? ['diff', '--name-only', '--diff-filter=ACMRD', ctx.baseSha, ctx.headSha ?? 'HEAD']
109
+ : ['ls-files'];
110
+ if (!ctx.baseSha && ctx.config.run?.fullScanBehaviour === 'skip')
111
+ return pass();
112
+ // `reject: false` so a directory that is not a repository is an ordinary
113
+ // outcome rather than a thrown error whose message carries git's raw
114
+ // `fatal: not a git repository` into the log. The check still skips — but
115
+ // a skip with a reason reads as a decision, while a bare `fatal:` reads as
116
+ // a crash, and someone has to spend time proving it was not one.
117
+ const result = await execa('git', args, { cwd: ctx.cwd, reject: false });
118
+ if (result.exitCode !== 0)
119
+ return skip('git unavailable');
120
+ const cfg = ctx.config.sensitiveFileGuard ?? {};
121
+ // Path exclusions are applied before categorising, so one false positive —
122
+ // a `.pem` test fixture, a committed `.env.example` — is excused by name.
123
+ // Without this the only exclusion a client has is `ignore`, which suppresses
124
+ // the whole CATEGORY: muting "Private keys and certificates" to excuse one
125
+ // fixture would also excuse the next real key someone commits.
126
+ // `excludePaths` before the check's own `ignorePaths`, and both before
127
+ // categorising.
128
+ //
129
+ // This check deliberately does NOT go through `targets.ts`: a committed
130
+ // `.env` inside `dist/` is still a committed secret, so `ignoreDirs` must
131
+ // not hide it, and a key outside `sourcePath` is still a key. But
132
+ // `excludePaths` is different in kind — it is the client saying "do not
133
+ // look here" in as many words, and the documentation calls it the one
134
+ // exclusion that reaches every check. It did not reach this one, which
135
+ // meant a `.pem` test fixture failed the build and the key the docs told
136
+ // people to reach for did nothing (ticket 080).
137
+ const changedFiles = excludeClientPaths(result.stdout.split('\n').filter(Boolean), ctx.cwd).filter(file => !matchesAnyGlob(file, cfg.ignorePaths ?? []));
138
+ if (changedFiles.length === 0)
139
+ return pass();
140
+ const { errors, warnings } = categorizeFiles(changedFiles);
141
+ return reportSeverityBuckets(errors, warnings, {
142
+ prefix: 'Sensitive Files',
143
+ // "changed" is only true of a diff; a full scan is answering a different
144
+ // question — what the repository contains — and saying otherwise would
145
+ // send someone looking for a change that never happened.
146
+ errorSummary: n => `${n} sensitive file(s) ${ctx.baseSha ? 'changed' : 'in the repo'}`,
147
+ warningSummary: n => `${n} notable file(s) ${ctx.baseSha ? 'changed' : 'in the repo'}`,
148
+ showCount: false,
149
+ ...clientFindingOptions(ctx, cfg),
150
+ // Every sensitive path is listed rather than sampled: knowing that eight
151
+ // files are in play but only which three is not an answer anyone can act on.
152
+ sampleLimit: undefined,
153
+ });
154
+ }
155
+ catch (err) {
156
+ logger.warn(`[Sensitive Files]: ⚠️ Could not check file changes\n${err}`);
157
+ return skip('diff unavailable');
158
+ }
159
+ },
160
+ };
@@ -0,0 +1,12 @@
1
+ import { Check } from '../../types.js';
2
+ import { DiffPattern } from '../findings.js';
3
+ /**
4
+ * GitHub Actions workflow risks, matched on added diff lines of workflow files.
5
+ * These are the high-frequency supply-chain / injection footguns.
6
+ *
7
+ * Exported for the corpus in `workflow-security.test.ts`: these rules turn on a
8
+ * context distinction subtle enough that only worked examples can hold it in
9
+ * place.
10
+ */
11
+ export declare const WORKFLOW_PATTERNS: DiffPattern[];
12
+ export declare const workflowSecurityCheck: Check;
@@ -0,0 +1,100 @@
1
+ import { logger } from '../../../utils/index.js';
2
+ import { listTrackedFiles } from '../../targets.js';
3
+ import { skip } from '../../types.js';
4
+ import { clientFindingOptions, diffAddedLines, emptyBuckets, mergeSeverityBuckets, reportSeverityBuckets, scanDiffPatterns, scanTrackedFiles, } from '../findings.js';
5
+ /**
6
+ * A YAML key binding — `HEAD_REF: ${{ … }}`, `ref: ${{ … }}` — rather than an
7
+ * interpolation inside a script body.
8
+ *
9
+ * This is the distinction the rules below turn on. Binding an untrusted value to
10
+ * an `env:` entry or an action input is *the recommended fix*: the value reaches
11
+ * the shell as data, expanded after parsing. Flagging it made the check punish
12
+ * the exact remediation it recommends, which is worse than not checking at all —
13
+ * a warning on correct code teaches people to ignore the warnings.
14
+ *
15
+ * The risky shape is the same expression inside a `run:` block, where GitHub
16
+ * substitutes it into the script *before* the shell parses it.
17
+ */
18
+ const YAML_KEY_BINDING = String.raw `(?!\s*[\w.-]+:\s*\$\{\{)`;
19
+ /**
20
+ * GitHub Actions workflow risks, matched on added diff lines of workflow files.
21
+ * These are the high-frequency supply-chain / injection footguns.
22
+ *
23
+ * Exported for the corpus in `workflow-security.test.ts`: these rules turn on a
24
+ * context distinction subtle enough that only worked examples can hold it in
25
+ * place.
26
+ */
27
+ export const WORKFLOW_PATTERNS = [
28
+ {
29
+ // Untrusted PR-controlled input interpolated into an expression — the classic
30
+ // `run:` script-injection vector when it reaches a shell.
31
+ pattern: new RegExp([
32
+ String.raw `^\+(?!\+)${YAML_KEY_BINDING}.*\$\{\{\s*github\.event\.`,
33
+ String.raw `(?:issue|pull_request|comment|review|discussion|head_commit|commits)[^}]*`,
34
+ String.raw `\.(?:title|body|head_ref|ref|label|name|email|message|url)`,
35
+ ].join('')),
36
+ label: 'untrusted github.event input in expression (script injection risk)',
37
+ severity: 'error',
38
+ },
39
+ {
40
+ pattern: new RegExp(String.raw `^\+(?!\+)${YAML_KEY_BINDING}.*\$\{\{\s*github\.head_ref\s*\}\}`),
41
+ label: 'github.head_ref interpolation (injection risk — use an env var)',
42
+ severity: 'warn',
43
+ },
44
+ {
45
+ // Third-party action not pinned to a full 40-char commit SHA.
46
+ pattern: /^\+(?!\+).*\buses:\s*['"]?[\w.-]+\/[\w.-]+@(?![0-9a-fA-F]{40}\b)[\w./-]+/,
47
+ label: 'action not pinned to a full commit SHA (supply-chain risk)',
48
+ severity: 'warn',
49
+ },
50
+ {
51
+ pattern: /^\+(?!\+).*\bpull_request_target\b/,
52
+ label: 'pull_request_target trigger (runs with repo secrets on untrusted PRs)',
53
+ severity: 'warn',
54
+ },
55
+ ];
56
+ export const workflowSecurityCheck = {
57
+ name: 'Workflow Security',
58
+ phase: 'informational',
59
+ // No `requiresDiff`: an injectable workflow is injectable whether the line
60
+ // arrived in this PR or long before it (ticket 067). Ours had lived there for
61
+ // months when ticket 060 found it — by hand, because no mode looked.
62
+ applies(ctx) {
63
+ return ctx.config.workflowSecurity?.enabled !== false;
64
+ },
65
+ async run(ctx) {
66
+ try {
67
+ const globs = ['.github/workflows/*.yml', '.github/workflows/*.yaml'];
68
+ const cfg = ctx.config.workflowSecurity ?? {};
69
+ const found = emptyBuckets();
70
+ const scanInto = (stdout) => {
71
+ // `diffAddedLines` already dropped `pr-checkmate-ignore` lines, and
72
+ // `scanTrackedFiles` does the same for the whole-file path.
73
+ mergeSeverityBuckets(found, scanDiffPatterns(stdout, WORKFLOW_PATTERNS, { ignorePaths: cfg.ignorePaths }));
74
+ };
75
+ if (ctx.baseSha) {
76
+ scanInto(await diffAddedLines(ctx, globs));
77
+ }
78
+ else if (ctx.config.run?.fullScanBehaviour !== 'skip') {
79
+ // `.github` is globally ignored and is the only place these files live,
80
+ // so this caller has to un-ignore it or scan nothing.
81
+ const files = await listTrackedFiles(ctx, globs, ['.'], {
82
+ keepDirs: ['.github', '.gitlab', '.circleci'],
83
+ });
84
+ if (files === null)
85
+ return skip('git unavailable');
86
+ await scanTrackedFiles(ctx, files, scanInto);
87
+ }
88
+ return reportSeverityBuckets(found.errors, found.warnings, {
89
+ prefix: 'Workflow Security',
90
+ errorSummary: n => `${n} issue(s)`,
91
+ warningSummary: n => `${n} warning(s)`,
92
+ ...clientFindingOptions(ctx, cfg),
93
+ });
94
+ }
95
+ catch (err) {
96
+ logger.warn(`[Workflow Security]: ⚠️ Could not analyze diff\n${err}`);
97
+ return skip('diff unavailable');
98
+ }
99
+ },
100
+ };
@@ -0,0 +1,102 @@
1
+ import { CheckOutcome } from '../types.js';
2
+ /**
3
+ * Where to get the external runners we deliberately do not ship.
4
+ *
5
+ * Eleven checks shell out to a tool that is not ours to bundle — a Kotlin linter,
6
+ * a Swift linter, the Go toolchain. When it is absent the check skips, which is
7
+ * the honest outcome, but `⏭️ ktlint: ktlint not installed` tells the reader
8
+ * something they already knew and nothing they can act on. A skip is only useful
9
+ * if it says what would make the check run.
10
+ *
11
+ * One table rather than a string at each call site: fourteen hand-written
12
+ * messages drift, and half of them end up naming an install route that has since
13
+ * moved.
14
+ *
15
+ * **Repository homepages, not deep documentation links.** A `#installation`
16
+ * anchor is exactly the kind of URL that rots, and a dead link printed into a
17
+ * client's CI log is worse than no link — it reads as carelessness about the
18
+ * thing we are asking them to trust. A project's repository root survives
19
+ * documentation reorganisations and always carries current install instructions.
20
+ *
21
+ * The command is the platform-agnostic one where a project has one, and the
22
+ * common-case one otherwise; the URL covers every other platform.
23
+ */
24
+ declare const INSTALL_HINTS: {
25
+ readonly ktlint: {
26
+ readonly command: "brew install ktlint";
27
+ readonly url: "https://github.com/pinterest/ktlint";
28
+ };
29
+ readonly swiftlint: {
30
+ readonly command: "brew install swiftlint";
31
+ readonly url: "https://github.com/realm/SwiftLint";
32
+ };
33
+ readonly rubocop: {
34
+ readonly command: "gem install rubocop";
35
+ readonly url: "https://github.com/rubocop/rubocop";
36
+ };
37
+ readonly shellcheck: {
38
+ readonly command: "brew install shellcheck";
39
+ readonly url: "https://github.com/koalaman/shellcheck";
40
+ };
41
+ readonly go: {
42
+ readonly command: null;
43
+ readonly url: "https://go.dev/doc/install";
44
+ };
45
+ readonly gofmt: {
46
+ readonly command: null;
47
+ readonly url: "https://go.dev/doc/install";
48
+ };
49
+ readonly clippy: {
50
+ readonly command: "rustup component add clippy";
51
+ readonly url: "https://github.com/rust-lang/rust-clippy";
52
+ };
53
+ readonly rustfmt: {
54
+ readonly command: "rustup component add rustfmt";
55
+ readonly url: "https://github.com/rust-lang/rustfmt";
56
+ };
57
+ readonly 'php-cs-fixer': {
58
+ readonly command: "composer global require friendsofphp/php-cs-fixer";
59
+ readonly url: "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer";
60
+ };
61
+ readonly dotnet: {
62
+ readonly command: null;
63
+ readonly url: "https://dotnet.microsoft.com/download";
64
+ };
65
+ readonly mypy: {
66
+ readonly command: "pip install mypy";
67
+ readonly url: "https://github.com/python/mypy";
68
+ };
69
+ readonly pyright: {
70
+ readonly command: "npm i -g pyright";
71
+ readonly url: "https://github.com/microsoft/pyright";
72
+ };
73
+ readonly grype: {
74
+ readonly command: "brew install grype";
75
+ readonly url: "https://github.com/anchore/grype";
76
+ };
77
+ readonly 'osv-scanner': {
78
+ readonly command: "brew install osv-scanner";
79
+ readonly url: "https://github.com/google/osv-scanner";
80
+ };
81
+ };
82
+ /**
83
+ * A tool this package expects the environment to provide.
84
+ *
85
+ * Exported because the runner specs in `languages/external-checks.ts` name one
86
+ * per check, and that is the point of naming them: a spec that cannot compile
87
+ * with a typo in the key cannot ship a skip pointing at an install route that
88
+ * does not exist.
89
+ */
90
+ export type ExternalTool = keyof typeof INSTALL_HINTS;
91
+ /**
92
+ * Skip because `tool` is not on the path, saying how to install it.
93
+ *
94
+ * @param tool Which runner is missing.
95
+ * @param label How to name it in the message. Omit when the check's own display
96
+ * name already is the tool's name — `⏭️ ktlint: ktlint not installed` repeats
97
+ * itself, which is what made these messages worth revisiting. Pass a label
98
+ * when the two differ (`Go Vet` needs `go`) or when either of two tools would
99
+ * have done (`mypy/pyright`).
100
+ */
101
+ export declare function notInstalled(tool: ExternalTool, label?: string): CheckOutcome;
102
+ export {};
@@ -0,0 +1,66 @@
1
+ import { skip } from '../types.js';
2
+ /**
3
+ * Where to get the external runners we deliberately do not ship.
4
+ *
5
+ * Eleven checks shell out to a tool that is not ours to bundle — a Kotlin linter,
6
+ * a Swift linter, the Go toolchain. When it is absent the check skips, which is
7
+ * the honest outcome, but `⏭️ ktlint: ktlint not installed` tells the reader
8
+ * something they already knew and nothing they can act on. A skip is only useful
9
+ * if it says what would make the check run.
10
+ *
11
+ * One table rather than a string at each call site: fourteen hand-written
12
+ * messages drift, and half of them end up naming an install route that has since
13
+ * moved.
14
+ *
15
+ * **Repository homepages, not deep documentation links.** A `#installation`
16
+ * anchor is exactly the kind of URL that rots, and a dead link printed into a
17
+ * client's CI log is worse than no link — it reads as carelessness about the
18
+ * thing we are asking them to trust. A project's repository root survives
19
+ * documentation reorganisations and always carries current install instructions.
20
+ *
21
+ * The command is the platform-agnostic one where a project has one, and the
22
+ * common-case one otherwise; the URL covers every other platform.
23
+ */
24
+ const INSTALL_HINTS = {
25
+ ktlint: { command: 'brew install ktlint', url: 'https://github.com/pinterest/ktlint' },
26
+ swiftlint: { command: 'brew install swiftlint', url: 'https://github.com/realm/SwiftLint' },
27
+ rubocop: { command: 'gem install rubocop', url: 'https://github.com/rubocop/rubocop' },
28
+ shellcheck: {
29
+ command: 'brew install shellcheck',
30
+ url: 'https://github.com/koalaman/shellcheck',
31
+ },
32
+ go: { command: null, url: 'https://go.dev/doc/install' },
33
+ gofmt: { command: null, url: 'https://go.dev/doc/install' },
34
+ clippy: {
35
+ command: 'rustup component add clippy',
36
+ url: 'https://github.com/rust-lang/rust-clippy',
37
+ },
38
+ rustfmt: { command: 'rustup component add rustfmt', url: 'https://github.com/rust-lang/rustfmt' },
39
+ 'php-cs-fixer': {
40
+ command: 'composer global require friendsofphp/php-cs-fixer',
41
+ url: 'https://github.com/PHP-CS-Fixer/PHP-CS-Fixer',
42
+ },
43
+ dotnet: { command: null, url: 'https://dotnet.microsoft.com/download' },
44
+ mypy: { command: 'pip install mypy', url: 'https://github.com/python/mypy' },
45
+ pyright: { command: 'npm i -g pyright', url: 'https://github.com/microsoft/pyright' },
46
+ grype: { command: 'brew install grype', url: 'https://github.com/anchore/grype' },
47
+ 'osv-scanner': {
48
+ command: 'brew install osv-scanner',
49
+ url: 'https://github.com/google/osv-scanner',
50
+ },
51
+ };
52
+ /**
53
+ * Skip because `tool` is not on the path, saying how to install it.
54
+ *
55
+ * @param tool Which runner is missing.
56
+ * @param label How to name it in the message. Omit when the check's own display
57
+ * name already is the tool's name — `⏭️ ktlint: ktlint not installed` repeats
58
+ * itself, which is what made these messages worth revisiting. Pass a label
59
+ * when the two differ (`Go Vet` needs `go`) or when either of two tools would
60
+ * have done (`mypy/pyright`).
61
+ */
62
+ export function notInstalled(tool, label) {
63
+ const { command, url } = INSTALL_HINTS[tool];
64
+ const subject = label ? `${label} not installed` : 'not installed';
65
+ return skip(command ? `${subject} — ${command} · ${url}` : `${subject} — ${url}`);
66
+ }