failproofai 0.0.6-beta.3 → 0.0.6-beta.4

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 (509) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +7 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  19. package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
  21. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  26. package/.next/standalone/.next/server/app/index.html +1 -1
  27. package/.next/standalone/.next/server/app/index.rsc +16 -16
  28. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
  30. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  31. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
  32. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  33. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  34. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  37. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  51. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  52. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kyh86x._.js → [root-of-the-server]__0om-5pe._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__096k.db._.js → [root-of-the-server]__111.vxi._.js} +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
  65. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  66. package/.next/standalone/.next/server/pages/404.html +2 -2
  67. package/.next/standalone/.next/server/pages/500.html +1 -1
  68. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  70. package/.next/standalone/.next/static/chunks/{01pmw1-asbek~.js → 02dqjyv6_9mhq.js} +2 -2
  71. package/.next/standalone/.next/static/chunks/{051m32nx~n5yr.js → 070orfsl6.xal.js} +1 -1
  72. package/.next/standalone/.next/static/chunks/0mir9jdxn35~s.css +1 -0
  73. package/.next/standalone/.next/static/chunks/{0mazj-p-~2kc6.js → 0o547jv-k_k35.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/0pk2h2.mjxy.m.js +6 -0
  75. package/.next/standalone/.next/static/chunks/{0l-mu4okl-cj1.js → 0rcwkbh24w38b.js} +1 -1
  76. package/.next/standalone/.next/static/chunks/{0-dm_9a6nsc2l.js → 140xx_tfr~lm_.js} +1 -1
  77. package/.next/standalone/.next/static/chunks/{156zca6aewyr-.js → 169_e4dq~1~b6.js} +1 -1
  78. package/.next/standalone/.next/static/chunks/{0a-yctdwn368y.js → 17ne4p.1sw1jy.js} +1 -1
  79. package/.next/standalone/next.config.ts +6 -0
  80. package/.next/standalone/package.json +2 -2
  81. package/.next/standalone/server.js +1 -1
  82. package/dist/cli.mjs +2 -2
  83. package/package.json +2 -2
  84. package/scripts/prune-standalone.mjs +128 -0
  85. package/.next/standalone/.claude/settings.json +0 -316
  86. package/.next/standalone/.failproofai/policies/review-policies.mjs +0 -113
  87. package/.next/standalone/.failproofai/policies/workflow-policies.mjs +0 -63
  88. package/.next/standalone/.failproofai/policies-config.json +0 -39
  89. package/.next/standalone/.next/static/chunks/0gu_a.a80ritd.css +0 -1
  90. package/.next/standalone/.next/static/chunks/0qakntsrpc~1j.js +0 -6
  91. package/.next/standalone/AGENTS.md +0 -80
  92. package/.next/standalone/CHANGELOG.md +0 -158
  93. package/.next/standalone/CLAUDE.md +0 -165
  94. package/.next/standalone/CONTRIBUTING.md +0 -76
  95. package/.next/standalone/Dockerfile.docs +0 -12
  96. package/.next/standalone/LICENSE +0 -42
  97. package/.next/standalone/README.md +0 -301
  98. package/.next/standalone/bin/failproofai.mjs +0 -439
  99. package/.next/standalone/bun.lock +0 -1119
  100. package/.next/standalone/components.json +0 -23
  101. package/.next/standalone/dist/cli.mjs +0 -4696
  102. package/.next/standalone/dist/index.js +0 -80
  103. package/.next/standalone/docs/ar/architecture.mdx +0 -334
  104. package/.next/standalone/docs/ar/built-in-policies.mdx +0 -574
  105. package/.next/standalone/docs/ar/cli/dashboard.mdx +0 -28
  106. package/.next/standalone/docs/ar/cli/environment-variables.mdx +0 -34
  107. package/.next/standalone/docs/ar/cli/hook.mdx +0 -31
  108. package/.next/standalone/docs/ar/cli/install-policies.mdx +0 -48
  109. package/.next/standalone/docs/ar/cli/list-policies.mdx +0 -31
  110. package/.next/standalone/docs/ar/cli/remove-policies.mdx +0 -43
  111. package/.next/standalone/docs/ar/cli/version.mdx +0 -13
  112. package/.next/standalone/docs/ar/configuration.mdx +0 -223
  113. package/.next/standalone/docs/ar/custom-policies.mdx +0 -354
  114. package/.next/standalone/docs/ar/dashboard.mdx +0 -142
  115. package/.next/standalone/docs/ar/examples.mdx +0 -307
  116. package/.next/standalone/docs/ar/for-agents.mdx +0 -39
  117. package/.next/standalone/docs/ar/getting-started.mdx +0 -187
  118. package/.next/standalone/docs/ar/introduction.mdx +0 -58
  119. package/.next/standalone/docs/ar/package-aliases.mdx +0 -82
  120. package/.next/standalone/docs/ar/testing.mdx +0 -261
  121. package/.next/standalone/docs/architecture.mdx +0 -332
  122. package/.next/standalone/docs/built-in-policies.mdx +0 -574
  123. package/.next/standalone/docs/cli/dashboard.mdx +0 -28
  124. package/.next/standalone/docs/cli/environment-variables.mdx +0 -34
  125. package/.next/standalone/docs/cli/hook.mdx +0 -30
  126. package/.next/standalone/docs/cli/install-policies.mdx +0 -47
  127. package/.next/standalone/docs/cli/list-policies.mdx +0 -31
  128. package/.next/standalone/docs/cli/remove-policies.mdx +0 -43
  129. package/.next/standalone/docs/cli/version.mdx +0 -12
  130. package/.next/standalone/docs/configuration.mdx +0 -222
  131. package/.next/standalone/docs/custom-policies.mdx +0 -353
  132. package/.next/standalone/docs/dashboard.mdx +0 -142
  133. package/.next/standalone/docs/de/architecture.mdx +0 -332
  134. package/.next/standalone/docs/de/built-in-policies.mdx +0 -574
  135. package/.next/standalone/docs/de/cli/dashboard.mdx +0 -28
  136. package/.next/standalone/docs/de/cli/environment-variables.mdx +0 -34
  137. package/.next/standalone/docs/de/cli/hook.mdx +0 -30
  138. package/.next/standalone/docs/de/cli/install-policies.mdx +0 -47
  139. package/.next/standalone/docs/de/cli/list-policies.mdx +0 -31
  140. package/.next/standalone/docs/de/cli/remove-policies.mdx +0 -43
  141. package/.next/standalone/docs/de/cli/version.mdx +0 -12
  142. package/.next/standalone/docs/de/configuration.mdx +0 -222
  143. package/.next/standalone/docs/de/custom-policies.mdx +0 -353
  144. package/.next/standalone/docs/de/dashboard.mdx +0 -142
  145. package/.next/standalone/docs/de/examples.mdx +0 -307
  146. package/.next/standalone/docs/de/for-agents.mdx +0 -38
  147. package/.next/standalone/docs/de/getting-started.mdx +0 -186
  148. package/.next/standalone/docs/de/introduction.mdx +0 -57
  149. package/.next/standalone/docs/de/package-aliases.mdx +0 -82
  150. package/.next/standalone/docs/de/testing.mdx +0 -260
  151. package/.next/standalone/docs/docs.json +0 -1002
  152. package/.next/standalone/docs/es/architecture.mdx +0 -332
  153. package/.next/standalone/docs/es/built-in-policies.mdx +0 -574
  154. package/.next/standalone/docs/es/cli/dashboard.mdx +0 -28
  155. package/.next/standalone/docs/es/cli/environment-variables.mdx +0 -34
  156. package/.next/standalone/docs/es/cli/hook.mdx +0 -30
  157. package/.next/standalone/docs/es/cli/install-policies.mdx +0 -47
  158. package/.next/standalone/docs/es/cli/list-policies.mdx +0 -31
  159. package/.next/standalone/docs/es/cli/remove-policies.mdx +0 -43
  160. package/.next/standalone/docs/es/cli/version.mdx +0 -12
  161. package/.next/standalone/docs/es/configuration.mdx +0 -222
  162. package/.next/standalone/docs/es/custom-policies.mdx +0 -353
  163. package/.next/standalone/docs/es/dashboard.mdx +0 -142
  164. package/.next/standalone/docs/es/examples.mdx +0 -307
  165. package/.next/standalone/docs/es/for-agents.mdx +0 -38
  166. package/.next/standalone/docs/es/getting-started.mdx +0 -186
  167. package/.next/standalone/docs/es/introduction.mdx +0 -57
  168. package/.next/standalone/docs/es/package-aliases.mdx +0 -82
  169. package/.next/standalone/docs/es/testing.mdx +0 -260
  170. package/.next/standalone/docs/examples.mdx +0 -307
  171. package/.next/standalone/docs/favicon.ico +0 -0
  172. package/.next/standalone/docs/for-agents.mdx +0 -38
  173. package/.next/standalone/docs/fr/architecture.mdx +0 -332
  174. package/.next/standalone/docs/fr/built-in-policies.mdx +0 -574
  175. package/.next/standalone/docs/fr/cli/dashboard.mdx +0 -28
  176. package/.next/standalone/docs/fr/cli/environment-variables.mdx +0 -34
  177. package/.next/standalone/docs/fr/cli/hook.mdx +0 -30
  178. package/.next/standalone/docs/fr/cli/install-policies.mdx +0 -47
  179. package/.next/standalone/docs/fr/cli/list-policies.mdx +0 -31
  180. package/.next/standalone/docs/fr/cli/remove-policies.mdx +0 -43
  181. package/.next/standalone/docs/fr/cli/version.mdx +0 -12
  182. package/.next/standalone/docs/fr/configuration.mdx +0 -222
  183. package/.next/standalone/docs/fr/custom-policies.mdx +0 -353
  184. package/.next/standalone/docs/fr/dashboard.mdx +0 -142
  185. package/.next/standalone/docs/fr/examples.mdx +0 -307
  186. package/.next/standalone/docs/fr/for-agents.mdx +0 -38
  187. package/.next/standalone/docs/fr/getting-started.mdx +0 -186
  188. package/.next/standalone/docs/fr/introduction.mdx +0 -57
  189. package/.next/standalone/docs/fr/package-aliases.mdx +0 -82
  190. package/.next/standalone/docs/fr/testing.mdx +0 -260
  191. package/.next/standalone/docs/getting-started.mdx +0 -186
  192. package/.next/standalone/docs/he/architecture.mdx +0 -333
  193. package/.next/standalone/docs/he/built-in-policies.mdx +0 -575
  194. package/.next/standalone/docs/he/cli/dashboard.mdx +0 -28
  195. package/.next/standalone/docs/he/cli/environment-variables.mdx +0 -34
  196. package/.next/standalone/docs/he/cli/hook.mdx +0 -30
  197. package/.next/standalone/docs/he/cli/install-policies.mdx +0 -47
  198. package/.next/standalone/docs/he/cli/list-policies.mdx +0 -32
  199. package/.next/standalone/docs/he/cli/remove-policies.mdx +0 -43
  200. package/.next/standalone/docs/he/cli/version.mdx +0 -12
  201. package/.next/standalone/docs/he/configuration.mdx +0 -223
  202. package/.next/standalone/docs/he/custom-policies.mdx +0 -353
  203. package/.next/standalone/docs/he/dashboard.mdx +0 -142
  204. package/.next/standalone/docs/he/examples.mdx +0 -307
  205. package/.next/standalone/docs/he/for-agents.mdx +0 -38
  206. package/.next/standalone/docs/he/getting-started.mdx +0 -186
  207. package/.next/standalone/docs/he/introduction.mdx +0 -57
  208. package/.next/standalone/docs/he/package-aliases.mdx +0 -82
  209. package/.next/standalone/docs/he/testing.mdx +0 -260
  210. package/.next/standalone/docs/hi/architecture.mdx +0 -334
  211. package/.next/standalone/docs/hi/built-in-policies.mdx +0 -572
  212. package/.next/standalone/docs/hi/cli/dashboard.mdx +0 -28
  213. package/.next/standalone/docs/hi/cli/environment-variables.mdx +0 -34
  214. package/.next/standalone/docs/hi/cli/hook.mdx +0 -30
  215. package/.next/standalone/docs/hi/cli/install-policies.mdx +0 -47
  216. package/.next/standalone/docs/hi/cli/list-policies.mdx +0 -31
  217. package/.next/standalone/docs/hi/cli/remove-policies.mdx +0 -43
  218. package/.next/standalone/docs/hi/cli/version.mdx +0 -12
  219. package/.next/standalone/docs/hi/configuration.mdx +0 -222
  220. package/.next/standalone/docs/hi/custom-policies.mdx +0 -354
  221. package/.next/standalone/docs/hi/dashboard.mdx +0 -142
  222. package/.next/standalone/docs/hi/examples.mdx +0 -309
  223. package/.next/standalone/docs/hi/for-agents.mdx +0 -38
  224. package/.next/standalone/docs/hi/getting-started.mdx +0 -187
  225. package/.next/standalone/docs/hi/introduction.mdx +0 -57
  226. package/.next/standalone/docs/hi/package-aliases.mdx +0 -82
  227. package/.next/standalone/docs/hi/testing.mdx +0 -260
  228. package/.next/standalone/docs/i18n/README.ar.md +0 -312
  229. package/.next/standalone/docs/i18n/README.de.md +0 -307
  230. package/.next/standalone/docs/i18n/README.es.md +0 -307
  231. package/.next/standalone/docs/i18n/README.fr.md +0 -307
  232. package/.next/standalone/docs/i18n/README.he.md +0 -312
  233. package/.next/standalone/docs/i18n/README.hi.md +0 -307
  234. package/.next/standalone/docs/i18n/README.it.md +0 -307
  235. package/.next/standalone/docs/i18n/README.ja.md +0 -307
  236. package/.next/standalone/docs/i18n/README.ko.md +0 -307
  237. package/.next/standalone/docs/i18n/README.pt-br.md +0 -307
  238. package/.next/standalone/docs/i18n/README.ru.md +0 -308
  239. package/.next/standalone/docs/i18n/README.tr.md +0 -307
  240. package/.next/standalone/docs/i18n/README.vi.md +0 -307
  241. package/.next/standalone/docs/i18n/README.zh.md +0 -307
  242. package/.next/standalone/docs/introduction.mdx +0 -57
  243. package/.next/standalone/docs/it/architecture.mdx +0 -334
  244. package/.next/standalone/docs/it/built-in-policies.mdx +0 -574
  245. package/.next/standalone/docs/it/cli/dashboard.mdx +0 -28
  246. package/.next/standalone/docs/it/cli/environment-variables.mdx +0 -34
  247. package/.next/standalone/docs/it/cli/hook.mdx +0 -30
  248. package/.next/standalone/docs/it/cli/install-policies.mdx +0 -47
  249. package/.next/standalone/docs/it/cli/list-policies.mdx +0 -31
  250. package/.next/standalone/docs/it/cli/remove-policies.mdx +0 -43
  251. package/.next/standalone/docs/it/cli/version.mdx +0 -12
  252. package/.next/standalone/docs/it/configuration.mdx +0 -222
  253. package/.next/standalone/docs/it/custom-policies.mdx +0 -353
  254. package/.next/standalone/docs/it/dashboard.mdx +0 -142
  255. package/.next/standalone/docs/it/examples.mdx +0 -307
  256. package/.next/standalone/docs/it/for-agents.mdx +0 -38
  257. package/.next/standalone/docs/it/getting-started.mdx +0 -186
  258. package/.next/standalone/docs/it/introduction.mdx +0 -57
  259. package/.next/standalone/docs/it/package-aliases.mdx +0 -82
  260. package/.next/standalone/docs/it/testing.mdx +0 -260
  261. package/.next/standalone/docs/ja/architecture.mdx +0 -332
  262. package/.next/standalone/docs/ja/built-in-policies.mdx +0 -572
  263. package/.next/standalone/docs/ja/cli/dashboard.mdx +0 -28
  264. package/.next/standalone/docs/ja/cli/environment-variables.mdx +0 -34
  265. package/.next/standalone/docs/ja/cli/hook.mdx +0 -30
  266. package/.next/standalone/docs/ja/cli/install-policies.mdx +0 -47
  267. package/.next/standalone/docs/ja/cli/list-policies.mdx +0 -31
  268. package/.next/standalone/docs/ja/cli/remove-policies.mdx +0 -43
  269. package/.next/standalone/docs/ja/cli/version.mdx +0 -12
  270. package/.next/standalone/docs/ja/configuration.mdx +0 -222
  271. package/.next/standalone/docs/ja/custom-policies.mdx +0 -353
  272. package/.next/standalone/docs/ja/dashboard.mdx +0 -142
  273. package/.next/standalone/docs/ja/examples.mdx +0 -307
  274. package/.next/standalone/docs/ja/for-agents.mdx +0 -38
  275. package/.next/standalone/docs/ja/getting-started.mdx +0 -186
  276. package/.next/standalone/docs/ja/introduction.mdx +0 -57
  277. package/.next/standalone/docs/ja/package-aliases.mdx +0 -82
  278. package/.next/standalone/docs/ja/testing.mdx +0 -260
  279. package/.next/standalone/docs/ko/architecture.mdx +0 -332
  280. package/.next/standalone/docs/ko/built-in-policies.mdx +0 -572
  281. package/.next/standalone/docs/ko/cli/dashboard.mdx +0 -28
  282. package/.next/standalone/docs/ko/cli/environment-variables.mdx +0 -34
  283. package/.next/standalone/docs/ko/cli/hook.mdx +0 -30
  284. package/.next/standalone/docs/ko/cli/install-policies.mdx +0 -47
  285. package/.next/standalone/docs/ko/cli/list-policies.mdx +0 -31
  286. package/.next/standalone/docs/ko/cli/remove-policies.mdx +0 -43
  287. package/.next/standalone/docs/ko/cli/version.mdx +0 -12
  288. package/.next/standalone/docs/ko/configuration.mdx +0 -222
  289. package/.next/standalone/docs/ko/custom-policies.mdx +0 -353
  290. package/.next/standalone/docs/ko/dashboard.mdx +0 -142
  291. package/.next/standalone/docs/ko/examples.mdx +0 -307
  292. package/.next/standalone/docs/ko/for-agents.mdx +0 -38
  293. package/.next/standalone/docs/ko/getting-started.mdx +0 -186
  294. package/.next/standalone/docs/ko/introduction.mdx +0 -57
  295. package/.next/standalone/docs/ko/package-aliases.mdx +0 -82
  296. package/.next/standalone/docs/ko/testing.mdx +0 -260
  297. package/.next/standalone/docs/logo/dark.svg +0 -21
  298. package/.next/standalone/docs/logo/exosphere-dark.png +0 -0
  299. package/.next/standalone/docs/logo/exosphere-light.png +0 -0
  300. package/.next/standalone/docs/logo/light.svg +0 -21
  301. package/.next/standalone/docs/package-aliases.mdx +0 -82
  302. package/.next/standalone/docs/pt-br/architecture.mdx +0 -332
  303. package/.next/standalone/docs/pt-br/built-in-policies.mdx +0 -574
  304. package/.next/standalone/docs/pt-br/cli/dashboard.mdx +0 -28
  305. package/.next/standalone/docs/pt-br/cli/environment-variables.mdx +0 -34
  306. package/.next/standalone/docs/pt-br/cli/hook.mdx +0 -30
  307. package/.next/standalone/docs/pt-br/cli/install-policies.mdx +0 -47
  308. package/.next/standalone/docs/pt-br/cli/list-policies.mdx +0 -31
  309. package/.next/standalone/docs/pt-br/cli/remove-policies.mdx +0 -43
  310. package/.next/standalone/docs/pt-br/cli/version.mdx +0 -12
  311. package/.next/standalone/docs/pt-br/configuration.mdx +0 -222
  312. package/.next/standalone/docs/pt-br/custom-policies.mdx +0 -353
  313. package/.next/standalone/docs/pt-br/dashboard.mdx +0 -142
  314. package/.next/standalone/docs/pt-br/examples.mdx +0 -307
  315. package/.next/standalone/docs/pt-br/for-agents.mdx +0 -38
  316. package/.next/standalone/docs/pt-br/getting-started.mdx +0 -186
  317. package/.next/standalone/docs/pt-br/introduction.mdx +0 -57
  318. package/.next/standalone/docs/pt-br/package-aliases.mdx +0 -82
  319. package/.next/standalone/docs/pt-br/testing.mdx +0 -260
  320. package/.next/standalone/docs/ru/architecture.mdx +0 -333
  321. package/.next/standalone/docs/ru/built-in-policies.mdx +0 -574
  322. package/.next/standalone/docs/ru/cli/dashboard.mdx +0 -28
  323. package/.next/standalone/docs/ru/cli/environment-variables.mdx +0 -34
  324. package/.next/standalone/docs/ru/cli/hook.mdx +0 -30
  325. package/.next/standalone/docs/ru/cli/install-policies.mdx +0 -48
  326. package/.next/standalone/docs/ru/cli/list-policies.mdx +0 -32
  327. package/.next/standalone/docs/ru/cli/remove-policies.mdx +0 -43
  328. package/.next/standalone/docs/ru/cli/version.mdx +0 -12
  329. package/.next/standalone/docs/ru/configuration.mdx +0 -222
  330. package/.next/standalone/docs/ru/custom-policies.mdx +0 -354
  331. package/.next/standalone/docs/ru/dashboard.mdx +0 -142
  332. package/.next/standalone/docs/ru/examples.mdx +0 -309
  333. package/.next/standalone/docs/ru/for-agents.mdx +0 -38
  334. package/.next/standalone/docs/ru/getting-started.mdx +0 -186
  335. package/.next/standalone/docs/ru/introduction.mdx +0 -57
  336. package/.next/standalone/docs/ru/package-aliases.mdx +0 -82
  337. package/.next/standalone/docs/ru/testing.mdx +0 -260
  338. package/.next/standalone/docs/testing.mdx +0 -260
  339. package/.next/standalone/docs/tr/architecture.mdx +0 -333
  340. package/.next/standalone/docs/tr/built-in-policies.mdx +0 -574
  341. package/.next/standalone/docs/tr/cli/dashboard.mdx +0 -28
  342. package/.next/standalone/docs/tr/cli/environment-variables.mdx +0 -34
  343. package/.next/standalone/docs/tr/cli/hook.mdx +0 -30
  344. package/.next/standalone/docs/tr/cli/install-policies.mdx +0 -47
  345. package/.next/standalone/docs/tr/cli/list-policies.mdx +0 -31
  346. package/.next/standalone/docs/tr/cli/remove-policies.mdx +0 -44
  347. package/.next/standalone/docs/tr/cli/version.mdx +0 -12
  348. package/.next/standalone/docs/tr/configuration.mdx +0 -222
  349. package/.next/standalone/docs/tr/custom-policies.mdx +0 -353
  350. package/.next/standalone/docs/tr/dashboard.mdx +0 -142
  351. package/.next/standalone/docs/tr/examples.mdx +0 -308
  352. package/.next/standalone/docs/tr/for-agents.mdx +0 -38
  353. package/.next/standalone/docs/tr/getting-started.mdx +0 -186
  354. package/.next/standalone/docs/tr/introduction.mdx +0 -57
  355. package/.next/standalone/docs/tr/package-aliases.mdx +0 -82
  356. package/.next/standalone/docs/tr/testing.mdx +0 -260
  357. package/.next/standalone/docs/vi/architecture.mdx +0 -334
  358. package/.next/standalone/docs/vi/built-in-policies.mdx +0 -572
  359. package/.next/standalone/docs/vi/cli/dashboard.mdx +0 -28
  360. package/.next/standalone/docs/vi/cli/environment-variables.mdx +0 -34
  361. package/.next/standalone/docs/vi/cli/hook.mdx +0 -30
  362. package/.next/standalone/docs/vi/cli/install-policies.mdx +0 -47
  363. package/.next/standalone/docs/vi/cli/list-policies.mdx +0 -31
  364. package/.next/standalone/docs/vi/cli/remove-policies.mdx +0 -43
  365. package/.next/standalone/docs/vi/cli/version.mdx +0 -13
  366. package/.next/standalone/docs/vi/configuration.mdx +0 -222
  367. package/.next/standalone/docs/vi/custom-policies.mdx +0 -353
  368. package/.next/standalone/docs/vi/dashboard.mdx +0 -142
  369. package/.next/standalone/docs/vi/examples.mdx +0 -308
  370. package/.next/standalone/docs/vi/for-agents.mdx +0 -38
  371. package/.next/standalone/docs/vi/getting-started.mdx +0 -186
  372. package/.next/standalone/docs/vi/introduction.mdx +0 -57
  373. package/.next/standalone/docs/vi/package-aliases.mdx +0 -82
  374. package/.next/standalone/docs/vi/testing.mdx +0 -260
  375. package/.next/standalone/docs/zh/architecture.mdx +0 -332
  376. package/.next/standalone/docs/zh/built-in-policies.mdx +0 -570
  377. package/.next/standalone/docs/zh/cli/dashboard.mdx +0 -28
  378. package/.next/standalone/docs/zh/cli/environment-variables.mdx +0 -34
  379. package/.next/standalone/docs/zh/cli/hook.mdx +0 -30
  380. package/.next/standalone/docs/zh/cli/install-policies.mdx +0 -47
  381. package/.next/standalone/docs/zh/cli/list-policies.mdx +0 -31
  382. package/.next/standalone/docs/zh/cli/remove-policies.mdx +0 -43
  383. package/.next/standalone/docs/zh/cli/version.mdx +0 -12
  384. package/.next/standalone/docs/zh/configuration.mdx +0 -222
  385. package/.next/standalone/docs/zh/custom-policies.mdx +0 -353
  386. package/.next/standalone/docs/zh/dashboard.mdx +0 -142
  387. package/.next/standalone/docs/zh/examples.mdx +0 -307
  388. package/.next/standalone/docs/zh/for-agents.mdx +0 -38
  389. package/.next/standalone/docs/zh/getting-started.mdx +0 -186
  390. package/.next/standalone/docs/zh/introduction.mdx +0 -57
  391. package/.next/standalone/docs/zh/package-aliases.mdx +0 -82
  392. package/.next/standalone/docs/zh/testing.mdx +0 -260
  393. package/.next/standalone/eslint.config.mjs +0 -15
  394. package/.next/standalone/examples/convention-policies/security-policies.mjs +0 -40
  395. package/.next/standalone/examples/convention-policies/workflow-policies.mjs +0 -41
  396. package/.next/standalone/examples/policies-advanced/index.js +0 -103
  397. package/.next/standalone/examples/policies-advanced/utils.js +0 -35
  398. package/.next/standalone/examples/policies-basic.js +0 -77
  399. package/.next/standalone/examples/policies-notification.js +0 -104
  400. package/.next/standalone/node_modules/@img/colour/color.cjs +0 -1594
  401. package/.next/standalone/node_modules/@img/colour/index.cjs +0 -1
  402. package/.next/standalone/node_modules/@img/colour/package.json +0 -45
  403. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +0 -46
  404. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  405. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +0 -1
  406. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  407. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +0 -42
  408. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/versions.json +0 -30
  409. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  410. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  411. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  412. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  413. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  414. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  415. package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  416. package/.next/standalone/node_modules/@img/sharp-linux-x64/package.json +0 -46
  417. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  418. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  419. package/.next/standalone/node_modules/detect-libc/lib/detect-libc.js +0 -313
  420. package/.next/standalone/node_modules/detect-libc/lib/elf.js +0 -39
  421. package/.next/standalone/node_modules/detect-libc/lib/filesystem.js +0 -51
  422. package/.next/standalone/node_modules/detect-libc/lib/process.js +0 -24
  423. package/.next/standalone/node_modules/detect-libc/package.json +0 -44
  424. package/.next/standalone/node_modules/sharp/lib/channel.js +0 -177
  425. package/.next/standalone/node_modules/sharp/lib/colour.js +0 -195
  426. package/.next/standalone/node_modules/sharp/lib/composite.js +0 -212
  427. package/.next/standalone/node_modules/sharp/lib/constructor.js +0 -499
  428. package/.next/standalone/node_modules/sharp/lib/index.js +0 -16
  429. package/.next/standalone/node_modules/sharp/lib/input.js +0 -809
  430. package/.next/standalone/node_modules/sharp/lib/is.js +0 -143
  431. package/.next/standalone/node_modules/sharp/lib/libvips.js +0 -207
  432. package/.next/standalone/node_modules/sharp/lib/operation.js +0 -1016
  433. package/.next/standalone/node_modules/sharp/lib/output.js +0 -1666
  434. package/.next/standalone/node_modules/sharp/lib/resize.js +0 -595
  435. package/.next/standalone/node_modules/sharp/lib/sharp.js +0 -121
  436. package/.next/standalone/node_modules/sharp/lib/utility.js +0 -291
  437. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/comparator.js +0 -143
  438. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/range.js +0 -557
  439. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/semver.js +0 -333
  440. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/cmp.js +0 -54
  441. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/coerce.js +0 -62
  442. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/compare.js +0 -7
  443. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/eq.js +0 -5
  444. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gt.js +0 -5
  445. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gte.js +0 -5
  446. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lt.js +0 -5
  447. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lte.js +0 -5
  448. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/neq.js +0 -5
  449. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/parse.js +0 -18
  450. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/satisfies.js +0 -12
  451. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/constants.js +0 -37
  452. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/debug.js +0 -11
  453. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/identifiers.js +0 -29
  454. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/lrucache.js +0 -42
  455. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/parse-options.js +0 -17
  456. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/re.js +0 -223
  457. package/.next/standalone/node_modules/sharp/node_modules/semver/package.json +0 -78
  458. package/.next/standalone/node_modules/sharp/package.json +0 -202
  459. package/.next/standalone/scripts/alias-proxy.js +0 -18
  460. package/.next/standalone/scripts/dev.ts +0 -3
  461. package/.next/standalone/scripts/install-telemetry.mjs +0 -108
  462. package/.next/standalone/scripts/launch.ts +0 -83
  463. package/.next/standalone/scripts/parse-script-args.ts +0 -87
  464. package/.next/standalone/scripts/postinstall.mjs +0 -121
  465. package/.next/standalone/scripts/preuninstall.mjs +0 -131
  466. package/.next/standalone/scripts/publish-aliases.mjs +0 -87
  467. package/.next/standalone/scripts/start.ts +0 -3
  468. package/.next/standalone/scripts/sync-hook-events-prompt.md +0 -60
  469. package/.next/standalone/scripts/translate-docs/cache.ts +0 -62
  470. package/.next/standalone/scripts/translate-docs/cli.ts +0 -357
  471. package/.next/standalone/scripts/translate-docs/config.ts +0 -248
  472. package/.next/standalone/scripts/translate-docs/mdx-translator.ts +0 -153
  473. package/.next/standalone/scripts/translate-docs/mintlify-nav.ts +0 -107
  474. package/.next/standalone/scripts/translate-docs/readme-translator.ts +0 -154
  475. package/.next/standalone/scripts/translate-docs/translator.ts +0 -68
  476. package/.next/standalone/scripts/translate-docs/types.ts +0 -43
  477. package/.next/standalone/src/auth/login.ts +0 -104
  478. package/.next/standalone/src/auth/logout.ts +0 -50
  479. package/.next/standalone/src/auth/token-store.ts +0 -64
  480. package/.next/standalone/src/cli-error.ts +0 -18
  481. package/.next/standalone/src/hooks/builtin-policies.ts +0 -1615
  482. package/.next/standalone/src/hooks/custom-hooks-loader.ts +0 -205
  483. package/.next/standalone/src/hooks/custom-hooks-registry.ts +0 -30
  484. package/.next/standalone/src/hooks/handler.ts +0 -222
  485. package/.next/standalone/src/hooks/hook-activity-store.ts +0 -349
  486. package/.next/standalone/src/hooks/hook-logger.ts +0 -133
  487. package/.next/standalone/src/hooks/hook-telemetry.ts +0 -43
  488. package/.next/standalone/src/hooks/hooks-config.ts +0 -166
  489. package/.next/standalone/src/hooks/install-prompt.ts +0 -357
  490. package/.next/standalone/src/hooks/llm-client.ts +0 -90
  491. package/.next/standalone/src/hooks/loader-utils.ts +0 -178
  492. package/.next/standalone/src/hooks/manager.ts +0 -692
  493. package/.next/standalone/src/hooks/policy-evaluator.ts +0 -224
  494. package/.next/standalone/src/hooks/policy-helpers.ts +0 -16
  495. package/.next/standalone/src/hooks/policy-registry.ts +0 -90
  496. package/.next/standalone/src/hooks/policy-types.ts +0 -77
  497. package/.next/standalone/src/hooks/types.ts +0 -63
  498. package/.next/standalone/src/index.ts +0 -19
  499. package/.next/standalone/src/posthog-key.ts +0 -5
  500. package/.next/standalone/src/relay/daemon.ts +0 -362
  501. package/.next/standalone/src/relay/pid.ts +0 -76
  502. package/.next/standalone/src/relay/queue.ts +0 -225
  503. package/.next/standalone/tailwind.config.ts +0 -11
  504. package/.next/standalone/tsconfig.json +0 -42
  505. package/.next/standalone/vitest.config.e2e.mts +0 -24
  506. package/.next/standalone/vitest.config.mts +0 -23
  507. /package/.next/standalone/.next/static/{r-wX0MuAfCjbhJm3phQc8 → wOkJXoch1UmRAmyIuKZWc}/_buildManifest.js +0 -0
  508. /package/.next/standalone/.next/static/{r-wX0MuAfCjbhJm3phQc8 → wOkJXoch1UmRAmyIuKZWc}/_clientMiddlewareManifest.js +0 -0
  509. /package/.next/standalone/.next/static/{r-wX0MuAfCjbhJm3phQc8 → wOkJXoch1UmRAmyIuKZWc}/_ssgManifest.js +0 -0
@@ -1,1615 +0,0 @@
1
- /**
2
- * Built-in security policies for Claude Code hooks.
3
- */
4
- import { resolve, join } from "node:path";
5
- import { readFile, writeFile, stat, open } from "node:fs/promises";
6
- import { execSync, execFileSync } from "node:child_process";
7
- import { homedir } from "node:os";
8
- import type { BuiltinPolicyDefinition, PolicyContext, PolicyResult, PolicyParamsSchema } from "./policy-types";
9
- import { allow, deny, instruct } from "./policy-helpers";
10
- import { registerPolicy } from "./policy-registry";
11
- import { hookLogWarn } from "./hook-logger";
12
-
13
- function isClaudeInternalPath(resolved: string): boolean {
14
- const claudeDir = join(homedir(), ".claude");
15
- return resolved === claudeDir || resolved.startsWith(claudeDir + "/");
16
- }
17
-
18
- function isClaudeSettingsFile(resolved: string): boolean {
19
- return /[\\/]\.claude[\\/]settings(?:\.[^/\\]+)?\.json$/.test(resolved);
20
- }
21
-
22
- function getCommand(ctx: PolicyContext): string {
23
- return (ctx.toolInput?.command as string) ?? "";
24
- }
25
-
26
- function getFilePath(ctx: PolicyContext): string {
27
- return (ctx.toolInput?.file_path as string) ?? "";
28
- }
29
-
30
- /**
31
- * Parse a command string into argv tokens for safe pattern matching.
32
- * Splits on whitespace and strips simple single/double quotes.
33
- * Does not handle all shell syntax — sufficient for prefix-match allowlists.
34
- */
35
- function parseArgvTokens(cmd: string): string[] {
36
- return cmd.trim().split(/\s+/).map((t) => t.replace(/^['"]|['"]$/g, ""));
37
- }
38
-
39
- // Shell operators that always act as command separators when whitespace-delimited.
40
- const SHELL_OPERATORS = new Set(["&&", "||", "|", ";"]);
41
-
42
- // Shell metacharacters that are unsafe when embedded inside a token. Any command
43
- // whose argv contains one of these in a token is rejected before allowlist matching.
44
- // This closes the bypass where operators are glued to a word (e.g. "nginx;evil" or
45
- // "nginx&&evil") and would otherwise be invisible to the standalone-operator check.
46
- // Note: | is intentionally excluded here because "foo|bar" is a valid grep/sed
47
- // argument value; the standalone-operator check above already handles bare "|" tokens.
48
- const SHELL_METACHAR_RE = /[;&<>`$()\\]/;
49
-
50
- // -- Pre-compiled regex constants (hoisted to avoid per-call allocation) --
51
-
52
- // sanitizeJwt
53
- const JWT_RE = /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/;
54
-
55
- // sanitizeApiKeys
56
- const API_KEY_PATTERNS: Array<[RegExp, string]> = [
57
- [/sk-ant-[A-Za-z0-9\-_]{20,}/, "Anthropic API key"],
58
- [/sk-proj-[A-Za-z0-9\-_]{20,}/, "OpenAI project API key"],
59
- [/sk-[A-Za-z0-9]{20,}/, "OpenAI API key"],
60
- [/ghp_[A-Za-z0-9]{36}/, "GitHub personal access token"],
61
- [/github_pat_[A-Za-z0-9_]{82}/, "GitHub fine-grained token"],
62
- [/AKIA[A-Z0-9]{16}/, "AWS access key ID"],
63
- [/sk_live_[A-Za-z0-9]{24,}/, "Stripe live secret key"],
64
- [/sk_test_[A-Za-z0-9]{24,}/, "Stripe test secret key"],
65
- [/AIza[0-9A-Za-z\-_]{35}/, "Google API key"],
66
- ];
67
-
68
- // sanitizeConnectionStrings
69
- const CONNECTION_STRING_RE = /(?:postgresql|postgres|mysql|mongodb(?:\+srv)?|redis|amqps?|smtps?):\/\/[^@\s]+@/;
70
-
71
- // sanitizePrivateKeyContent
72
- const PRIVATE_KEY_RE = /-----BEGIN (?:[A-Z]+ )?PRIVATE KEY-----/;
73
-
74
- // sanitizeBearerTokens
75
- const BEARER_TOKEN_RE = /Authorization:\s*Bearer\s+[A-Za-z0-9\-._~+/]{20,}/i;
76
-
77
- // warnDestructiveSql / warnSchemaAlteration
78
- const SQL_TOOL_RE = /\b(?:psql|mysql|sqlite3|pgcli|clickhouse-client)\b/;
79
- const DESTRUCTIVE_SQL_RE = /\b(?:DROP\s+(?:TABLE|DATABASE|SCHEMA)|TRUNCATE\b)/i;
80
- const DELETE_NO_WHERE_RE = /\bDELETE\s+FROM\b/i;
81
- const SQL_WHERE_RE = /\bWHERE\b/i;
82
- const SCHEMA_ALTER_RE = /\bALTER\s+TABLE\b[\s\S]*\b(?:DROP\s+COLUMN|ADD\s+COLUMN|RENAME\s+(?:COLUMN|TO)|MODIFY\s+COLUMN)\b/i;
83
-
84
- // warnPackagePublish
85
- const PUBLISH_CMD_RE = /(?:npm\s+publish|bun\s+publish|pnpm\s+publish|yarn\s+npm\s+publish|twine\s+upload|poetry\s+publish|cargo\s+publish|gem\s+push)\b/;
86
-
87
- // protectEnvVars
88
- const ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
89
- const ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
90
- const EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
91
- const PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
92
- const PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
93
- const DOTNET_GETENV_RE = /\[Environment\]::GetEnvironment/i;
94
- const CMD_ECHO_ENV_RE = /echo\s+%[A-Za-z_]/i;
95
-
96
- // blockEnvFiles
97
- const ENV_FILE_PATH_RE = /(?:^|[\\/])\.env(?:\.|$)/;
98
- const ENV_CMD_RE = /\.env(?:\b|\s|$|\.)/;
99
-
100
- // blockSudo
101
- const SUDO_RE = /(?:^|;|&&|\|\|)\s*sudo\s/;
102
- const PS_ELEVATION_RE = /Start-Process\s+.*-Verb\s+RunAs/i;
103
- const RUNAS_RE = /(?:^|;|&&|\|\|)\s*runas\s/i;
104
-
105
- // blockCurlPipeSh
106
- const CURL_PIPE_SH_RE = /(?:curl|wget)\s.*\|\s*(?:sh|bash|zsh|dash|ksh|csh|tcsh|fish|ash)\b/;
107
- const PS_WEB_PIPE_RE = /(?:Invoke-WebRequest|iwr|Invoke-RestMethod|irm)\s+.*\|\s*(?:Invoke-Expression|iex)/i;
108
-
109
- // blockForcePush
110
- const FORCE_PUSH_RE = /(?:--force|-f\b)/;
111
-
112
- // blockSecretsWrite
113
- const SECRET_FILE_RE = /\.(?:pem|key)$/;
114
- const SECRET_FILE_ID_RSA_RE = /id_rsa/;
115
- const SECRET_FILE_CREDENTIALS_RE = /credentials/;
116
-
117
- // blockWorkOnMain
118
- const GIT_COMMIT_MERGE_RE = /git\s+(?:commit|merge|rebase|cherry-pick)\b/;
119
-
120
- // blockFailproofaiCommands
121
- const FAILPROOFAI_CLI_RE = /(?:^|;|&&|\|\||\|)\s*failproofai(?:\s|$)/;
122
- const FAILPROOFAI_UNINSTALL_RE = /(?:npm\s+(?:uninstall|remove|un|r)\s.*failproofai|bun\s+remove\s.*failproofai|yarn\s+global\s+remove\s+failproofai|pnpm\s+(?:remove|uninstall|un)\s.*failproofai)/;
123
-
124
- // warnGitAmend
125
- const GIT_AMEND_RE = /\bgit\s+commit\b.*--amend\b/;
126
-
127
- // warnGitStashDrop
128
- const GIT_STASH_DROP_RE = /\bgit\s+stash\s+(?:drop|clear)\b/;
129
-
130
- // warnAllFilesStaged
131
- const GIT_ADD_ALL_RE = /\bgit\s+add\s+(?:-A\b|--all\b|\.(?:\s|$|;|&&|\|\|))/;
132
-
133
- // warnGlobalPackageInstall
134
- const NPM_GLOBAL_RE = /\bnpm\s+(?:install|i)\b(?=.*(?:\s-g\b|--global\b))/;
135
- const YARN_GLOBAL_RE = /\byarn\s+global\s+add\b/;
136
- const PNPM_GLOBAL_RE = /\bpnpm\s+(?:add|install|i)\b(?=.*(?:\s-g\b|--global\b))/;
137
- const BUN_GLOBAL_RE = /\bbun\s+(?:install|add)\b(?=.*(?:\s-g\b|--global\b))/;
138
- const CARGO_INSTALL_RE = /\bcargo\s+install\b/;
139
- const PIP_SYSTEM_RE = /\bpip(?:3)?\s+install\b(?=.*(?:--user\b|--break-system-packages\b))/;
140
-
141
- // preferPackageManager — maps manager name → detection patterns
142
- const PKG_MANAGER_DETECTORS: Record<string, RegExp[]> = {
143
- pip: [/\bpip\b/, /\bpip3\b/, /\bpython3?\s+-m\s+pip\b/],
144
- npm: [/\bnpm\b/, /\bnpx\b/],
145
- yarn: [/\byarn\b/],
146
- pnpm: [/\bpnpm\b/, /\bpnpx\b/],
147
- bun: [/\bbun\b/, /\bbunx\b/],
148
- uv: [/\buv\b/],
149
- poetry: [/\bpoetry\b/],
150
- pipenv: [/\bpipenv\b/],
151
- conda: [/\bconda\b/],
152
- cargo: [/\bcargo\b/],
153
- };
154
-
155
- // warnBackgroundProcess
156
- const NOHUP_RE = /\bnohup\s+\S/;
157
- const SCREEN_DETACH_RE = /\bscreen\s+-[A-Za-z]*d[A-Za-z]*\b/;
158
- const TMUX_DETACH_RE = /\btmux\s+(?:new-session|new)\b[^|&;]*-d\b/;
159
- const DISOWN_RE = /\bdisown\b/;
160
- const BACKGROUND_AMPERSAND_RE = /(?<![&|])\s?&\s*(?:$|#|;)/;
161
-
162
- // Caches the current branch per cwd to avoid repeated execSync calls.
163
- // Trade-off: if the user switches branches externally mid-session, the cache serves
164
- // the stale value until the process restarts. This is acceptable since branch switches
165
- // during an active Claude session are rare.
166
- const gitBranchCache = new Map<string, string>();
167
-
168
- function getCurrentBranch(cwd: string): string | null {
169
- try {
170
- let branch = gitBranchCache.get(cwd);
171
- if (branch === undefined) {
172
- branch = execSync("git rev-parse --abbrev-ref HEAD", {
173
- cwd,
174
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
175
- timeout: 3000,
176
- }).trim();
177
- gitBranchCache.set(cwd, branch);
178
- }
179
- return branch || null;
180
- } catch {
181
- return null;
182
- }
183
- }
184
-
185
- function getHeadSha(cwd: string): string | null {
186
- try {
187
- const sha = execSync("git rev-parse HEAD", {
188
- cwd,
189
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
190
- timeout: 3000,
191
- }).trim();
192
- return sha || null;
193
- } catch {
194
- return null;
195
- }
196
- }
197
-
198
- interface CiCheck {
199
- name: string;
200
- status: string;
201
- conclusion: string;
202
- }
203
-
204
- /** Fetch third-party check runs (non-GitHub-Actions) for a commit via the Checks API. */
205
- function getThirdPartyCheckRuns(cwd: string, sha: string): CiCheck[] {
206
- try {
207
- const json = execFileSync(
208
- "gh",
209
- [
210
- "api",
211
- `repos/{owner}/{repo}/commits/${sha}/check-runs`,
212
- "--jq",
213
- '.check_runs | map(select(.app.slug != "github-actions")) | map({name: .name, status: .status, conclusion: (.conclusion // "")})',
214
- ],
215
- {
216
- cwd,
217
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
218
- timeout: 15000,
219
- },
220
- ).trim();
221
-
222
- if (!json || json === "[]") return [];
223
- return JSON.parse(json) as CiCheck[];
224
- } catch {
225
- return [];
226
- }
227
- }
228
-
229
- /** Fetch commit statuses (legacy Status API) and normalize to CiCheck format. */
230
- function getCommitStatuses(cwd: string, sha: string): CiCheck[] {
231
- try {
232
- const json = execFileSync(
233
- "gh",
234
- [
235
- "api",
236
- `repos/{owner}/{repo}/commits/${sha}/statuses`,
237
- "--jq",
238
- 'map({name: .context, state: .state}) | unique_by(.name)',
239
- ],
240
- {
241
- cwd,
242
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
243
- timeout: 15000,
244
- },
245
- ).trim();
246
-
247
- if (!json || json === "[]") return [];
248
- const statuses = JSON.parse(json) as Array<{ name: string; state: string }>;
249
- return statuses.map((s) => ({
250
- name: s.name,
251
- status: s.state === "pending" ? "in_progress" : "completed",
252
- conclusion: s.state === "pending" ? "" : s.state === "success" ? "success" : "failure",
253
- }));
254
- } catch {
255
- return [];
256
- }
257
- }
258
-
259
- /**
260
- * Check if a command matches an allow pattern using token-by-token comparison.
261
- * The "*" token is a wildcard. Extra command tokens beyond the pattern are allowed,
262
- * UNLESS any token is a standalone shell operator (&&, ||, |, ;) OR contains an
263
- * embedded shell metacharacter — both cases are rejected to prevent bypass via
264
- * appended sub-commands or glued operators (e.g. "nginx;" or "nginx;evil").
265
- */
266
- function matchesAllowedPattern(cmd: string, pattern: string): boolean {
267
- const cmdTokens = parseArgvTokens(cmd);
268
- const patTokens = parseArgvTokens(pattern);
269
- if (cmdTokens.length < patTokens.length) return false;
270
- // Reject commands containing standalone shell-operator tokens
271
- if (cmdTokens.some((tok) => SHELL_OPERATORS.has(tok))) return false;
272
- // Reject any token containing embedded shell metacharacters
273
- if (cmdTokens.some((tok) => SHELL_METACHAR_RE.test(tok))) return false;
274
- return patTokens.every((tok, i) => tok === "*" || tok === cmdTokens[i]);
275
- }
276
-
277
- // -- Policy implementations --
278
-
279
- function sanitizeJwt(ctx: PolicyContext): PolicyResult {
280
- // PostToolUse: scrub JWT patterns from tool output
281
- const output = JSON.stringify(ctx.payload);
282
- if (JWT_RE.test(output)) {
283
- return {
284
- decision: "deny",
285
- reason: "JWT token detected in tool output",
286
- message: "[REDACTED: JWT token removed by failproofai]",
287
- };
288
- }
289
- return allow();
290
- }
291
-
292
- function sanitizeApiKeys(ctx: PolicyContext): PolicyResult {
293
- // PostToolUse: scrub common API key patterns from tool output
294
- const output = JSON.stringify(ctx.payload);
295
- for (const [pattern, label] of API_KEY_PATTERNS) {
296
- if (pattern.test(output)) {
297
- return {
298
- decision: "deny",
299
- reason: `${label} detected in tool output`,
300
- message: `[REDACTED: ${label} removed by failproofai]`,
301
- };
302
- }
303
- }
304
-
305
- // Check additional user-configured patterns
306
- const additional = ((ctx.params?.additionalPatterns ?? []) as Array<{ regex: string; label: string }>);
307
- for (const { regex, label } of additional) {
308
- try {
309
- if (new RegExp(regex).test(output)) {
310
- return {
311
- decision: "deny",
312
- reason: `${label} detected in tool output`,
313
- message: `[REDACTED: ${label} removed by failproofai]`,
314
- };
315
- }
316
- } catch {
317
- hookLogWarn(`additionalPatterns: invalid regex "${regex}", skipping`);
318
- }
319
- }
320
-
321
- return allow();
322
- }
323
-
324
- function sanitizeConnectionStrings(ctx: PolicyContext): PolicyResult {
325
- // PostToolUse: scrub database connection strings with embedded credentials
326
- const output = JSON.stringify(ctx.payload);
327
- if (CONNECTION_STRING_RE.test(output)) {
328
- return {
329
- decision: "deny",
330
- reason: "Database connection string with credentials detected in tool output",
331
- message: "[REDACTED: connection string removed by failproofai]",
332
- };
333
- }
334
- return allow();
335
- }
336
-
337
- function sanitizePrivateKeyContent(ctx: PolicyContext): PolicyResult {
338
- // PostToolUse: scrub PEM private key blocks from tool output
339
- const output = JSON.stringify(ctx.payload);
340
- if (PRIVATE_KEY_RE.test(output)) {
341
- return {
342
- decision: "deny",
343
- reason: "Private key content detected in tool output",
344
- message: "[REDACTED: private key content removed by failproofai]",
345
- };
346
- }
347
- return allow();
348
- }
349
-
350
- function sanitizeBearerTokens(ctx: PolicyContext): PolicyResult {
351
- // PostToolUse: scrub Authorization: Bearer tokens from tool output
352
- const output = JSON.stringify(ctx.payload);
353
- if (BEARER_TOKEN_RE.test(output)) {
354
- return {
355
- decision: "deny",
356
- reason: "Bearer token detected in tool output",
357
- message: "[REDACTED: Bearer token removed by failproofai]",
358
- };
359
- }
360
- return allow();
361
- }
362
-
363
- function warnDestructiveSql(ctx: PolicyContext): PolicyResult {
364
- if (ctx.toolName !== "Bash") return allow();
365
- const cmd = getCommand(ctx);
366
- if (!SQL_TOOL_RE.test(cmd)) return allow();
367
-
368
- // DROP or TRUNCATE always warns
369
- if (DESTRUCTIVE_SQL_RE.test(cmd)) {
370
- return instruct(
371
- "STOP: This command contains destructive SQL (DROP/TRUNCATE/DELETE). Confirm with the user before executing.",
372
- );
373
- }
374
-
375
- // DELETE FROM without WHERE warns
376
- if (DELETE_NO_WHERE_RE.test(cmd) && !SQL_WHERE_RE.test(cmd)) {
377
- return instruct(
378
- "STOP: This command contains destructive SQL (DROP/TRUNCATE/DELETE). Confirm with the user before executing.",
379
- );
380
- }
381
-
382
- return allow();
383
- }
384
-
385
- function warnLargeFileWrite(ctx: PolicyContext): PolicyResult {
386
- if (ctx.toolName !== "Write") return allow();
387
- const content = ctx.toolInput?.content as string | undefined;
388
- if (typeof content !== "string") return allow();
389
- const thresholdKb = ((ctx.params?.thresholdKb ?? 1024) as number);
390
- const thresholdBytes = thresholdKb * 1024;
391
- if (content.length > thresholdBytes) {
392
- return instruct(
393
- `STOP: You are writing a file larger than ${thresholdKb}KB (${Math.round(content.length / 1024)}KB). This is unusually large. Confirm this is intentional before proceeding.`,
394
- );
395
- }
396
- return allow();
397
- }
398
-
399
- function warnPackagePublish(ctx: PolicyContext): PolicyResult {
400
- if (ctx.toolName !== "Bash") return allow();
401
- const cmd = getCommand(ctx);
402
- if (PUBLISH_CMD_RE.test(cmd)) {
403
- return instruct(
404
- "STOP: This command publishes a package to a public registry. Confirm with the user that this is intentional.",
405
- );
406
- }
407
- return allow();
408
- }
409
-
410
- function protectEnvVars(ctx: PolicyContext): PolicyResult {
411
- if (ctx.toolName !== "Bash") return allow();
412
- const cmd = getCommand(ctx);
413
- // Block: env, printenv, echo $VAR, export VAR=
414
- if (ENV_PRINTENV_RE.test(cmd)) {
415
- return deny("Command reads environment variables");
416
- }
417
- if (ECHO_ENV_RE.test(cmd)) {
418
- return deny("Command echoes environment variable");
419
- }
420
- if (EXPORT_RE.test(cmd)) {
421
- return deny("Command exports environment variable");
422
- }
423
- // PowerShell: $env:VAR
424
- if (PS_ENV_VAR_RE.test(cmd)) {
425
- return deny("Command reads environment variable via PowerShell");
426
- }
427
- // PowerShell: Get-ChildItem Env: / dir env: / gci env: / ls env:
428
- if (PS_CHILDITEM_ENV_RE.test(cmd)) {
429
- return deny("Command reads environment variables via PowerShell");
430
- }
431
- // PowerShell: [Environment]::GetEnvironmentVariable
432
- if (DOTNET_GETENV_RE.test(cmd)) {
433
- return deny("Command reads environment variable via .NET");
434
- }
435
- // cmd: echo %VAR%
436
- if (CMD_ECHO_ENV_RE.test(cmd)) {
437
- return deny("Command echoes environment variable via cmd");
438
- }
439
- return allow();
440
- }
441
-
442
- function blockEnvFiles(ctx: PolicyContext): PolicyResult {
443
- const cmd = getCommand(ctx);
444
- const filePath = getFilePath(ctx);
445
-
446
- // Check file_path for Read/Write tools (match both / and \ path separators)
447
- if (filePath && ENV_FILE_PATH_RE.test(filePath)) {
448
- return deny("Access to .env file blocked");
449
- }
450
- // Check Bash commands referencing .env files
451
- if (ctx.toolName === "Bash" && ENV_CMD_RE.test(cmd)) {
452
- return deny("Command references .env file");
453
- }
454
- return allow();
455
- }
456
-
457
- function blockSudo(ctx: PolicyContext): PolicyResult {
458
- if (ctx.toolName !== "Bash") return allow();
459
- const cmd = getCommand(ctx).trimStart();
460
- if (SUDO_RE.test(cmd) || cmd.startsWith("sudo ")) {
461
- // Check allowPatterns — match against parsed tokens, not raw string
462
- const allowPatterns = ((ctx.params?.allowPatterns ?? []) as string[]);
463
- if (allowPatterns.some((p) => matchesAllowedPattern(cmd, p))) return allow();
464
- return deny("sudo commands are blocked");
465
- }
466
- // PowerShell: Start-Process -Verb RunAs (elevation)
467
- if (PS_ELEVATION_RE.test(cmd)) {
468
- return deny("Elevated process launch is blocked");
469
- }
470
- // Windows: runas command
471
- if (RUNAS_RE.test(cmd)) {
472
- return deny("runas elevation is blocked");
473
- }
474
- return allow();
475
- }
476
-
477
- function blockCurlPipeSh(ctx: PolicyContext): PolicyResult {
478
- if (ctx.toolName !== "Bash") return allow();
479
- const cmd = getCommand(ctx);
480
- if (CURL_PIPE_SH_RE.test(cmd)) {
481
- return deny("Piping downloads to shell is blocked");
482
- }
483
- // PowerShell: iwr | iex, irm | iex, Invoke-WebRequest | Invoke-Expression
484
- if (PS_WEB_PIPE_RE.test(cmd)) {
485
- return deny("Piping downloads to Invoke-Expression is blocked");
486
- }
487
- return allow();
488
- }
489
-
490
- function extractGitPushArgs(cmd: string): string[] {
491
- return cmd
492
- .split(/&&|\|\||[|;\n]/)
493
- .map((s) => s.trim())
494
- .filter((s) => /^git\s+push\s/.test(s))
495
- .map((s) => s.replace(/^git\s+push\s+/, ""));
496
- }
497
-
498
- function blockPushMaster(ctx: PolicyContext): PolicyResult {
499
- if (ctx.toolName !== "Bash") return allow();
500
- const protectedBranches = ((ctx.params?.protectedBranches ?? ["main", "master"]) as string[]);
501
- if (protectedBranches.length === 0) return allow();
502
- const args = extractGitPushArgs(getCommand(ctx));
503
- const branchPattern = new RegExp(`\\b(?:${protectedBranches.map((b) => b.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`);
504
- if (args.some((a) => branchPattern.test(a))) {
505
- return deny(`Pushing to ${protectedBranches.join("/")} is blocked`);
506
- }
507
- return allow();
508
- }
509
-
510
- /**
511
- * Check whether all *recursive* rm path targets in a command are under an allowlisted path.
512
- * Splits on shell operators first so that `/tmp` appearing in an unrelated
513
- * sub-command (e.g. `echo /tmp && rm -rf /`) does not trigger a false allow.
514
- * Uses path-boundary comparison so `/tmp` does not cover `/tmp2`.
515
- * Non-recursive rm segments (no -r/-R flag) are skipped — they pose no catastrophic risk.
516
- * Quoted paths with spaces are handled via a segment-level regex fallback.
517
- */
518
- function rmTargetIsAllowed(cmd: string, allowPaths: string[]): boolean {
519
- if (allowPaths.length === 0) return false;
520
- const segments = cmd
521
- .split(/&&|\|\||[|;\n]/)
522
- .map((s) => s.trim())
523
- .filter((s) => /\brm\b/.test(s));
524
- if (segments.length === 0) return false;
525
- for (const seg of segments) {
526
- const tokens = parseArgvTokens(seg);
527
- const rmIdx = tokens.findIndex((t) => t === "rm");
528
- if (rmIdx < 0) continue;
529
- // Only validate recursive rm segments — non-recursive rm has no catastrophic-deletion risk
530
- const flagTokens = tokens.slice(rmIdx + 1).filter((t) => /^-[^-]/.test(t));
531
- const longFlagsInSeg = tokens.slice(rmIdx + 1).filter((t) => /^--/.test(t));
532
- if (!/r/i.test(flagTokens.join("")) && !longFlagsInSeg.some(f => /^--recursive$/i.test(f))) continue;
533
- const pathArgs = tokens.slice(rmIdx + 1).filter((t) => !t.startsWith("-"));
534
- for (const target of pathArgs) {
535
- const normalized = target.replace(/\/\*$/, "").replace(/\/+$/, "") || "/";
536
- const covered = allowPaths.some((p) => {
537
- const np = p.replace(/\/+$/, "") || "/";
538
- return normalized === np || normalized.startsWith(np + "/");
539
- });
540
- if (!covered) {
541
- // Fallback: check the raw segment for quoted paths that contain spaces
542
- // (parseArgvTokens splits on whitespace, so "/tmp/my dir" becomes two tokens)
543
- const segCovered = allowPaths.some((p) => {
544
- const escaped = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
545
- return new RegExp(`${escaped}(?:[/"'\\s/*]|$)`).test(seg);
546
- });
547
- if (!segCovered) return false;
548
- }
549
- }
550
- }
551
- return true;
552
- }
553
-
554
- function blockRmRf(ctx: PolicyContext): PolicyResult {
555
- if (ctx.toolName !== "Bash") return allow();
556
- const cmd = getCommand(ctx);
557
- const hasDestructivePath = parseArgvTokens(cmd).some((token) => {
558
- const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
559
- return normalized === "/" || normalized === "~" || /^\/[A-Za-z_][\w.-]*$/.test(normalized);
560
- });
561
-
562
- // Combined flags in one token: rm -rf /, rm -fr /
563
- if (hasDestructivePath && (
564
- /rm\s+-[^\s]*r[^\s]*f[^\s]*/.test(cmd) ||
565
- /rm\s+-[^\s]*f[^\s]*r[^\s]*/.test(cmd)
566
- )) {
567
- const allowPaths = ((ctx.params?.allowPaths ?? []) as string[]);
568
- if (rmTargetIsAllowed(cmd, allowPaths)) return allow();
569
- return deny("Catastrophic deletion blocked");
570
- }
571
-
572
- // Separated flags: rm -r -f /, rm -f -r /, rm -r -v -f /, etc.
573
- if (hasDestructivePath && /\brm\b/.test(cmd)) {
574
- const tokens = parseArgvTokens(cmd);
575
- const shortFlags = tokens.filter((t) => /^-[^-]/.test(t)).join("");
576
- const longFlags = tokens.filter((t) => /^--/.test(t));
577
- const hasRecursive = /r/i.test(shortFlags) || longFlags.some(f => /^--recursive$/i.test(f));
578
- const hasForce = /f/.test(shortFlags) || longFlags.some(f => /^--force$/i.test(f));
579
- if (hasRecursive && hasForce) {
580
- const allowPaths = ((ctx.params?.allowPaths ?? []) as string[]);
581
- if (rmTargetIsAllowed(cmd, allowPaths)) return allow();
582
- return deny("Catastrophic deletion blocked");
583
- }
584
- }
585
- // PowerShell: Remove-Item -Recurse -Force on root/drive
586
- if (/Remove-Item\s+.*-Recurse.*-Force.*(?:[A-Z]:\\(?:\s|$)|\\\*)/i.test(cmd)) {
587
- return deny("Catastrophic deletion blocked");
588
- }
589
- // cmd: rd /s /q or rmdir /s /q on drive root
590
- if (/(?:rd|rmdir)\s+\/s\s+\/q\s+[A-Z]:\\/i.test(cmd)) {
591
- return deny("Catastrophic deletion blocked");
592
- }
593
- return allow();
594
- }
595
-
596
- function blockForcePush(ctx: PolicyContext): PolicyResult {
597
- if (ctx.toolName !== "Bash") return allow();
598
- const args = extractGitPushArgs(getCommand(ctx));
599
- if (args.some((a) => FORCE_PUSH_RE.test(a))) {
600
- return deny("Force-pushing is blocked");
601
- }
602
- return allow();
603
- }
604
-
605
- function blockSecretsWrite(ctx: PolicyContext): PolicyResult {
606
- if (ctx.toolName !== "Write") return allow();
607
- const filePath = getFilePath(ctx);
608
- if (SECRET_FILE_RE.test(filePath) || SECRET_FILE_ID_RSA_RE.test(filePath) || SECRET_FILE_CREDENTIALS_RE.test(filePath)) {
609
- return deny("Writing secret key files is blocked");
610
- }
611
- const additionalPatterns = ((ctx.params?.additionalPatterns ?? []) as string[]);
612
- for (const pattern of additionalPatterns) {
613
- if (filePath.includes(pattern)) {
614
- return deny(`Writing blocked file pattern: ${pattern}`);
615
- }
616
- }
617
- return allow();
618
- }
619
-
620
- /** Read-like commands that access file system contents. */
621
- const READ_LIKE_CMDS =
622
- /(?:^|;|&&|\|\||\|)\s*(?:ls|find|cat|head|tail|less|more|wc|file|stat|tree|du)\s/;
623
-
624
- /**
625
- * Extract absolute paths from a Bash command string.
626
- * Scans quoted strings only in the first pipeline segment (before the first
627
- * bare pipe) and only when the quoted content has no glob or regex metacharacters.
628
- * This catches `cat "/etc/passwd"` while avoiding false positives from grep
629
- * patterns and find glob patterns that appear in later pipeline stages.
630
- * Unquoted absolute paths are extracted from the whole command as before.
631
- */
632
- function extractAbsolutePaths(command: string): string[] {
633
- const paths: string[] = [];
634
- const pathRe = /(?<![a-zA-Z0-9_.\-~\\])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
635
-
636
- function addPaths(s: string): void {
637
- pathRe.lastIndex = 0;
638
- let m: RegExpExecArray | null;
639
- while ((m = pathRe.exec(s)) !== null) {
640
- let p = m[0];
641
- if (p === "~") p = homedir();
642
- else if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
643
- paths.push(p);
644
- }
645
- }
646
-
647
- // Find the index of the first bare pipe (not inside quotes) to limit quoted extraction.
648
- let firstBarePipe = command.length;
649
- let inDouble = false, inSingle = false;
650
- for (let i = 0; i < command.length; i++) {
651
- const c = command[i];
652
- if (c === '"' && !inSingle) inDouble = !inDouble;
653
- else if (c === "'" && !inDouble) inSingle = !inSingle;
654
- else if (c === "|" && !inDouble && !inSingle) { firstBarePipe = i; break; }
655
- }
656
-
657
- // Extract paths from quoted strings in the FIRST pipeline segment only,
658
- // and only when the content has no glob/regex metacharacters.
659
- const firstSegment = command.slice(0, firstBarePipe);
660
- const quotedRe = /"([^"]*)"|'([^']*)'/g;
661
- let qm: RegExpExecArray | null;
662
- while ((qm = quotedRe.exec(firstSegment)) !== null) {
663
- const content = qm[1] ?? qm[2] ?? "";
664
- // Skip patterns that contain glob or common regex metacharacters
665
- if (/[*?\[\]^$+()\\]/.test(content)) continue;
666
- addPaths(content);
667
- }
668
-
669
- // Extract from unquoted portions of the whole command (existing behaviour).
670
- const stripped = command
671
- .replace(/"[^"]*"/g, (m) => " ".repeat(m.length))
672
- .replace(/'[^']*'/g, (m) => " ".repeat(m.length));
673
- addPaths(stripped);
674
-
675
- return paths;
676
- }
677
-
678
- function blockReadOutsideCwd(ctx: PolicyContext): PolicyResult {
679
- // Prefer $CLAUDE_PROJECT_DIR (stable project root) over ctx.session.cwd,
680
- // which tracks the live shell CWD and drifts when Claude `cd`s into a subdir.
681
- const cwd = process.env.CLAUDE_PROJECT_DIR || ctx.session?.cwd;
682
- if (!cwd) return allow(); // Can't enforce without cwd
683
-
684
- const allowPaths = ((ctx.params?.allowPaths ?? []) as string[]);
685
-
686
- // For Bash tool: check read-like commands for absolute paths outside cwd
687
- if (ctx.toolName === "Bash") {
688
- const cmd = getCommand(ctx);
689
- if (!READ_LIKE_CMDS.test(cmd)) return allow();
690
-
691
- const paths = extractAbsolutePaths(cmd);
692
- const cwdWithSep = cwd.endsWith("/") ? cwd : cwd + "/";
693
- for (const p of paths) {
694
- const resolved = resolve(cwd, p);
695
- if (isClaudeSettingsFile(resolved)) {
696
- return deny(`Reading Claude settings file blocked: ${resolved}`);
697
- }
698
- if (isClaudeInternalPath(resolved)) continue; // Whitelist ~/.claude/
699
- if (resolved === "/dev/null") continue; // Harmless special file
700
- if (resolved !== cwd && !resolved.startsWith(cwdWithSep)) {
701
- if (allowPaths.some((ap) => resolved === ap || resolved.startsWith(ap.endsWith("/") ? ap : ap + "/"))) continue;
702
- return deny(`Bash read outside project directory blocked: ${resolved}`);
703
- }
704
- }
705
- return allow();
706
- }
707
-
708
- // For Read/Glob/Grep: existing file_path / path check
709
- const filePath = getFilePath(ctx);
710
- const searchPath = (ctx.toolInput?.path as string) ?? "";
711
-
712
- const target = filePath || searchPath;
713
- if (!target) return allow();
714
-
715
- const resolved = resolve(cwd, target);
716
-
717
- // Block settings files in any .claude directory before whitelisting
718
- if (isClaudeSettingsFile(resolved)) {
719
- return deny(`Reading Claude settings file blocked: ${resolved}`);
720
- }
721
-
722
- // Whitelist ~/.claude/ — Claude Code's own config, plans, memory, and settings
723
- if (isClaudeInternalPath(resolved)) return allow();
724
-
725
- // Whitelist /dev/null — harmless special file commonly used in shell commands
726
- if (resolved === "/dev/null") return allow();
727
-
728
- const cwdWithSep = cwd.endsWith("/") ? cwd : cwd + "/";
729
- if (resolved !== cwd && !resolved.startsWith(cwdWithSep)) {
730
- if (allowPaths.some((ap) => resolved === ap || resolved.startsWith(ap.endsWith("/") ? ap : ap + "/"))) return allow();
731
- return deny(`Access outside project directory blocked: ${resolved}`);
732
- }
733
- return allow();
734
- }
735
-
736
- function blockWorkOnMain(ctx: PolicyContext): PolicyResult {
737
- if (ctx.toolName !== "Bash") return allow();
738
- const cmd = getCommand(ctx);
739
- if (!GIT_COMMIT_MERGE_RE.test(cmd)) return allow();
740
-
741
- const cwd = ctx.session?.cwd;
742
- if (!cwd) return allow();
743
-
744
- const branch = getCurrentBranch(cwd);
745
- if (!branch) return allow();
746
-
747
- const protectedBranches = ((ctx.params?.protectedBranches ?? ["main", "master"]) as string[]);
748
- if (protectedBranches.includes(branch)) {
749
- return deny(
750
- `Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`,
751
- );
752
- }
753
- return allow();
754
- }
755
-
756
- function blockFailproofaiCommands(ctx: PolicyContext): PolicyResult {
757
- if (ctx.toolName !== "Bash") return allow();
758
- const cmd = getCommand(ctx);
759
-
760
- // Block direct failproofai CLI invocations
761
- if (FAILPROOFAI_CLI_RE.test(cmd)) {
762
- return deny("Running failproofai CLI commands is blocked");
763
- }
764
-
765
- // Block package-manager uninstallation of failproofai
766
- if (FAILPROOFAI_UNINSTALL_RE.test(cmd)) {
767
- return deny("Uninstalling failproofai is blocked");
768
- }
769
-
770
- return allow();
771
- }
772
-
773
- // Maximum size of the per-session tool-call sidecar before we stop updating it.
774
- // If exceeded, repeated-call detection degrades gracefully (allows through) rather
775
- // than growing the file unboundedly.
776
- const TOOL_CALL_TRACKER_MAX_BYTES = 65_536; // 64 KB
777
-
778
- async function warnRepeatedToolCalls(ctx: PolicyContext): Promise<PolicyResult> {
779
- const THRESHOLD = 3;
780
- const transcriptPath = ctx.session?.transcriptPath;
781
- if (!transcriptPath || !ctx.toolName || !ctx.toolInput) return allow();
782
-
783
- // Sidecar file tracks { fingerprint: count } — O(1) per call vs O(transcript) per call.
784
- const trackerPath = `${transcriptPath}.tool-calls.json`;
785
- const fingerprint = JSON.stringify({ tool: ctx.toolName, input: ctx.toolInput });
786
-
787
- let counts: Record<string, number> = {};
788
- try {
789
- const raw = await readFile(trackerPath, "utf8");
790
- counts = JSON.parse(raw) as Record<string, number>;
791
- } catch { /* first call or unreadable — start fresh */ }
792
-
793
- const prevCount = counts[fingerprint] ?? 0;
794
- if (prevCount >= THRESHOLD) {
795
- return instruct(
796
- `STOP: You have already called ${ctx.toolName} ${prevCount} times with identical parameters. This is wasteful and unproductive. Do NOT repeat this call — use a different approach or ask the user for clarification.`,
797
- );
798
- }
799
-
800
- counts[fingerprint] = prevCount + 1;
801
- try {
802
- const serialized = JSON.stringify(counts);
803
- if (serialized.length <= TOOL_CALL_TRACKER_MAX_BYTES) {
804
- await writeFile(trackerPath, serialized, "utf8");
805
- }
806
- } catch { /* non-fatal */ }
807
-
808
- return allow();
809
- }
810
-
811
- function warnGitAmend(ctx: PolicyContext): PolicyResult {
812
- if (ctx.toolName !== "Bash") return allow();
813
- const cmd = getCommand(ctx);
814
- if (GIT_AMEND_RE.test(cmd)) {
815
- return instruct(
816
- "STOP: This command amends the last commit, which rewrites git history. If this commit has already been pushed to a shared branch, this will cause divergence for other contributors. Confirm with the user before executing.",
817
- );
818
- }
819
- return allow();
820
- }
821
-
822
- function warnGitStashDrop(ctx: PolicyContext): PolicyResult {
823
- if (ctx.toolName !== "Bash") return allow();
824
- const cmd = getCommand(ctx);
825
- if (GIT_STASH_DROP_RE.test(cmd)) {
826
- return instruct(
827
- "STOP: This command permanently deletes stashed changes (git stash drop/clear). Stash entries cannot be recovered after deletion. Confirm with the user before executing.",
828
- );
829
- }
830
- return allow();
831
- }
832
-
833
- function warnAllFilesStaged(ctx: PolicyContext): PolicyResult {
834
- if (ctx.toolName !== "Bash") return allow();
835
- const cmd = getCommand(ctx);
836
- if (GIT_ADD_ALL_RE.test(cmd)) {
837
- return instruct(
838
- "STOP: This command stages all files in the working tree (git add -A / --all / .). This may inadvertently include build artifacts, generated files, or sensitive files not covered by .gitignore. Confirm with the user before executing.",
839
- );
840
- }
841
- return allow();
842
- }
843
-
844
- function warnSchemaAlteration(ctx: PolicyContext): PolicyResult {
845
- if (ctx.toolName !== "Bash") return allow();
846
- const cmd = getCommand(ctx);
847
- if (!SQL_TOOL_RE.test(cmd)) return allow();
848
- if (SCHEMA_ALTER_RE.test(cmd)) {
849
- return instruct(
850
- "STOP: This command contains a schema-altering SQL statement (ALTER TABLE with column or rename operation). Schema changes on production databases are irreversible or disruptive. Confirm with the user before executing.",
851
- );
852
- }
853
- return allow();
854
- }
855
-
856
- function warnGlobalPackageInstall(ctx: PolicyContext): PolicyResult {
857
- if (ctx.toolName !== "Bash") return allow();
858
- const cmd = getCommand(ctx);
859
- const isGlobal =
860
- NPM_GLOBAL_RE.test(cmd) ||
861
- YARN_GLOBAL_RE.test(cmd) ||
862
- PNPM_GLOBAL_RE.test(cmd) ||
863
- BUN_GLOBAL_RE.test(cmd) ||
864
- CARGO_INSTALL_RE.test(cmd) ||
865
- // Bare 'pip install' respects the active venv when one is present;
866
- // only flag explicit system-level flags (--user, --break-system-packages).
867
- PIP_SYSTEM_RE.test(cmd);
868
- if (isGlobal) {
869
- return instruct(
870
- "STOP: This command installs a package globally, which modifies the system-wide environment outside the project. This can conflict with other projects or system tools. Confirm with the user before executing.",
871
- );
872
- }
873
- return allow();
874
- }
875
-
876
- // Split a compound shell command into independent segments.
877
- const SEGMENT_SPLIT_RE = /\s*(?:&&|\|\||\||;)\s*/;
878
-
879
- function preferPackageManager(ctx: PolicyContext): PolicyResult {
880
- if (ctx.toolName !== "Bash") return allow();
881
- const cmd = getCommand(ctx);
882
- if (!cmd) return allow();
883
-
884
- const allowed = (ctx.params?.allowed ?? []) as string[];
885
- if (allowed.length === 0) return allow();
886
-
887
- const allowedSet = new Set(allowed.map((a) => a.toLowerCase()));
888
- const blocked = (ctx.params?.blocked ?? []) as string[];
889
- const allowedList = allowed.join(", ");
890
-
891
- // Evaluate each shell segment independently so that
892
- // "uv --version && pip install flask" correctly denies the pip segment.
893
- const segments = cmd.split(SEGMENT_SPLIT_RE);
894
-
895
- for (const segment of segments) {
896
- const trimmed = segment.trim();
897
- if (!trimmed) continue;
898
-
899
- // Check if this segment uses an allowed manager — if so, skip it.
900
- let segmentAllowed = false;
901
- for (const manager of allowedSet) {
902
- const patterns = PKG_MANAGER_DETECTORS[manager];
903
- if (!patterns) continue;
904
- for (const pattern of patterns) {
905
- if (pattern.test(trimmed)) { segmentAllowed = true; break; }
906
- }
907
- if (segmentAllowed) break;
908
- }
909
- if (segmentAllowed) continue;
910
-
911
- // Check if this segment uses a non-allowed builtin manager.
912
- for (const [manager, patterns] of Object.entries(PKG_MANAGER_DETECTORS)) {
913
- if (allowedSet.has(manager)) continue;
914
- for (const pattern of patterns) {
915
- if (pattern.test(trimmed)) {
916
- return deny(
917
- `"${manager}" is not an allowed package manager. ` +
918
- `Allowed package managers for this project: ${allowedList}. ` +
919
- `Rewrite this command using an allowed package manager.`,
920
- );
921
- }
922
- }
923
- }
924
-
925
- // Check user-specified blocked managers.
926
- for (const name of blocked) {
927
- const lower = name.toLowerCase();
928
- if (allowedSet.has(lower)) continue;
929
- const re = new RegExp(`\\b${lower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
930
- if (re.test(trimmed)) {
931
- return deny(
932
- `"${lower}" is not an allowed package manager. ` +
933
- `Allowed package managers for this project: ${allowedList}. ` +
934
- `Rewrite this command using an allowed package manager.`,
935
- );
936
- }
937
- }
938
- }
939
-
940
- return allow();
941
- }
942
-
943
- function warnBackgroundProcess(ctx: PolicyContext): PolicyResult {
944
- if (ctx.toolName !== "Bash") return allow();
945
- const cmd = getCommand(ctx);
946
- const isBackground =
947
- NOHUP_RE.test(cmd) ||
948
- SCREEN_DETACH_RE.test(cmd) ||
949
- TMUX_DETACH_RE.test(cmd) ||
950
- DISOWN_RE.test(cmd) ||
951
- BACKGROUND_AMPERSAND_RE.test(cmd);
952
- if (isBackground) {
953
- return instruct(
954
- "STOP: This command starts a background or detached process (nohup, screen -d, tmux -d, or trailing &). Background processes persist after Claude's session and may be difficult to track or stop. Confirm with the user before executing.",
955
- );
956
- }
957
- return allow();
958
- }
959
-
960
- // -- Workflow (Stop event) policies --
961
-
962
- function requireCommitBeforeStop(ctx: PolicyContext): PolicyResult {
963
- const cwd = ctx.session?.cwd;
964
- if (!cwd) return allow("No working directory available, skipping commit check.");
965
-
966
- try {
967
- const status = execSync("git status --porcelain", {
968
- cwd,
969
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
970
- timeout: 5000,
971
- }).trim();
972
-
973
- if (status.length > 0) {
974
- return deny(
975
- "You have uncommitted changes in the working directory. Commit all changes now.",
976
- );
977
- }
978
- return allow("All changes are committed.");
979
- } catch {
980
- return allow("Not a git repository, skipping commit check.");
981
- }
982
- }
983
-
984
- function requirePushBeforeStop(ctx: PolicyContext): PolicyResult {
985
- const cwd = ctx.session?.cwd;
986
- if (!cwd) return allow("No working directory available, skipping push check.");
987
-
988
- try {
989
- const remotes = execSync("git remote", {
990
- cwd,
991
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
992
- timeout: 3000,
993
- }).trim();
994
-
995
- if (!remotes) return allow("No git remote configured, skipping push check.");
996
-
997
- const remote = (ctx.params?.remote as string) ?? "origin";
998
-
999
- const branch = getCurrentBranch(cwd);
1000
- if (!branch || branch === "HEAD") return allow("Detached HEAD, skipping push check.");
1001
-
1002
- const baseBranch = (ctx.params?.baseBranch as string) ?? "main";
1003
-
1004
- // If on the base branch itself, no push of a feature branch is needed
1005
- if (branch === baseBranch) {
1006
- return allow(`On base branch "${baseBranch}", skipping push check.`);
1007
- }
1008
-
1009
- // Check if branch has diverged from base in any meaningful way
1010
- try {
1011
- const ahead = execFileSync(
1012
- "git",
1013
- ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"],
1014
- { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 },
1015
- ).trim();
1016
-
1017
- if (!ahead) {
1018
- // No commits ahead — branch is fully merged (regular merge / fast-forward)
1019
- return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
1020
- }
1021
-
1022
- // Commits exist but might be from a squash-merged PR.
1023
- // Check actual file diff — if trees are identical, work is already in base.
1024
- const diff = execFileSync(
1025
- "git",
1026
- ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"],
1027
- { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 },
1028
- ).trim();
1029
-
1030
- if (!diff) {
1031
- return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
1032
- }
1033
- } catch {
1034
- // remote/{baseBranch} ref missing — fall through to existing push checks
1035
- }
1036
-
1037
- // Check if remote tracking branch exists
1038
- let hasTracking = false;
1039
- try {
1040
- execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
1041
- cwd,
1042
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
1043
- timeout: 3000,
1044
- });
1045
- hasTracking = true;
1046
- } catch {
1047
- // Remote tracking branch does not exist
1048
- }
1049
-
1050
- if (!hasTracking) {
1051
- return deny(
1052
- `Branch "${branch}" has not been pushed to remote "${remote}". ` +
1053
- `Run now: git push -u ${remote} ${branch}`,
1054
- );
1055
- }
1056
-
1057
- // Check for unpushed commits
1058
- const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
1059
- cwd,
1060
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
1061
- timeout: 5000,
1062
- }).trim();
1063
-
1064
- if (unpushed.length > 0) {
1065
- const commitCount = unpushed.split("\n").length;
1066
- return deny(
1067
- `You have ${commitCount} unpushed commit${commitCount > 1 ? "s" : ""} on branch "${branch}". ` +
1068
- `Run now: git push`,
1069
- );
1070
- }
1071
-
1072
- return allow(`All commits pushed to "${remote}".`);
1073
- } catch {
1074
- return allow("Could not check push status, skipping.");
1075
- }
1076
- }
1077
-
1078
- function requirePrBeforeStop(ctx: PolicyContext): PolicyResult {
1079
- const cwd = ctx.session?.cwd;
1080
- if (!cwd) return allow("No working directory available, skipping PR check.");
1081
-
1082
- try {
1083
- // Check if gh CLI is available
1084
- try {
1085
- execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
1086
- } catch {
1087
- return allow("GitHub CLI (gh) not installed, skipping PR check.");
1088
- }
1089
-
1090
- const branch = getCurrentBranch(cwd);
1091
- if (!branch || branch === "HEAD") return allow("Detached HEAD, skipping PR check.");
1092
-
1093
- const baseBranch = (ctx.params?.baseBranch as string) ?? "main";
1094
-
1095
- // If on the base branch itself, no PR is needed
1096
- if (branch === baseBranch) {
1097
- return allow(`On base branch "${baseBranch}", skipping PR check.`);
1098
- }
1099
-
1100
- // Check if branch has diverged from base in any meaningful way
1101
- try {
1102
- const ahead = execFileSync(
1103
- "git",
1104
- ["log", `origin/${baseBranch}..HEAD`, "--oneline"],
1105
- { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 },
1106
- ).trim();
1107
-
1108
- if (!ahead) {
1109
- // No commits ahead — branch is fully merged (regular merge / fast-forward)
1110
- return allow(`No commits ahead of origin/${baseBranch}, skipping PR check.`);
1111
- }
1112
-
1113
- // Commits exist but might be from a squash-merged PR.
1114
- // Check actual file diff — if trees are identical, work is already in main.
1115
- const diff = execFileSync(
1116
- "git",
1117
- ["diff", "--stat", `origin/${baseBranch}`, "HEAD"],
1118
- { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 },
1119
- ).trim();
1120
-
1121
- if (!diff) {
1122
- return allow(`No file changes compared to origin/${baseBranch}, skipping PR check.`);
1123
- }
1124
- } catch {
1125
- // origin/{baseBranch} ref missing or git error — fall through to gh pr view
1126
- }
1127
-
1128
- // Check if a PR exists for this branch
1129
- let prJson: string;
1130
- try {
1131
- prJson = execSync("gh pr view --json number,url,state", {
1132
- cwd,
1133
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
1134
- timeout: 15000,
1135
- }).trim();
1136
- } catch {
1137
- // gh pr view exits non-zero when no PR exists
1138
- return deny(
1139
- `No pull request found for branch "${branch}". ` +
1140
- `Run now: gh pr create`,
1141
- );
1142
- }
1143
-
1144
- const pr = JSON.parse(prJson) as { number: number; url: string; state: string };
1145
-
1146
- if (pr.state === "OPEN") {
1147
- return allow(`PR #${pr.number} exists: ${pr.url}`);
1148
- }
1149
-
1150
- // PR is merged/closed. The earlier origin/{baseBranch} checks may have
1151
- // used a stale ref. Fetch and re-verify before denying.
1152
- if (pr.state === "MERGED") {
1153
- try {
1154
- execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
1155
- cwd,
1156
- encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
1157
- timeout: 10000,
1158
- });
1159
- const freshAhead = execFileSync(
1160
- "git",
1161
- ["log", `origin/${baseBranch}..HEAD`, "--oneline"],
1162
- { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 },
1163
- ).trim();
1164
- if (!freshAhead) {
1165
- return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
1166
- }
1167
- const freshDiff = execFileSync(
1168
- "git",
1169
- ["diff", "--stat", `origin/${baseBranch}`, "HEAD"],
1170
- { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 },
1171
- ).trim();
1172
- if (!freshDiff) {
1173
- return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
1174
- }
1175
- } catch {
1176
- // Fetch or git command failed — fall through to deny
1177
- }
1178
- }
1179
-
1180
- return deny(
1181
- `Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`,
1182
- );
1183
- } catch {
1184
- return allow("Could not check PR status, skipping.");
1185
- }
1186
- }
1187
-
1188
- function requireCiGreenBeforeStop(ctx: PolicyContext): PolicyResult {
1189
- const cwd = ctx.session?.cwd;
1190
- if (!cwd) return allow("No working directory available, skipping CI check.");
1191
-
1192
- try {
1193
- // Check if gh CLI is available
1194
- try {
1195
- execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
1196
- } catch {
1197
- return allow("GitHub CLI (gh) not installed, skipping CI check.");
1198
- }
1199
-
1200
- const branch = getCurrentBranch(cwd);
1201
- if (!branch || branch === "HEAD") return allow("Detached HEAD, skipping CI check.");
1202
-
1203
- // 1. GitHub Actions workflow runs
1204
- let workflowRuns: CiCheck[] = [];
1205
- try {
1206
- const runsJson = execFileSync(
1207
- "gh",
1208
- ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"],
1209
- { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 },
1210
- ).trim();
1211
-
1212
- if (runsJson && runsJson !== "[]") {
1213
- workflowRuns = JSON.parse(runsJson) as CiCheck[];
1214
- }
1215
- } catch {
1216
- // fail-open for workflow runs; continue to check third-party checks
1217
- }
1218
-
1219
- // 2. Third-party check runs (CodeRabbit, SonarCloud, Codecov, etc.)
1220
- let thirdPartyChecks: CiCheck[] = [];
1221
- let commitStatuses: CiCheck[] = [];
1222
- const sha = getHeadSha(cwd);
1223
- if (sha) {
1224
- thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
1225
- commitStatuses = getCommitStatuses(cwd, sha);
1226
- }
1227
-
1228
- // 3. Merge all checks
1229
- const allChecks = [...workflowRuns, ...thirdPartyChecks, ...commitStatuses];
1230
-
1231
- if (allChecks.length === 0) return allow(`No CI runs found for branch "${branch}".`);
1232
-
1233
- const failing = allChecks.filter(
1234
- (r) =>
1235
- r.status === "completed" &&
1236
- r.conclusion !== "success" &&
1237
- r.conclusion !== "skipped" &&
1238
- r.conclusion !== "cancelled",
1239
- );
1240
- if (failing.length > 0) {
1241
- const names = failing.map((r) => `"${r.name}"`).join(", ");
1242
- return deny(
1243
- `CI checks are failing on branch "${branch}": ${names}. Fix the failing checks now.`,
1244
- );
1245
- }
1246
-
1247
- const pending = allChecks.filter(
1248
- (r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting",
1249
- );
1250
- if (pending.length > 0) {
1251
- const names = pending.map((r) => `"${r.name}"`).join(", ");
1252
- return deny(
1253
- `CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete, then verify they pass.`,
1254
- );
1255
- }
1256
-
1257
- return allow(`All CI checks passed on branch "${branch}".`);
1258
- } catch {
1259
- return allow("Could not check CI status, skipping.");
1260
- }
1261
- }
1262
-
1263
- // -- Registry --
1264
-
1265
- export const BUILTIN_POLICIES: BuiltinPolicyDefinition[] = [
1266
- {
1267
- name: "sanitize-jwt",
1268
- description: "Stop Claude from reading JWTs in tool responses",
1269
- fn: sanitizeJwt,
1270
- match: { events: ["PostToolUse"] },
1271
- defaultEnabled: true,
1272
- category: "Sanitize",
1273
- },
1274
- {
1275
- name: "sanitize-api-keys",
1276
- description: "Stop Claude from reading API keys (OpenAI, Anthropic, GitHub, AWS, Stripe, Google) in tool responses",
1277
- fn: sanitizeApiKeys,
1278
- match: { events: ["PostToolUse"] },
1279
- defaultEnabled: true,
1280
- category: "Sanitize",
1281
- params: {
1282
- additionalPatterns: {
1283
- type: "pattern[]",
1284
- description: "Additional API key patterns to scrub, each with { regex, label }",
1285
- default: [],
1286
- },
1287
- } satisfies PolicyParamsSchema,
1288
- },
1289
- {
1290
- name: "sanitize-connection-strings",
1291
- description: "Stop Claude from reading database connection strings with embedded credentials in tool responses",
1292
- fn: sanitizeConnectionStrings,
1293
- match: { events: ["PostToolUse"] },
1294
- defaultEnabled: true,
1295
- category: "Sanitize",
1296
- },
1297
- {
1298
- name: "sanitize-private-key-content",
1299
- description: "Stop Claude from reading PEM private key content in tool responses",
1300
- fn: sanitizePrivateKeyContent,
1301
- match: { events: ["PostToolUse"] },
1302
- defaultEnabled: true,
1303
- category: "Sanitize",
1304
- },
1305
- {
1306
- name: "sanitize-bearer-tokens",
1307
- description: "Stop Claude from reading Authorization Bearer tokens in tool responses",
1308
- fn: sanitizeBearerTokens,
1309
- match: { events: ["PostToolUse"] },
1310
- defaultEnabled: true,
1311
- category: "Sanitize",
1312
- },
1313
- {
1314
- name: "protect-env-vars",
1315
- description: "Prevent commands that read environment variables",
1316
- fn: protectEnvVars,
1317
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1318
- defaultEnabled: true,
1319
- category: "Environment",
1320
- },
1321
- {
1322
- name: "block-env-files",
1323
- description: "Block reading/writing .env files",
1324
- fn: blockEnvFiles,
1325
- match: { events: ["PreToolUse"] },
1326
- defaultEnabled: true,
1327
- category: "Environment",
1328
- },
1329
- {
1330
- name: "block-read-outside-cwd",
1331
- description: "Block file reads outside the session working directory",
1332
- fn: blockReadOutsideCwd,
1333
- match: { events: ["PreToolUse"], toolNames: ["Read", "Glob", "Grep", "Bash"] },
1334
- defaultEnabled: false,
1335
- category: "Environment",
1336
- params: {
1337
- allowPaths: {
1338
- type: "string[]",
1339
- description: "Absolute paths outside cwd that are allowed to be read",
1340
- default: [],
1341
- },
1342
- } satisfies PolicyParamsSchema,
1343
- },
1344
- {
1345
- name: "block-sudo",
1346
- description: "Block sudo commands",
1347
- fn: blockSudo,
1348
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1349
- defaultEnabled: true,
1350
- category: "Dangerous Commands",
1351
- params: {
1352
- allowPatterns: {
1353
- type: "string[]",
1354
- description: "Sudo command patterns to allow, matched token-by-token (e.g. 'sudo systemctl status')",
1355
- default: [],
1356
- },
1357
- } satisfies PolicyParamsSchema,
1358
- },
1359
- {
1360
- name: "block-curl-pipe-sh",
1361
- description: "Block piping downloads to shell",
1362
- fn: blockCurlPipeSh,
1363
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1364
- defaultEnabled: true,
1365
- category: "Dangerous Commands",
1366
- },
1367
- {
1368
- name: "block-rm-rf",
1369
- description: "Prevent catastrophic deletions",
1370
- fn: blockRmRf,
1371
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1372
- defaultEnabled: false,
1373
- category: "Dangerous Commands",
1374
- params: {
1375
- allowPaths: {
1376
- type: "string[]",
1377
- description: "Paths that are allowed to be recursively deleted",
1378
- default: [],
1379
- },
1380
- } satisfies PolicyParamsSchema,
1381
- },
1382
- {
1383
- name: "block-failproofai-commands",
1384
- description: "Block failproofai CLI commands and uninstallation",
1385
- fn: blockFailproofaiCommands,
1386
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1387
- defaultEnabled: true,
1388
- category: "Dangerous Commands",
1389
- },
1390
- {
1391
- name: "block-secrets-write",
1392
- description: "Block writing secret key files",
1393
- fn: blockSecretsWrite,
1394
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1395
- defaultEnabled: false,
1396
- category: "Dangerous Commands",
1397
- params: {
1398
- additionalPatterns: {
1399
- type: "string[]",
1400
- description: "Additional filename patterns (substrings) to block",
1401
- default: [],
1402
- },
1403
- } satisfies PolicyParamsSchema,
1404
- },
1405
- {
1406
- name: "block-push-master",
1407
- description: "Block pushing to main/master",
1408
- fn: blockPushMaster,
1409
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1410
- defaultEnabled: true,
1411
- category: "Git",
1412
- params: {
1413
- protectedBranches: {
1414
- type: "string[]",
1415
- description: "Branch names to protect from direct pushes",
1416
- default: ["main", "master"],
1417
- },
1418
- } satisfies PolicyParamsSchema,
1419
- },
1420
- {
1421
- name: "block-force-push",
1422
- description: "Prevent force-pushing to any branch",
1423
- fn: blockForcePush,
1424
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1425
- defaultEnabled: false,
1426
- category: "Git",
1427
- },
1428
- {
1429
- name: "block-work-on-main",
1430
- description: "Block git commits and merges on main/master branch",
1431
- fn: blockWorkOnMain,
1432
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1433
- defaultEnabled: false,
1434
- category: "Git",
1435
- params: {
1436
- protectedBranches: {
1437
- type: "string[]",
1438
- description: "Branch names where commits/merges are blocked",
1439
- default: ["main", "master"],
1440
- },
1441
- } satisfies PolicyParamsSchema,
1442
- },
1443
- {
1444
- name: "warn-git-amend",
1445
- description: "Warns before amending git commits, which rewrites history",
1446
- fn: warnGitAmend,
1447
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1448
- defaultEnabled: false,
1449
- category: "Git",
1450
- },
1451
- {
1452
- name: "warn-git-stash-drop",
1453
- description: "Warns before permanently deleting stashed changes",
1454
- fn: warnGitStashDrop,
1455
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1456
- defaultEnabled: false,
1457
- category: "Git",
1458
- },
1459
- {
1460
- name: "warn-all-files-staged",
1461
- description: "Warns before staging all working tree files with git add -A / . / --all",
1462
- fn: warnAllFilesStaged,
1463
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1464
- defaultEnabled: false,
1465
- category: "Git",
1466
- },
1467
- {
1468
- name: "warn-destructive-sql",
1469
- description: "Warn before executing destructive SQL (DROP/TRUNCATE/DELETE without WHERE) via database clients",
1470
- fn: warnDestructiveSql,
1471
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1472
- defaultEnabled: false,
1473
- category: "Database",
1474
- },
1475
- {
1476
- name: "warn-schema-alteration",
1477
- description: "Warns before SQL schema changes (ALTER TABLE with column or rename operations)",
1478
- fn: warnSchemaAlteration,
1479
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1480
- defaultEnabled: false,
1481
- category: "Database",
1482
- },
1483
- {
1484
- name: "warn-package-publish",
1485
- description: "Warn before publishing packages to public registries (npm, PyPI, crates.io, RubyGems, etc.)",
1486
- fn: warnPackagePublish,
1487
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1488
- defaultEnabled: false,
1489
- category: "Packages & System",
1490
- },
1491
- {
1492
- name: "warn-global-package-install",
1493
- description: "Warns before installing packages globally (npm -g, cargo install, etc.)",
1494
- fn: warnGlobalPackageInstall,
1495
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1496
- defaultEnabled: false,
1497
- category: "Packages & System",
1498
- },
1499
- {
1500
- name: "prefer-package-manager",
1501
- description: "Blocks non-preferred package managers and tells Claude to use an allowed one (e.g., uv instead of pip)",
1502
- fn: preferPackageManager,
1503
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1504
- defaultEnabled: false,
1505
- category: "Packages & System",
1506
- params: {
1507
- allowed: {
1508
- type: "string[]",
1509
- description: "Allowed package manager names (e.g. ['uv', 'bun']). Any detected manager not in this list is blocked.",
1510
- default: [],
1511
- },
1512
- blocked: {
1513
- type: "string[]",
1514
- description: "Additional manager names to block beyond the built-in list (e.g. ['pdm', 'pipx']).",
1515
- default: [],
1516
- },
1517
- } satisfies PolicyParamsSchema,
1518
- },
1519
- {
1520
- name: "warn-large-file-write",
1521
- description: "Warn before writing files larger than 1MB (configurable via thresholdKb param)",
1522
- fn: warnLargeFileWrite,
1523
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1524
- defaultEnabled: false,
1525
- category: "Packages & System",
1526
- params: {
1527
- thresholdKb: {
1528
- type: "number",
1529
- description: "File size threshold in KB above which a warning is issued",
1530
- default: 1024,
1531
- },
1532
- } satisfies PolicyParamsSchema,
1533
- },
1534
- {
1535
- name: "warn-background-process",
1536
- description: "Warns before starting detached or background processes",
1537
- fn: warnBackgroundProcess,
1538
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1539
- defaultEnabled: false,
1540
- category: "Packages & System",
1541
- },
1542
- {
1543
- name: "warn-repeated-tool-calls",
1544
- description: "Warn when the same tool is called 3+ times with identical parameters",
1545
- fn: warnRepeatedToolCalls,
1546
- match: { events: ["PreToolUse"] },
1547
- defaultEnabled: false,
1548
- category: "AI Behavior",
1549
- },
1550
- {
1551
- name: "require-commit-before-stop",
1552
- description: "Require all changes to be committed before Claude stops",
1553
- fn: requireCommitBeforeStop,
1554
- match: { events: ["Stop"] },
1555
- defaultEnabled: false,
1556
- category: "Workflow",
1557
- },
1558
- {
1559
- name: "require-push-before-stop",
1560
- description: "Require all commits to be pushed to remote before Claude stops",
1561
- fn: requirePushBeforeStop,
1562
- match: { events: ["Stop"] },
1563
- defaultEnabled: false,
1564
- category: "Workflow",
1565
- params: {
1566
- remote: {
1567
- type: "string",
1568
- description: "Remote name to push to (default: origin)",
1569
- default: "origin",
1570
- },
1571
- baseBranch: {
1572
- type: "string",
1573
- description: "Base branch to compare against (default: main)",
1574
- default: "main",
1575
- },
1576
- } satisfies PolicyParamsSchema,
1577
- },
1578
- {
1579
- name: "require-pr-before-stop",
1580
- description: "Require a pull request to exist for the current branch before Claude stops",
1581
- fn: requirePrBeforeStop,
1582
- match: { events: ["Stop"] },
1583
- defaultEnabled: false,
1584
- category: "Workflow",
1585
- params: {
1586
- baseBranch: {
1587
- type: "string",
1588
- description: "Base branch to compare against (default: main)",
1589
- default: "main",
1590
- },
1591
- } satisfies PolicyParamsSchema,
1592
- },
1593
- {
1594
- name: "require-ci-green-before-stop",
1595
- description: "Require CI checks to pass on the current branch before Claude stops",
1596
- fn: requireCiGreenBeforeStop,
1597
- match: { events: ["Stop"] },
1598
- defaultEnabled: false,
1599
- category: "Workflow",
1600
- },
1601
- ];
1602
-
1603
- export function registerBuiltinPolicies(enabledNames: string[]): void {
1604
- const enabledSet = new Set(enabledNames);
1605
- for (const policy of BUILTIN_POLICIES) {
1606
- if (enabledSet.has(policy.name)) {
1607
- registerPolicy(policy.name, policy.description, policy.fn, policy.match);
1608
- }
1609
- }
1610
- }
1611
-
1612
- /** Clears the git branch cache. Exposed for test isolation only. */
1613
- export function clearGitBranchCache(): void {
1614
- gitBranchCache.clear();
1615
- }