failproofai 0.0.6-beta.2 → 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 (511) 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]__0i5kvry._.js → [root-of-the-server]__0om-5pe._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__05akje6._.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/{05j1px0r8yzh6.js → 02dqjyv6_9mhq.js} +2 -2
  71. package/.next/standalone/.next/static/chunks/{14cl9poem30dq.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/{00j0rr7rh8ef8.js → 0o547jv-k_k35.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/{0ijk_kek9_wyx.js → 0pk2h2.mjxy.m.js} +1 -1
  75. package/.next/standalone/.next/static/chunks/{0xpl.oscrakvx.js → 0rcwkbh24w38b.js} +1 -1
  76. package/.next/standalone/.next/static/chunks/{1052sguyd-.ka.js → 140xx_tfr~lm_.js} +1 -1
  77. package/.next/standalone/.next/static/chunks/{0npb~873.wvg3.js → 169_e4dq~1~b6.js} +1 -1
  78. package/.next/standalone/.next/static/chunks/{0badv41uxa56..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/bin/failproofai.mjs +91 -4
  83. package/dist/cli.mjs +1155 -54
  84. package/package.json +2 -2
  85. package/scripts/prune-standalone.mjs +128 -0
  86. package/src/auth/login.ts +104 -0
  87. package/src/auth/logout.ts +50 -0
  88. package/src/auth/token-store.ts +64 -0
  89. package/src/hooks/builtin-policies.ts +22 -20
  90. package/src/hooks/handler.ts +35 -15
  91. package/src/relay/daemon.ts +362 -0
  92. package/src/relay/pid.ts +76 -0
  93. package/src/relay/queue.ts +225 -0
  94. package/.next/standalone/.claude/settings.json +0 -316
  95. package/.next/standalone/.failproofai/policies/review-policies.mjs +0 -113
  96. package/.next/standalone/.failproofai/policies/workflow-policies.mjs +0 -63
  97. package/.next/standalone/.failproofai/policies-config.json +0 -39
  98. package/.next/standalone/.next/static/chunks/0gu_a.a80ritd.css +0 -1
  99. package/.next/standalone/AGENTS.md +0 -80
  100. package/.next/standalone/CHANGELOG.md +0 -151
  101. package/.next/standalone/CLAUDE.md +0 -165
  102. package/.next/standalone/CONTRIBUTING.md +0 -76
  103. package/.next/standalone/Dockerfile.docs +0 -12
  104. package/.next/standalone/LICENSE +0 -42
  105. package/.next/standalone/README.md +0 -301
  106. package/.next/standalone/bin/failproofai.mjs +0 -352
  107. package/.next/standalone/bun.lock +0 -1119
  108. package/.next/standalone/components.json +0 -23
  109. package/.next/standalone/dist/cli.mjs +0 -3595
  110. package/.next/standalone/dist/index.js +0 -80
  111. package/.next/standalone/docs/ar/architecture.mdx +0 -334
  112. package/.next/standalone/docs/ar/built-in-policies.mdx +0 -574
  113. package/.next/standalone/docs/ar/cli/dashboard.mdx +0 -28
  114. package/.next/standalone/docs/ar/cli/environment-variables.mdx +0 -34
  115. package/.next/standalone/docs/ar/cli/hook.mdx +0 -31
  116. package/.next/standalone/docs/ar/cli/install-policies.mdx +0 -48
  117. package/.next/standalone/docs/ar/cli/list-policies.mdx +0 -31
  118. package/.next/standalone/docs/ar/cli/remove-policies.mdx +0 -43
  119. package/.next/standalone/docs/ar/cli/version.mdx +0 -13
  120. package/.next/standalone/docs/ar/configuration.mdx +0 -223
  121. package/.next/standalone/docs/ar/custom-policies.mdx +0 -354
  122. package/.next/standalone/docs/ar/dashboard.mdx +0 -142
  123. package/.next/standalone/docs/ar/examples.mdx +0 -307
  124. package/.next/standalone/docs/ar/for-agents.mdx +0 -39
  125. package/.next/standalone/docs/ar/getting-started.mdx +0 -187
  126. package/.next/standalone/docs/ar/introduction.mdx +0 -58
  127. package/.next/standalone/docs/ar/package-aliases.mdx +0 -82
  128. package/.next/standalone/docs/ar/testing.mdx +0 -261
  129. package/.next/standalone/docs/architecture.mdx +0 -332
  130. package/.next/standalone/docs/built-in-policies.mdx +0 -574
  131. package/.next/standalone/docs/cli/dashboard.mdx +0 -28
  132. package/.next/standalone/docs/cli/environment-variables.mdx +0 -34
  133. package/.next/standalone/docs/cli/hook.mdx +0 -30
  134. package/.next/standalone/docs/cli/install-policies.mdx +0 -47
  135. package/.next/standalone/docs/cli/list-policies.mdx +0 -31
  136. package/.next/standalone/docs/cli/remove-policies.mdx +0 -43
  137. package/.next/standalone/docs/cli/version.mdx +0 -12
  138. package/.next/standalone/docs/configuration.mdx +0 -222
  139. package/.next/standalone/docs/custom-policies.mdx +0 -353
  140. package/.next/standalone/docs/dashboard.mdx +0 -142
  141. package/.next/standalone/docs/de/architecture.mdx +0 -332
  142. package/.next/standalone/docs/de/built-in-policies.mdx +0 -574
  143. package/.next/standalone/docs/de/cli/dashboard.mdx +0 -28
  144. package/.next/standalone/docs/de/cli/environment-variables.mdx +0 -34
  145. package/.next/standalone/docs/de/cli/hook.mdx +0 -30
  146. package/.next/standalone/docs/de/cli/install-policies.mdx +0 -47
  147. package/.next/standalone/docs/de/cli/list-policies.mdx +0 -31
  148. package/.next/standalone/docs/de/cli/remove-policies.mdx +0 -43
  149. package/.next/standalone/docs/de/cli/version.mdx +0 -12
  150. package/.next/standalone/docs/de/configuration.mdx +0 -222
  151. package/.next/standalone/docs/de/custom-policies.mdx +0 -353
  152. package/.next/standalone/docs/de/dashboard.mdx +0 -142
  153. package/.next/standalone/docs/de/examples.mdx +0 -307
  154. package/.next/standalone/docs/de/for-agents.mdx +0 -38
  155. package/.next/standalone/docs/de/getting-started.mdx +0 -186
  156. package/.next/standalone/docs/de/introduction.mdx +0 -57
  157. package/.next/standalone/docs/de/package-aliases.mdx +0 -82
  158. package/.next/standalone/docs/de/testing.mdx +0 -260
  159. package/.next/standalone/docs/docs.json +0 -1002
  160. package/.next/standalone/docs/es/architecture.mdx +0 -332
  161. package/.next/standalone/docs/es/built-in-policies.mdx +0 -574
  162. package/.next/standalone/docs/es/cli/dashboard.mdx +0 -28
  163. package/.next/standalone/docs/es/cli/environment-variables.mdx +0 -34
  164. package/.next/standalone/docs/es/cli/hook.mdx +0 -30
  165. package/.next/standalone/docs/es/cli/install-policies.mdx +0 -47
  166. package/.next/standalone/docs/es/cli/list-policies.mdx +0 -31
  167. package/.next/standalone/docs/es/cli/remove-policies.mdx +0 -43
  168. package/.next/standalone/docs/es/cli/version.mdx +0 -12
  169. package/.next/standalone/docs/es/configuration.mdx +0 -222
  170. package/.next/standalone/docs/es/custom-policies.mdx +0 -353
  171. package/.next/standalone/docs/es/dashboard.mdx +0 -142
  172. package/.next/standalone/docs/es/examples.mdx +0 -307
  173. package/.next/standalone/docs/es/for-agents.mdx +0 -38
  174. package/.next/standalone/docs/es/getting-started.mdx +0 -186
  175. package/.next/standalone/docs/es/introduction.mdx +0 -57
  176. package/.next/standalone/docs/es/package-aliases.mdx +0 -82
  177. package/.next/standalone/docs/es/testing.mdx +0 -260
  178. package/.next/standalone/docs/examples.mdx +0 -307
  179. package/.next/standalone/docs/favicon.ico +0 -0
  180. package/.next/standalone/docs/for-agents.mdx +0 -38
  181. package/.next/standalone/docs/fr/architecture.mdx +0 -332
  182. package/.next/standalone/docs/fr/built-in-policies.mdx +0 -574
  183. package/.next/standalone/docs/fr/cli/dashboard.mdx +0 -28
  184. package/.next/standalone/docs/fr/cli/environment-variables.mdx +0 -34
  185. package/.next/standalone/docs/fr/cli/hook.mdx +0 -30
  186. package/.next/standalone/docs/fr/cli/install-policies.mdx +0 -47
  187. package/.next/standalone/docs/fr/cli/list-policies.mdx +0 -31
  188. package/.next/standalone/docs/fr/cli/remove-policies.mdx +0 -43
  189. package/.next/standalone/docs/fr/cli/version.mdx +0 -12
  190. package/.next/standalone/docs/fr/configuration.mdx +0 -222
  191. package/.next/standalone/docs/fr/custom-policies.mdx +0 -353
  192. package/.next/standalone/docs/fr/dashboard.mdx +0 -142
  193. package/.next/standalone/docs/fr/examples.mdx +0 -307
  194. package/.next/standalone/docs/fr/for-agents.mdx +0 -38
  195. package/.next/standalone/docs/fr/getting-started.mdx +0 -186
  196. package/.next/standalone/docs/fr/introduction.mdx +0 -57
  197. package/.next/standalone/docs/fr/package-aliases.mdx +0 -82
  198. package/.next/standalone/docs/fr/testing.mdx +0 -260
  199. package/.next/standalone/docs/getting-started.mdx +0 -186
  200. package/.next/standalone/docs/he/architecture.mdx +0 -333
  201. package/.next/standalone/docs/he/built-in-policies.mdx +0 -574
  202. package/.next/standalone/docs/he/cli/dashboard.mdx +0 -28
  203. package/.next/standalone/docs/he/cli/environment-variables.mdx +0 -34
  204. package/.next/standalone/docs/he/cli/hook.mdx +0 -30
  205. package/.next/standalone/docs/he/cli/install-policies.mdx +0 -47
  206. package/.next/standalone/docs/he/cli/list-policies.mdx +0 -32
  207. package/.next/standalone/docs/he/cli/remove-policies.mdx +0 -43
  208. package/.next/standalone/docs/he/cli/version.mdx +0 -12
  209. package/.next/standalone/docs/he/configuration.mdx +0 -223
  210. package/.next/standalone/docs/he/custom-policies.mdx +0 -353
  211. package/.next/standalone/docs/he/dashboard.mdx +0 -142
  212. package/.next/standalone/docs/he/examples.mdx +0 -307
  213. package/.next/standalone/docs/he/for-agents.mdx +0 -38
  214. package/.next/standalone/docs/he/getting-started.mdx +0 -186
  215. package/.next/standalone/docs/he/introduction.mdx +0 -57
  216. package/.next/standalone/docs/he/package-aliases.mdx +0 -82
  217. package/.next/standalone/docs/he/testing.mdx +0 -260
  218. package/.next/standalone/docs/hi/architecture.mdx +0 -334
  219. package/.next/standalone/docs/hi/built-in-policies.mdx +0 -576
  220. package/.next/standalone/docs/hi/cli/dashboard.mdx +0 -28
  221. package/.next/standalone/docs/hi/cli/environment-variables.mdx +0 -34
  222. package/.next/standalone/docs/hi/cli/hook.mdx +0 -30
  223. package/.next/standalone/docs/hi/cli/install-policies.mdx +0 -47
  224. package/.next/standalone/docs/hi/cli/list-policies.mdx +0 -31
  225. package/.next/standalone/docs/hi/cli/remove-policies.mdx +0 -43
  226. package/.next/standalone/docs/hi/cli/version.mdx +0 -12
  227. package/.next/standalone/docs/hi/configuration.mdx +0 -222
  228. package/.next/standalone/docs/hi/custom-policies.mdx +0 -354
  229. package/.next/standalone/docs/hi/dashboard.mdx +0 -142
  230. package/.next/standalone/docs/hi/examples.mdx +0 -309
  231. package/.next/standalone/docs/hi/for-agents.mdx +0 -38
  232. package/.next/standalone/docs/hi/getting-started.mdx +0 -187
  233. package/.next/standalone/docs/hi/introduction.mdx +0 -57
  234. package/.next/standalone/docs/hi/package-aliases.mdx +0 -82
  235. package/.next/standalone/docs/hi/testing.mdx +0 -260
  236. package/.next/standalone/docs/i18n/README.ar.md +0 -312
  237. package/.next/standalone/docs/i18n/README.de.md +0 -307
  238. package/.next/standalone/docs/i18n/README.es.md +0 -307
  239. package/.next/standalone/docs/i18n/README.fr.md +0 -307
  240. package/.next/standalone/docs/i18n/README.he.md +0 -312
  241. package/.next/standalone/docs/i18n/README.hi.md +0 -307
  242. package/.next/standalone/docs/i18n/README.it.md +0 -307
  243. package/.next/standalone/docs/i18n/README.ja.md +0 -307
  244. package/.next/standalone/docs/i18n/README.ko.md +0 -307
  245. package/.next/standalone/docs/i18n/README.pt-br.md +0 -307
  246. package/.next/standalone/docs/i18n/README.ru.md +0 -308
  247. package/.next/standalone/docs/i18n/README.tr.md +0 -307
  248. package/.next/standalone/docs/i18n/README.vi.md +0 -307
  249. package/.next/standalone/docs/i18n/README.zh.md +0 -307
  250. package/.next/standalone/docs/introduction.mdx +0 -57
  251. package/.next/standalone/docs/it/architecture.mdx +0 -334
  252. package/.next/standalone/docs/it/built-in-policies.mdx +0 -574
  253. package/.next/standalone/docs/it/cli/dashboard.mdx +0 -28
  254. package/.next/standalone/docs/it/cli/environment-variables.mdx +0 -34
  255. package/.next/standalone/docs/it/cli/hook.mdx +0 -30
  256. package/.next/standalone/docs/it/cli/install-policies.mdx +0 -47
  257. package/.next/standalone/docs/it/cli/list-policies.mdx +0 -31
  258. package/.next/standalone/docs/it/cli/remove-policies.mdx +0 -43
  259. package/.next/standalone/docs/it/cli/version.mdx +0 -12
  260. package/.next/standalone/docs/it/configuration.mdx +0 -222
  261. package/.next/standalone/docs/it/custom-policies.mdx +0 -353
  262. package/.next/standalone/docs/it/dashboard.mdx +0 -142
  263. package/.next/standalone/docs/it/examples.mdx +0 -307
  264. package/.next/standalone/docs/it/for-agents.mdx +0 -38
  265. package/.next/standalone/docs/it/getting-started.mdx +0 -186
  266. package/.next/standalone/docs/it/introduction.mdx +0 -57
  267. package/.next/standalone/docs/it/package-aliases.mdx +0 -82
  268. package/.next/standalone/docs/it/testing.mdx +0 -260
  269. package/.next/standalone/docs/ja/architecture.mdx +0 -332
  270. package/.next/standalone/docs/ja/built-in-policies.mdx +0 -572
  271. package/.next/standalone/docs/ja/cli/dashboard.mdx +0 -28
  272. package/.next/standalone/docs/ja/cli/environment-variables.mdx +0 -34
  273. package/.next/standalone/docs/ja/cli/hook.mdx +0 -30
  274. package/.next/standalone/docs/ja/cli/install-policies.mdx +0 -47
  275. package/.next/standalone/docs/ja/cli/list-policies.mdx +0 -31
  276. package/.next/standalone/docs/ja/cli/remove-policies.mdx +0 -43
  277. package/.next/standalone/docs/ja/cli/version.mdx +0 -12
  278. package/.next/standalone/docs/ja/configuration.mdx +0 -222
  279. package/.next/standalone/docs/ja/custom-policies.mdx +0 -353
  280. package/.next/standalone/docs/ja/dashboard.mdx +0 -142
  281. package/.next/standalone/docs/ja/examples.mdx +0 -307
  282. package/.next/standalone/docs/ja/for-agents.mdx +0 -38
  283. package/.next/standalone/docs/ja/getting-started.mdx +0 -186
  284. package/.next/standalone/docs/ja/introduction.mdx +0 -57
  285. package/.next/standalone/docs/ja/package-aliases.mdx +0 -82
  286. package/.next/standalone/docs/ja/testing.mdx +0 -260
  287. package/.next/standalone/docs/ko/architecture.mdx +0 -332
  288. package/.next/standalone/docs/ko/built-in-policies.mdx +0 -572
  289. package/.next/standalone/docs/ko/cli/dashboard.mdx +0 -28
  290. package/.next/standalone/docs/ko/cli/environment-variables.mdx +0 -34
  291. package/.next/standalone/docs/ko/cli/hook.mdx +0 -30
  292. package/.next/standalone/docs/ko/cli/install-policies.mdx +0 -47
  293. package/.next/standalone/docs/ko/cli/list-policies.mdx +0 -31
  294. package/.next/standalone/docs/ko/cli/remove-policies.mdx +0 -43
  295. package/.next/standalone/docs/ko/cli/version.mdx +0 -12
  296. package/.next/standalone/docs/ko/configuration.mdx +0 -222
  297. package/.next/standalone/docs/ko/custom-policies.mdx +0 -353
  298. package/.next/standalone/docs/ko/dashboard.mdx +0 -142
  299. package/.next/standalone/docs/ko/examples.mdx +0 -307
  300. package/.next/standalone/docs/ko/for-agents.mdx +0 -38
  301. package/.next/standalone/docs/ko/getting-started.mdx +0 -186
  302. package/.next/standalone/docs/ko/introduction.mdx +0 -57
  303. package/.next/standalone/docs/ko/package-aliases.mdx +0 -82
  304. package/.next/standalone/docs/ko/testing.mdx +0 -260
  305. package/.next/standalone/docs/logo/dark.svg +0 -21
  306. package/.next/standalone/docs/logo/exosphere-dark.png +0 -0
  307. package/.next/standalone/docs/logo/exosphere-light.png +0 -0
  308. package/.next/standalone/docs/logo/light.svg +0 -21
  309. package/.next/standalone/docs/package-aliases.mdx +0 -82
  310. package/.next/standalone/docs/pt-br/architecture.mdx +0 -332
  311. package/.next/standalone/docs/pt-br/built-in-policies.mdx +0 -574
  312. package/.next/standalone/docs/pt-br/cli/dashboard.mdx +0 -28
  313. package/.next/standalone/docs/pt-br/cli/environment-variables.mdx +0 -34
  314. package/.next/standalone/docs/pt-br/cli/hook.mdx +0 -30
  315. package/.next/standalone/docs/pt-br/cli/install-policies.mdx +0 -47
  316. package/.next/standalone/docs/pt-br/cli/list-policies.mdx +0 -31
  317. package/.next/standalone/docs/pt-br/cli/remove-policies.mdx +0 -43
  318. package/.next/standalone/docs/pt-br/cli/version.mdx +0 -12
  319. package/.next/standalone/docs/pt-br/configuration.mdx +0 -222
  320. package/.next/standalone/docs/pt-br/custom-policies.mdx +0 -353
  321. package/.next/standalone/docs/pt-br/dashboard.mdx +0 -142
  322. package/.next/standalone/docs/pt-br/examples.mdx +0 -307
  323. package/.next/standalone/docs/pt-br/for-agents.mdx +0 -38
  324. package/.next/standalone/docs/pt-br/getting-started.mdx +0 -186
  325. package/.next/standalone/docs/pt-br/introduction.mdx +0 -57
  326. package/.next/standalone/docs/pt-br/package-aliases.mdx +0 -82
  327. package/.next/standalone/docs/pt-br/testing.mdx +0 -260
  328. package/.next/standalone/docs/ru/architecture.mdx +0 -333
  329. package/.next/standalone/docs/ru/built-in-policies.mdx +0 -574
  330. package/.next/standalone/docs/ru/cli/dashboard.mdx +0 -28
  331. package/.next/standalone/docs/ru/cli/environment-variables.mdx +0 -34
  332. package/.next/standalone/docs/ru/cli/hook.mdx +0 -30
  333. package/.next/standalone/docs/ru/cli/install-policies.mdx +0 -48
  334. package/.next/standalone/docs/ru/cli/list-policies.mdx +0 -32
  335. package/.next/standalone/docs/ru/cli/remove-policies.mdx +0 -43
  336. package/.next/standalone/docs/ru/cli/version.mdx +0 -12
  337. package/.next/standalone/docs/ru/configuration.mdx +0 -222
  338. package/.next/standalone/docs/ru/custom-policies.mdx +0 -354
  339. package/.next/standalone/docs/ru/dashboard.mdx +0 -142
  340. package/.next/standalone/docs/ru/examples.mdx +0 -309
  341. package/.next/standalone/docs/ru/for-agents.mdx +0 -38
  342. package/.next/standalone/docs/ru/getting-started.mdx +0 -186
  343. package/.next/standalone/docs/ru/introduction.mdx +0 -57
  344. package/.next/standalone/docs/ru/package-aliases.mdx +0 -82
  345. package/.next/standalone/docs/ru/testing.mdx +0 -260
  346. package/.next/standalone/docs/testing.mdx +0 -260
  347. package/.next/standalone/docs/tr/architecture.mdx +0 -333
  348. package/.next/standalone/docs/tr/built-in-policies.mdx +0 -574
  349. package/.next/standalone/docs/tr/cli/dashboard.mdx +0 -28
  350. package/.next/standalone/docs/tr/cli/environment-variables.mdx +0 -34
  351. package/.next/standalone/docs/tr/cli/hook.mdx +0 -30
  352. package/.next/standalone/docs/tr/cli/install-policies.mdx +0 -47
  353. package/.next/standalone/docs/tr/cli/list-policies.mdx +0 -31
  354. package/.next/standalone/docs/tr/cli/remove-policies.mdx +0 -44
  355. package/.next/standalone/docs/tr/cli/version.mdx +0 -12
  356. package/.next/standalone/docs/tr/configuration.mdx +0 -222
  357. package/.next/standalone/docs/tr/custom-policies.mdx +0 -353
  358. package/.next/standalone/docs/tr/dashboard.mdx +0 -142
  359. package/.next/standalone/docs/tr/examples.mdx +0 -308
  360. package/.next/standalone/docs/tr/for-agents.mdx +0 -38
  361. package/.next/standalone/docs/tr/getting-started.mdx +0 -186
  362. package/.next/standalone/docs/tr/introduction.mdx +0 -57
  363. package/.next/standalone/docs/tr/package-aliases.mdx +0 -82
  364. package/.next/standalone/docs/tr/testing.mdx +0 -260
  365. package/.next/standalone/docs/vi/architecture.mdx +0 -334
  366. package/.next/standalone/docs/vi/built-in-policies.mdx +0 -575
  367. package/.next/standalone/docs/vi/cli/dashboard.mdx +0 -28
  368. package/.next/standalone/docs/vi/cli/environment-variables.mdx +0 -34
  369. package/.next/standalone/docs/vi/cli/hook.mdx +0 -30
  370. package/.next/standalone/docs/vi/cli/install-policies.mdx +0 -47
  371. package/.next/standalone/docs/vi/cli/list-policies.mdx +0 -31
  372. package/.next/standalone/docs/vi/cli/remove-policies.mdx +0 -43
  373. package/.next/standalone/docs/vi/cli/version.mdx +0 -13
  374. package/.next/standalone/docs/vi/configuration.mdx +0 -222
  375. package/.next/standalone/docs/vi/custom-policies.mdx +0 -353
  376. package/.next/standalone/docs/vi/dashboard.mdx +0 -142
  377. package/.next/standalone/docs/vi/examples.mdx +0 -308
  378. package/.next/standalone/docs/vi/for-agents.mdx +0 -38
  379. package/.next/standalone/docs/vi/getting-started.mdx +0 -186
  380. package/.next/standalone/docs/vi/introduction.mdx +0 -57
  381. package/.next/standalone/docs/vi/package-aliases.mdx +0 -82
  382. package/.next/standalone/docs/vi/testing.mdx +0 -260
  383. package/.next/standalone/docs/zh/architecture.mdx +0 -332
  384. package/.next/standalone/docs/zh/built-in-policies.mdx +0 -572
  385. package/.next/standalone/docs/zh/cli/dashboard.mdx +0 -28
  386. package/.next/standalone/docs/zh/cli/environment-variables.mdx +0 -34
  387. package/.next/standalone/docs/zh/cli/hook.mdx +0 -30
  388. package/.next/standalone/docs/zh/cli/install-policies.mdx +0 -47
  389. package/.next/standalone/docs/zh/cli/list-policies.mdx +0 -31
  390. package/.next/standalone/docs/zh/cli/remove-policies.mdx +0 -43
  391. package/.next/standalone/docs/zh/cli/version.mdx +0 -12
  392. package/.next/standalone/docs/zh/configuration.mdx +0 -222
  393. package/.next/standalone/docs/zh/custom-policies.mdx +0 -353
  394. package/.next/standalone/docs/zh/dashboard.mdx +0 -142
  395. package/.next/standalone/docs/zh/examples.mdx +0 -307
  396. package/.next/standalone/docs/zh/for-agents.mdx +0 -38
  397. package/.next/standalone/docs/zh/getting-started.mdx +0 -186
  398. package/.next/standalone/docs/zh/introduction.mdx +0 -57
  399. package/.next/standalone/docs/zh/package-aliases.mdx +0 -82
  400. package/.next/standalone/docs/zh/testing.mdx +0 -260
  401. package/.next/standalone/eslint.config.mjs +0 -15
  402. package/.next/standalone/examples/convention-policies/security-policies.mjs +0 -40
  403. package/.next/standalone/examples/convention-policies/workflow-policies.mjs +0 -41
  404. package/.next/standalone/examples/policies-advanced/index.js +0 -103
  405. package/.next/standalone/examples/policies-advanced/utils.js +0 -35
  406. package/.next/standalone/examples/policies-basic.js +0 -77
  407. package/.next/standalone/examples/policies-notification.js +0 -104
  408. package/.next/standalone/node_modules/@img/colour/color.cjs +0 -1594
  409. package/.next/standalone/node_modules/@img/colour/index.cjs +0 -1
  410. package/.next/standalone/node_modules/@img/colour/package.json +0 -45
  411. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +0 -46
  412. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  413. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +0 -1
  414. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  415. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +0 -42
  416. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/versions.json +0 -30
  417. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  418. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  419. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  420. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  421. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  422. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  423. package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  424. package/.next/standalone/node_modules/@img/sharp-linux-x64/package.json +0 -46
  425. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  426. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  427. package/.next/standalone/node_modules/detect-libc/lib/detect-libc.js +0 -313
  428. package/.next/standalone/node_modules/detect-libc/lib/elf.js +0 -39
  429. package/.next/standalone/node_modules/detect-libc/lib/filesystem.js +0 -51
  430. package/.next/standalone/node_modules/detect-libc/lib/process.js +0 -24
  431. package/.next/standalone/node_modules/detect-libc/package.json +0 -44
  432. package/.next/standalone/node_modules/sharp/lib/channel.js +0 -177
  433. package/.next/standalone/node_modules/sharp/lib/colour.js +0 -195
  434. package/.next/standalone/node_modules/sharp/lib/composite.js +0 -212
  435. package/.next/standalone/node_modules/sharp/lib/constructor.js +0 -499
  436. package/.next/standalone/node_modules/sharp/lib/index.js +0 -16
  437. package/.next/standalone/node_modules/sharp/lib/input.js +0 -809
  438. package/.next/standalone/node_modules/sharp/lib/is.js +0 -143
  439. package/.next/standalone/node_modules/sharp/lib/libvips.js +0 -207
  440. package/.next/standalone/node_modules/sharp/lib/operation.js +0 -1016
  441. package/.next/standalone/node_modules/sharp/lib/output.js +0 -1666
  442. package/.next/standalone/node_modules/sharp/lib/resize.js +0 -595
  443. package/.next/standalone/node_modules/sharp/lib/sharp.js +0 -121
  444. package/.next/standalone/node_modules/sharp/lib/utility.js +0 -291
  445. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/comparator.js +0 -143
  446. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/range.js +0 -557
  447. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/semver.js +0 -333
  448. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/cmp.js +0 -54
  449. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/coerce.js +0 -62
  450. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/compare.js +0 -7
  451. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/eq.js +0 -5
  452. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gt.js +0 -5
  453. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gte.js +0 -5
  454. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lt.js +0 -5
  455. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lte.js +0 -5
  456. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/neq.js +0 -5
  457. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/parse.js +0 -18
  458. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/satisfies.js +0 -12
  459. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/constants.js +0 -37
  460. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/debug.js +0 -11
  461. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/identifiers.js +0 -29
  462. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/lrucache.js +0 -42
  463. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/parse-options.js +0 -17
  464. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/re.js +0 -223
  465. package/.next/standalone/node_modules/sharp/node_modules/semver/package.json +0 -78
  466. package/.next/standalone/node_modules/sharp/package.json +0 -202
  467. package/.next/standalone/scripts/alias-proxy.js +0 -18
  468. package/.next/standalone/scripts/dev.ts +0 -3
  469. package/.next/standalone/scripts/install-telemetry.mjs +0 -108
  470. package/.next/standalone/scripts/launch.ts +0 -83
  471. package/.next/standalone/scripts/parse-script-args.ts +0 -87
  472. package/.next/standalone/scripts/postinstall.mjs +0 -121
  473. package/.next/standalone/scripts/preuninstall.mjs +0 -131
  474. package/.next/standalone/scripts/publish-aliases.mjs +0 -87
  475. package/.next/standalone/scripts/start.ts +0 -3
  476. package/.next/standalone/scripts/sync-hook-events-prompt.md +0 -60
  477. package/.next/standalone/scripts/translate-docs/cache.ts +0 -62
  478. package/.next/standalone/scripts/translate-docs/cli.ts +0 -357
  479. package/.next/standalone/scripts/translate-docs/config.ts +0 -248
  480. package/.next/standalone/scripts/translate-docs/mdx-translator.ts +0 -153
  481. package/.next/standalone/scripts/translate-docs/mintlify-nav.ts +0 -107
  482. package/.next/standalone/scripts/translate-docs/readme-translator.ts +0 -154
  483. package/.next/standalone/scripts/translate-docs/translator.ts +0 -68
  484. package/.next/standalone/scripts/translate-docs/types.ts +0 -43
  485. package/.next/standalone/src/cli-error.ts +0 -18
  486. package/.next/standalone/src/hooks/builtin-policies.ts +0 -1613
  487. package/.next/standalone/src/hooks/custom-hooks-loader.ts +0 -205
  488. package/.next/standalone/src/hooks/custom-hooks-registry.ts +0 -30
  489. package/.next/standalone/src/hooks/handler.ts +0 -202
  490. package/.next/standalone/src/hooks/hook-activity-store.ts +0 -349
  491. package/.next/standalone/src/hooks/hook-logger.ts +0 -133
  492. package/.next/standalone/src/hooks/hook-telemetry.ts +0 -43
  493. package/.next/standalone/src/hooks/hooks-config.ts +0 -166
  494. package/.next/standalone/src/hooks/install-prompt.ts +0 -357
  495. package/.next/standalone/src/hooks/llm-client.ts +0 -90
  496. package/.next/standalone/src/hooks/loader-utils.ts +0 -178
  497. package/.next/standalone/src/hooks/manager.ts +0 -692
  498. package/.next/standalone/src/hooks/policy-evaluator.ts +0 -224
  499. package/.next/standalone/src/hooks/policy-helpers.ts +0 -16
  500. package/.next/standalone/src/hooks/policy-registry.ts +0 -90
  501. package/.next/standalone/src/hooks/policy-types.ts +0 -77
  502. package/.next/standalone/src/hooks/types.ts +0 -63
  503. package/.next/standalone/src/index.ts +0 -19
  504. package/.next/standalone/src/posthog-key.ts +0 -5
  505. package/.next/standalone/tailwind.config.ts +0 -11
  506. package/.next/standalone/tsconfig.json +0 -42
  507. package/.next/standalone/vitest.config.e2e.mts +0 -24
  508. package/.next/standalone/vitest.config.mts +0 -23
  509. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → wOkJXoch1UmRAmyIuKZWc}/_buildManifest.js +0 -0
  510. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → wOkJXoch1UmRAmyIuKZWc}/_clientMiddlewareManifest.js +0 -0
  511. /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → wOkJXoch1UmRAmyIuKZWc}/_ssgManifest.js +0 -0
@@ -1,1613 +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",
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",
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",
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",
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
- const cwd = ctx.session?.cwd;
680
- if (!cwd) return allow(); // Can't enforce without cwd
681
-
682
- const allowPaths = ((ctx.params?.allowPaths ?? []) as string[]);
683
-
684
- // For Bash tool: check read-like commands for absolute paths outside cwd
685
- if (ctx.toolName === "Bash") {
686
- const cmd = getCommand(ctx);
687
- if (!READ_LIKE_CMDS.test(cmd)) return allow();
688
-
689
- const paths = extractAbsolutePaths(cmd);
690
- const cwdWithSep = cwd.endsWith("/") ? cwd : cwd + "/";
691
- for (const p of paths) {
692
- const resolved = resolve(cwd, p);
693
- if (isClaudeSettingsFile(resolved)) {
694
- return deny(`Reading Claude settings file blocked: ${resolved}`);
695
- }
696
- if (isClaudeInternalPath(resolved)) continue; // Whitelist ~/.claude/
697
- if (resolved === "/dev/null") continue; // Harmless special file
698
- if (resolved !== cwd && !resolved.startsWith(cwdWithSep)) {
699
- if (allowPaths.some((ap) => resolved === ap || resolved.startsWith(ap.endsWith("/") ? ap : ap + "/"))) continue;
700
- return deny(`Bash read outside project directory blocked: ${resolved}`);
701
- }
702
- }
703
- return allow();
704
- }
705
-
706
- // For Read/Glob/Grep: existing file_path / path check
707
- const filePath = getFilePath(ctx);
708
- const searchPath = (ctx.toolInput?.path as string) ?? "";
709
-
710
- const target = filePath || searchPath;
711
- if (!target) return allow();
712
-
713
- const resolved = resolve(cwd, target);
714
-
715
- // Block settings files in any .claude directory before whitelisting
716
- if (isClaudeSettingsFile(resolved)) {
717
- return deny(`Reading Claude settings file blocked: ${resolved}`);
718
- }
719
-
720
- // Whitelist ~/.claude/ — Claude Code's own config, plans, memory, and settings
721
- if (isClaudeInternalPath(resolved)) return allow();
722
-
723
- // Whitelist /dev/null — harmless special file commonly used in shell commands
724
- if (resolved === "/dev/null") return allow();
725
-
726
- const cwdWithSep = cwd.endsWith("/") ? cwd : cwd + "/";
727
- if (resolved !== cwd && !resolved.startsWith(cwdWithSep)) {
728
- if (allowPaths.some((ap) => resolved === ap || resolved.startsWith(ap.endsWith("/") ? ap : ap + "/"))) return allow();
729
- return deny(`Access outside project directory blocked: ${resolved}`);
730
- }
731
- return allow();
732
- }
733
-
734
- function blockWorkOnMain(ctx: PolicyContext): PolicyResult {
735
- if (ctx.toolName !== "Bash") return allow();
736
- const cmd = getCommand(ctx);
737
- if (!GIT_COMMIT_MERGE_RE.test(cmd)) return allow();
738
-
739
- const cwd = ctx.session?.cwd;
740
- if (!cwd) return allow();
741
-
742
- const branch = getCurrentBranch(cwd);
743
- if (!branch) return allow();
744
-
745
- const protectedBranches = ((ctx.params?.protectedBranches ?? ["main", "master"]) as string[]);
746
- if (protectedBranches.includes(branch)) {
747
- return deny(
748
- `Git ${cmd.match(/git\s+(\S+)/)?.[1] ?? "operation"} on ${branch} is blocked. Create a feature branch first.`,
749
- );
750
- }
751
- return allow();
752
- }
753
-
754
- function blockFailproofaiCommands(ctx: PolicyContext): PolicyResult {
755
- if (ctx.toolName !== "Bash") return allow();
756
- const cmd = getCommand(ctx);
757
-
758
- // Block direct failproofai CLI invocations
759
- if (FAILPROOFAI_CLI_RE.test(cmd)) {
760
- return deny("Running failproofai CLI commands is blocked");
761
- }
762
-
763
- // Block package-manager uninstallation of failproofai
764
- if (FAILPROOFAI_UNINSTALL_RE.test(cmd)) {
765
- return deny("Uninstalling failproofai is blocked");
766
- }
767
-
768
- return allow();
769
- }
770
-
771
- // Maximum size of the per-session tool-call sidecar before we stop updating it.
772
- // If exceeded, repeated-call detection degrades gracefully (allows through) rather
773
- // than growing the file unboundedly.
774
- const TOOL_CALL_TRACKER_MAX_BYTES = 65_536; // 64 KB
775
-
776
- async function warnRepeatedToolCalls(ctx: PolicyContext): Promise<PolicyResult> {
777
- const THRESHOLD = 3;
778
- const transcriptPath = ctx.session?.transcriptPath;
779
- if (!transcriptPath || !ctx.toolName || !ctx.toolInput) return allow();
780
-
781
- // Sidecar file tracks { fingerprint: count } — O(1) per call vs O(transcript) per call.
782
- const trackerPath = `${transcriptPath}.tool-calls.json`;
783
- const fingerprint = JSON.stringify({ tool: ctx.toolName, input: ctx.toolInput });
784
-
785
- let counts: Record<string, number> = {};
786
- try {
787
- const raw = await readFile(trackerPath, "utf8");
788
- counts = JSON.parse(raw) as Record<string, number>;
789
- } catch { /* first call or unreadable — start fresh */ }
790
-
791
- const prevCount = counts[fingerprint] ?? 0;
792
- if (prevCount >= THRESHOLD) {
793
- return instruct(
794
- `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.`,
795
- );
796
- }
797
-
798
- counts[fingerprint] = prevCount + 1;
799
- try {
800
- const serialized = JSON.stringify(counts);
801
- if (serialized.length <= TOOL_CALL_TRACKER_MAX_BYTES) {
802
- await writeFile(trackerPath, serialized, "utf8");
803
- }
804
- } catch { /* non-fatal */ }
805
-
806
- return allow();
807
- }
808
-
809
- function warnGitAmend(ctx: PolicyContext): PolicyResult {
810
- if (ctx.toolName !== "Bash") return allow();
811
- const cmd = getCommand(ctx);
812
- if (GIT_AMEND_RE.test(cmd)) {
813
- return instruct(
814
- "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.",
815
- );
816
- }
817
- return allow();
818
- }
819
-
820
- function warnGitStashDrop(ctx: PolicyContext): PolicyResult {
821
- if (ctx.toolName !== "Bash") return allow();
822
- const cmd = getCommand(ctx);
823
- if (GIT_STASH_DROP_RE.test(cmd)) {
824
- return instruct(
825
- "STOP: This command permanently deletes stashed changes (git stash drop/clear). Stash entries cannot be recovered after deletion. Confirm with the user before executing.",
826
- );
827
- }
828
- return allow();
829
- }
830
-
831
- function warnAllFilesStaged(ctx: PolicyContext): PolicyResult {
832
- if (ctx.toolName !== "Bash") return allow();
833
- const cmd = getCommand(ctx);
834
- if (GIT_ADD_ALL_RE.test(cmd)) {
835
- return instruct(
836
- "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.",
837
- );
838
- }
839
- return allow();
840
- }
841
-
842
- function warnSchemaAlteration(ctx: PolicyContext): PolicyResult {
843
- if (ctx.toolName !== "Bash") return allow();
844
- const cmd = getCommand(ctx);
845
- if (!SQL_TOOL_RE.test(cmd)) return allow();
846
- if (SCHEMA_ALTER_RE.test(cmd)) {
847
- return instruct(
848
- "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.",
849
- );
850
- }
851
- return allow();
852
- }
853
-
854
- function warnGlobalPackageInstall(ctx: PolicyContext): PolicyResult {
855
- if (ctx.toolName !== "Bash") return allow();
856
- const cmd = getCommand(ctx);
857
- const isGlobal =
858
- NPM_GLOBAL_RE.test(cmd) ||
859
- YARN_GLOBAL_RE.test(cmd) ||
860
- PNPM_GLOBAL_RE.test(cmd) ||
861
- BUN_GLOBAL_RE.test(cmd) ||
862
- CARGO_INSTALL_RE.test(cmd) ||
863
- // Bare 'pip install' respects the active venv when one is present;
864
- // only flag explicit system-level flags (--user, --break-system-packages).
865
- PIP_SYSTEM_RE.test(cmd);
866
- if (isGlobal) {
867
- return instruct(
868
- "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.",
869
- );
870
- }
871
- return allow();
872
- }
873
-
874
- // Split a compound shell command into independent segments.
875
- const SEGMENT_SPLIT_RE = /\s*(?:&&|\|\||\||;)\s*/;
876
-
877
- function preferPackageManager(ctx: PolicyContext): PolicyResult {
878
- if (ctx.toolName !== "Bash") return allow();
879
- const cmd = getCommand(ctx);
880
- if (!cmd) return allow();
881
-
882
- const allowed = (ctx.params?.allowed ?? []) as string[];
883
- if (allowed.length === 0) return allow();
884
-
885
- const allowedSet = new Set(allowed.map((a) => a.toLowerCase()));
886
- const blocked = (ctx.params?.blocked ?? []) as string[];
887
- const allowedList = allowed.join(", ");
888
-
889
- // Evaluate each shell segment independently so that
890
- // "uv --version && pip install flask" correctly denies the pip segment.
891
- const segments = cmd.split(SEGMENT_SPLIT_RE);
892
-
893
- for (const segment of segments) {
894
- const trimmed = segment.trim();
895
- if (!trimmed) continue;
896
-
897
- // Check if this segment uses an allowed manager — if so, skip it.
898
- let segmentAllowed = false;
899
- for (const manager of allowedSet) {
900
- const patterns = PKG_MANAGER_DETECTORS[manager];
901
- if (!patterns) continue;
902
- for (const pattern of patterns) {
903
- if (pattern.test(trimmed)) { segmentAllowed = true; break; }
904
- }
905
- if (segmentAllowed) break;
906
- }
907
- if (segmentAllowed) continue;
908
-
909
- // Check if this segment uses a non-allowed builtin manager.
910
- for (const [manager, patterns] of Object.entries(PKG_MANAGER_DETECTORS)) {
911
- if (allowedSet.has(manager)) continue;
912
- for (const pattern of patterns) {
913
- if (pattern.test(trimmed)) {
914
- return deny(
915
- `"${manager}" is not an allowed package manager. ` +
916
- `Allowed package managers for this project: ${allowedList}. ` +
917
- `Rewrite this command using an allowed package manager.`,
918
- );
919
- }
920
- }
921
- }
922
-
923
- // Check user-specified blocked managers.
924
- for (const name of blocked) {
925
- const lower = name.toLowerCase();
926
- if (allowedSet.has(lower)) continue;
927
- const re = new RegExp(`\\b${lower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
928
- if (re.test(trimmed)) {
929
- return deny(
930
- `"${lower}" is not an allowed package manager. ` +
931
- `Allowed package managers for this project: ${allowedList}. ` +
932
- `Rewrite this command using an allowed package manager.`,
933
- );
934
- }
935
- }
936
- }
937
-
938
- return allow();
939
- }
940
-
941
- function warnBackgroundProcess(ctx: PolicyContext): PolicyResult {
942
- if (ctx.toolName !== "Bash") return allow();
943
- const cmd = getCommand(ctx);
944
- const isBackground =
945
- NOHUP_RE.test(cmd) ||
946
- SCREEN_DETACH_RE.test(cmd) ||
947
- TMUX_DETACH_RE.test(cmd) ||
948
- DISOWN_RE.test(cmd) ||
949
- BACKGROUND_AMPERSAND_RE.test(cmd);
950
- if (isBackground) {
951
- return instruct(
952
- "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.",
953
- );
954
- }
955
- return allow();
956
- }
957
-
958
- // -- Workflow (Stop event) policies --
959
-
960
- function requireCommitBeforeStop(ctx: PolicyContext): PolicyResult {
961
- const cwd = ctx.session?.cwd;
962
- if (!cwd) return allow("No working directory available, skipping commit check.");
963
-
964
- try {
965
- const status = execSync("git status --porcelain", {
966
- cwd,
967
- encoding: "utf8",
968
- timeout: 5000,
969
- }).trim();
970
-
971
- if (status.length > 0) {
972
- return deny(
973
- "You have uncommitted changes in the working directory. Commit all changes now.",
974
- );
975
- }
976
- return allow("All changes are committed.");
977
- } catch {
978
- return allow("Not a git repository, skipping commit check.");
979
- }
980
- }
981
-
982
- function requirePushBeforeStop(ctx: PolicyContext): PolicyResult {
983
- const cwd = ctx.session?.cwd;
984
- if (!cwd) return allow("No working directory available, skipping push check.");
985
-
986
- try {
987
- const remotes = execSync("git remote", {
988
- cwd,
989
- encoding: "utf8",
990
- timeout: 3000,
991
- }).trim();
992
-
993
- if (!remotes) return allow("No git remote configured, skipping push check.");
994
-
995
- const remote = (ctx.params?.remote as string) ?? "origin";
996
-
997
- const branch = getCurrentBranch(cwd);
998
- if (!branch || branch === "HEAD") return allow("Detached HEAD, skipping push check.");
999
-
1000
- const baseBranch = (ctx.params?.baseBranch as string) ?? "main";
1001
-
1002
- // If on the base branch itself, no push of a feature branch is needed
1003
- if (branch === baseBranch) {
1004
- return allow(`On base branch "${baseBranch}", skipping push check.`);
1005
- }
1006
-
1007
- // Check if branch has diverged from base in any meaningful way
1008
- try {
1009
- const ahead = execFileSync(
1010
- "git",
1011
- ["log", `${remote}/${baseBranch}..HEAD`, "--oneline"],
1012
- { cwd, encoding: "utf8", timeout: 5000 },
1013
- ).trim();
1014
-
1015
- if (!ahead) {
1016
- // No commits ahead — branch is fully merged (regular merge / fast-forward)
1017
- return allow(`No commits ahead of ${remote}/${baseBranch}, skipping push check.`);
1018
- }
1019
-
1020
- // Commits exist but might be from a squash-merged PR.
1021
- // Check actual file diff — if trees are identical, work is already in base.
1022
- const diff = execFileSync(
1023
- "git",
1024
- ["diff", "--stat", `${remote}/${baseBranch}`, "HEAD"],
1025
- { cwd, encoding: "utf8", timeout: 5000 },
1026
- ).trim();
1027
-
1028
- if (!diff) {
1029
- return allow(`No file changes compared to ${remote}/${baseBranch}, skipping push check.`);
1030
- }
1031
- } catch {
1032
- // remote/{baseBranch} ref missing — fall through to existing push checks
1033
- }
1034
-
1035
- // Check if remote tracking branch exists
1036
- let hasTracking = false;
1037
- try {
1038
- execFileSync("git", ["rev-parse", "--verify", `${remote}/${branch}`], {
1039
- cwd,
1040
- encoding: "utf8",
1041
- timeout: 3000,
1042
- });
1043
- hasTracking = true;
1044
- } catch {
1045
- // Remote tracking branch does not exist
1046
- }
1047
-
1048
- if (!hasTracking) {
1049
- return deny(
1050
- `Branch "${branch}" has not been pushed to remote "${remote}". ` +
1051
- `Run now: git push -u ${remote} ${branch}`,
1052
- );
1053
- }
1054
-
1055
- // Check for unpushed commits
1056
- const unpushed = execFileSync("git", ["log", `${remote}/${branch}..HEAD`, "--oneline"], {
1057
- cwd,
1058
- encoding: "utf8",
1059
- timeout: 5000,
1060
- }).trim();
1061
-
1062
- if (unpushed.length > 0) {
1063
- const commitCount = unpushed.split("\n").length;
1064
- return deny(
1065
- `You have ${commitCount} unpushed commit${commitCount > 1 ? "s" : ""} on branch "${branch}". ` +
1066
- `Run now: git push`,
1067
- );
1068
- }
1069
-
1070
- return allow(`All commits pushed to "${remote}".`);
1071
- } catch {
1072
- return allow("Could not check push status, skipping.");
1073
- }
1074
- }
1075
-
1076
- function requirePrBeforeStop(ctx: PolicyContext): PolicyResult {
1077
- const cwd = ctx.session?.cwd;
1078
- if (!cwd) return allow("No working directory available, skipping PR check.");
1079
-
1080
- try {
1081
- // Check if gh CLI is available
1082
- try {
1083
- execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
1084
- } catch {
1085
- return allow("GitHub CLI (gh) not installed, skipping PR check.");
1086
- }
1087
-
1088
- const branch = getCurrentBranch(cwd);
1089
- if (!branch || branch === "HEAD") return allow("Detached HEAD, skipping PR check.");
1090
-
1091
- const baseBranch = (ctx.params?.baseBranch as string) ?? "main";
1092
-
1093
- // If on the base branch itself, no PR is needed
1094
- if (branch === baseBranch) {
1095
- return allow(`On base branch "${baseBranch}", skipping PR check.`);
1096
- }
1097
-
1098
- // Check if branch has diverged from base in any meaningful way
1099
- try {
1100
- const ahead = execFileSync(
1101
- "git",
1102
- ["log", `origin/${baseBranch}..HEAD`, "--oneline"],
1103
- { cwd, encoding: "utf8", timeout: 5000 },
1104
- ).trim();
1105
-
1106
- if (!ahead) {
1107
- // No commits ahead — branch is fully merged (regular merge / fast-forward)
1108
- return allow(`No commits ahead of origin/${baseBranch}, skipping PR check.`);
1109
- }
1110
-
1111
- // Commits exist but might be from a squash-merged PR.
1112
- // Check actual file diff — if trees are identical, work is already in main.
1113
- const diff = execFileSync(
1114
- "git",
1115
- ["diff", "--stat", `origin/${baseBranch}`, "HEAD"],
1116
- { cwd, encoding: "utf8", timeout: 5000 },
1117
- ).trim();
1118
-
1119
- if (!diff) {
1120
- return allow(`No file changes compared to origin/${baseBranch}, skipping PR check.`);
1121
- }
1122
- } catch {
1123
- // origin/{baseBranch} ref missing or git error — fall through to gh pr view
1124
- }
1125
-
1126
- // Check if a PR exists for this branch
1127
- let prJson: string;
1128
- try {
1129
- prJson = execSync("gh pr view --json number,url,state", {
1130
- cwd,
1131
- encoding: "utf8",
1132
- timeout: 15000,
1133
- }).trim();
1134
- } catch {
1135
- // gh pr view exits non-zero when no PR exists
1136
- return deny(
1137
- `No pull request found for branch "${branch}". ` +
1138
- `Run now: gh pr create`,
1139
- );
1140
- }
1141
-
1142
- const pr = JSON.parse(prJson) as { number: number; url: string; state: string };
1143
-
1144
- if (pr.state === "OPEN") {
1145
- return allow(`PR #${pr.number} exists: ${pr.url}`);
1146
- }
1147
-
1148
- // PR is merged/closed. The earlier origin/{baseBranch} checks may have
1149
- // used a stale ref. Fetch and re-verify before denying.
1150
- if (pr.state === "MERGED") {
1151
- try {
1152
- execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
1153
- cwd,
1154
- encoding: "utf8",
1155
- timeout: 10000,
1156
- });
1157
- const freshAhead = execFileSync(
1158
- "git",
1159
- ["log", `origin/${baseBranch}..HEAD`, "--oneline"],
1160
- { cwd, encoding: "utf8", timeout: 5000 },
1161
- ).trim();
1162
- if (!freshAhead) {
1163
- return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
1164
- }
1165
- const freshDiff = execFileSync(
1166
- "git",
1167
- ["diff", "--stat", `origin/${baseBranch}`, "HEAD"],
1168
- { cwd, encoding: "utf8", timeout: 5000 },
1169
- ).trim();
1170
- if (!freshDiff) {
1171
- return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
1172
- }
1173
- } catch {
1174
- // Fetch or git command failed — fall through to deny
1175
- }
1176
- }
1177
-
1178
- return deny(
1179
- `Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`,
1180
- );
1181
- } catch {
1182
- return allow("Could not check PR status, skipping.");
1183
- }
1184
- }
1185
-
1186
- function requireCiGreenBeforeStop(ctx: PolicyContext): PolicyResult {
1187
- const cwd = ctx.session?.cwd;
1188
- if (!cwd) return allow("No working directory available, skipping CI check.");
1189
-
1190
- try {
1191
- // Check if gh CLI is available
1192
- try {
1193
- execSync("gh --version", { cwd, encoding: "utf8", timeout: 3000 });
1194
- } catch {
1195
- return allow("GitHub CLI (gh) not installed, skipping CI check.");
1196
- }
1197
-
1198
- const branch = getCurrentBranch(cwd);
1199
- if (!branch || branch === "HEAD") return allow("Detached HEAD, skipping CI check.");
1200
-
1201
- // 1. GitHub Actions workflow runs
1202
- let workflowRuns: CiCheck[] = [];
1203
- try {
1204
- const runsJson = execFileSync(
1205
- "gh",
1206
- ["run", "list", "--branch", branch, "--limit", "5", "--json", "status,conclusion,name"],
1207
- { cwd, encoding: "utf8", timeout: 15000 },
1208
- ).trim();
1209
-
1210
- if (runsJson && runsJson !== "[]") {
1211
- workflowRuns = JSON.parse(runsJson) as CiCheck[];
1212
- }
1213
- } catch {
1214
- // fail-open for workflow runs; continue to check third-party checks
1215
- }
1216
-
1217
- // 2. Third-party check runs (CodeRabbit, SonarCloud, Codecov, etc.)
1218
- let thirdPartyChecks: CiCheck[] = [];
1219
- let commitStatuses: CiCheck[] = [];
1220
- const sha = getHeadSha(cwd);
1221
- if (sha) {
1222
- thirdPartyChecks = getThirdPartyCheckRuns(cwd, sha);
1223
- commitStatuses = getCommitStatuses(cwd, sha);
1224
- }
1225
-
1226
- // 3. Merge all checks
1227
- const allChecks = [...workflowRuns, ...thirdPartyChecks, ...commitStatuses];
1228
-
1229
- if (allChecks.length === 0) return allow(`No CI runs found for branch "${branch}".`);
1230
-
1231
- const failing = allChecks.filter(
1232
- (r) =>
1233
- r.status === "completed" &&
1234
- r.conclusion !== "success" &&
1235
- r.conclusion !== "skipped" &&
1236
- r.conclusion !== "cancelled",
1237
- );
1238
- if (failing.length > 0) {
1239
- const names = failing.map((r) => `"${r.name}"`).join(", ");
1240
- return deny(
1241
- `CI checks are failing on branch "${branch}": ${names}. Fix the failing checks now.`,
1242
- );
1243
- }
1244
-
1245
- const pending = allChecks.filter(
1246
- (r) => r.status === "in_progress" || r.status === "queued" || r.status === "waiting",
1247
- );
1248
- if (pending.length > 0) {
1249
- const names = pending.map((r) => `"${r.name}"`).join(", ");
1250
- return deny(
1251
- `CI checks are still running on branch "${branch}": ${names}. Wait for all checks to complete, then verify they pass.`,
1252
- );
1253
- }
1254
-
1255
- return allow(`All CI checks passed on branch "${branch}".`);
1256
- } catch {
1257
- return allow("Could not check CI status, skipping.");
1258
- }
1259
- }
1260
-
1261
- // -- Registry --
1262
-
1263
- export const BUILTIN_POLICIES: BuiltinPolicyDefinition[] = [
1264
- {
1265
- name: "sanitize-jwt",
1266
- description: "Stop Claude from reading JWTs in tool responses",
1267
- fn: sanitizeJwt,
1268
- match: { events: ["PostToolUse"] },
1269
- defaultEnabled: true,
1270
- category: "Sanitize",
1271
- },
1272
- {
1273
- name: "sanitize-api-keys",
1274
- description: "Stop Claude from reading API keys (OpenAI, Anthropic, GitHub, AWS, Stripe, Google) in tool responses",
1275
- fn: sanitizeApiKeys,
1276
- match: { events: ["PostToolUse"] },
1277
- defaultEnabled: true,
1278
- category: "Sanitize",
1279
- params: {
1280
- additionalPatterns: {
1281
- type: "pattern[]",
1282
- description: "Additional API key patterns to scrub, each with { regex, label }",
1283
- default: [],
1284
- },
1285
- } satisfies PolicyParamsSchema,
1286
- },
1287
- {
1288
- name: "sanitize-connection-strings",
1289
- description: "Stop Claude from reading database connection strings with embedded credentials in tool responses",
1290
- fn: sanitizeConnectionStrings,
1291
- match: { events: ["PostToolUse"] },
1292
- defaultEnabled: true,
1293
- category: "Sanitize",
1294
- },
1295
- {
1296
- name: "sanitize-private-key-content",
1297
- description: "Stop Claude from reading PEM private key content in tool responses",
1298
- fn: sanitizePrivateKeyContent,
1299
- match: { events: ["PostToolUse"] },
1300
- defaultEnabled: true,
1301
- category: "Sanitize",
1302
- },
1303
- {
1304
- name: "sanitize-bearer-tokens",
1305
- description: "Stop Claude from reading Authorization Bearer tokens in tool responses",
1306
- fn: sanitizeBearerTokens,
1307
- match: { events: ["PostToolUse"] },
1308
- defaultEnabled: true,
1309
- category: "Sanitize",
1310
- },
1311
- {
1312
- name: "protect-env-vars",
1313
- description: "Prevent commands that read environment variables",
1314
- fn: protectEnvVars,
1315
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1316
- defaultEnabled: true,
1317
- category: "Environment",
1318
- },
1319
- {
1320
- name: "block-env-files",
1321
- description: "Block reading/writing .env files",
1322
- fn: blockEnvFiles,
1323
- match: { events: ["PreToolUse"] },
1324
- defaultEnabled: true,
1325
- category: "Environment",
1326
- },
1327
- {
1328
- name: "block-read-outside-cwd",
1329
- description: "Block file reads outside the session working directory",
1330
- fn: blockReadOutsideCwd,
1331
- match: { events: ["PreToolUse"], toolNames: ["Read", "Glob", "Grep", "Bash"] },
1332
- defaultEnabled: false,
1333
- category: "Environment",
1334
- params: {
1335
- allowPaths: {
1336
- type: "string[]",
1337
- description: "Absolute paths outside cwd that are allowed to be read",
1338
- default: [],
1339
- },
1340
- } satisfies PolicyParamsSchema,
1341
- },
1342
- {
1343
- name: "block-sudo",
1344
- description: "Block sudo commands",
1345
- fn: blockSudo,
1346
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1347
- defaultEnabled: true,
1348
- category: "Dangerous Commands",
1349
- params: {
1350
- allowPatterns: {
1351
- type: "string[]",
1352
- description: "Sudo command patterns to allow, matched token-by-token (e.g. 'sudo systemctl status')",
1353
- default: [],
1354
- },
1355
- } satisfies PolicyParamsSchema,
1356
- },
1357
- {
1358
- name: "block-curl-pipe-sh",
1359
- description: "Block piping downloads to shell",
1360
- fn: blockCurlPipeSh,
1361
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1362
- defaultEnabled: true,
1363
- category: "Dangerous Commands",
1364
- },
1365
- {
1366
- name: "block-rm-rf",
1367
- description: "Prevent catastrophic deletions",
1368
- fn: blockRmRf,
1369
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1370
- defaultEnabled: false,
1371
- category: "Dangerous Commands",
1372
- params: {
1373
- allowPaths: {
1374
- type: "string[]",
1375
- description: "Paths that are allowed to be recursively deleted",
1376
- default: [],
1377
- },
1378
- } satisfies PolicyParamsSchema,
1379
- },
1380
- {
1381
- name: "block-failproofai-commands",
1382
- description: "Block failproofai CLI commands and uninstallation",
1383
- fn: blockFailproofaiCommands,
1384
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1385
- defaultEnabled: true,
1386
- category: "Dangerous Commands",
1387
- },
1388
- {
1389
- name: "block-secrets-write",
1390
- description: "Block writing secret key files",
1391
- fn: blockSecretsWrite,
1392
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1393
- defaultEnabled: false,
1394
- category: "Dangerous Commands",
1395
- params: {
1396
- additionalPatterns: {
1397
- type: "string[]",
1398
- description: "Additional filename patterns (substrings) to block",
1399
- default: [],
1400
- },
1401
- } satisfies PolicyParamsSchema,
1402
- },
1403
- {
1404
- name: "block-push-master",
1405
- description: "Block pushing to main/master",
1406
- fn: blockPushMaster,
1407
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1408
- defaultEnabled: true,
1409
- category: "Git",
1410
- params: {
1411
- protectedBranches: {
1412
- type: "string[]",
1413
- description: "Branch names to protect from direct pushes",
1414
- default: ["main", "master"],
1415
- },
1416
- } satisfies PolicyParamsSchema,
1417
- },
1418
- {
1419
- name: "block-force-push",
1420
- description: "Prevent force-pushing to any branch",
1421
- fn: blockForcePush,
1422
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1423
- defaultEnabled: false,
1424
- category: "Git",
1425
- },
1426
- {
1427
- name: "block-work-on-main",
1428
- description: "Block git commits and merges on main/master branch",
1429
- fn: blockWorkOnMain,
1430
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1431
- defaultEnabled: false,
1432
- category: "Git",
1433
- params: {
1434
- protectedBranches: {
1435
- type: "string[]",
1436
- description: "Branch names where commits/merges are blocked",
1437
- default: ["main", "master"],
1438
- },
1439
- } satisfies PolicyParamsSchema,
1440
- },
1441
- {
1442
- name: "warn-git-amend",
1443
- description: "Warns before amending git commits, which rewrites history",
1444
- fn: warnGitAmend,
1445
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1446
- defaultEnabled: false,
1447
- category: "Git",
1448
- },
1449
- {
1450
- name: "warn-git-stash-drop",
1451
- description: "Warns before permanently deleting stashed changes",
1452
- fn: warnGitStashDrop,
1453
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1454
- defaultEnabled: false,
1455
- category: "Git",
1456
- },
1457
- {
1458
- name: "warn-all-files-staged",
1459
- description: "Warns before staging all working tree files with git add -A / . / --all",
1460
- fn: warnAllFilesStaged,
1461
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1462
- defaultEnabled: false,
1463
- category: "Git",
1464
- },
1465
- {
1466
- name: "warn-destructive-sql",
1467
- description: "Warn before executing destructive SQL (DROP/TRUNCATE/DELETE without WHERE) via database clients",
1468
- fn: warnDestructiveSql,
1469
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1470
- defaultEnabled: false,
1471
- category: "Database",
1472
- },
1473
- {
1474
- name: "warn-schema-alteration",
1475
- description: "Warns before SQL schema changes (ALTER TABLE with column or rename operations)",
1476
- fn: warnSchemaAlteration,
1477
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1478
- defaultEnabled: false,
1479
- category: "Database",
1480
- },
1481
- {
1482
- name: "warn-package-publish",
1483
- description: "Warn before publishing packages to public registries (npm, PyPI, crates.io, RubyGems, etc.)",
1484
- fn: warnPackagePublish,
1485
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1486
- defaultEnabled: false,
1487
- category: "Packages & System",
1488
- },
1489
- {
1490
- name: "warn-global-package-install",
1491
- description: "Warns before installing packages globally (npm -g, cargo install, etc.)",
1492
- fn: warnGlobalPackageInstall,
1493
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1494
- defaultEnabled: false,
1495
- category: "Packages & System",
1496
- },
1497
- {
1498
- name: "prefer-package-manager",
1499
- description: "Blocks non-preferred package managers and tells Claude to use an allowed one (e.g., uv instead of pip)",
1500
- fn: preferPackageManager,
1501
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1502
- defaultEnabled: false,
1503
- category: "Packages & System",
1504
- params: {
1505
- allowed: {
1506
- type: "string[]",
1507
- description: "Allowed package manager names (e.g. ['uv', 'bun']). Any detected manager not in this list is blocked.",
1508
- default: [],
1509
- },
1510
- blocked: {
1511
- type: "string[]",
1512
- description: "Additional manager names to block beyond the built-in list (e.g. ['pdm', 'pipx']).",
1513
- default: [],
1514
- },
1515
- } satisfies PolicyParamsSchema,
1516
- },
1517
- {
1518
- name: "warn-large-file-write",
1519
- description: "Warn before writing files larger than 1MB (configurable via thresholdKb param)",
1520
- fn: warnLargeFileWrite,
1521
- match: { events: ["PreToolUse"], toolNames: ["Write"] },
1522
- defaultEnabled: false,
1523
- category: "Packages & System",
1524
- params: {
1525
- thresholdKb: {
1526
- type: "number",
1527
- description: "File size threshold in KB above which a warning is issued",
1528
- default: 1024,
1529
- },
1530
- } satisfies PolicyParamsSchema,
1531
- },
1532
- {
1533
- name: "warn-background-process",
1534
- description: "Warns before starting detached or background processes",
1535
- fn: warnBackgroundProcess,
1536
- match: { events: ["PreToolUse"], toolNames: ["Bash"] },
1537
- defaultEnabled: false,
1538
- category: "Packages & System",
1539
- },
1540
- {
1541
- name: "warn-repeated-tool-calls",
1542
- description: "Warn when the same tool is called 3+ times with identical parameters",
1543
- fn: warnRepeatedToolCalls,
1544
- match: { events: ["PreToolUse"] },
1545
- defaultEnabled: false,
1546
- category: "AI Behavior",
1547
- },
1548
- {
1549
- name: "require-commit-before-stop",
1550
- description: "Require all changes to be committed before Claude stops",
1551
- fn: requireCommitBeforeStop,
1552
- match: { events: ["Stop"] },
1553
- defaultEnabled: false,
1554
- category: "Workflow",
1555
- },
1556
- {
1557
- name: "require-push-before-stop",
1558
- description: "Require all commits to be pushed to remote before Claude stops",
1559
- fn: requirePushBeforeStop,
1560
- match: { events: ["Stop"] },
1561
- defaultEnabled: false,
1562
- category: "Workflow",
1563
- params: {
1564
- remote: {
1565
- type: "string",
1566
- description: "Remote name to push to (default: origin)",
1567
- default: "origin",
1568
- },
1569
- baseBranch: {
1570
- type: "string",
1571
- description: "Base branch to compare against (default: main)",
1572
- default: "main",
1573
- },
1574
- } satisfies PolicyParamsSchema,
1575
- },
1576
- {
1577
- name: "require-pr-before-stop",
1578
- description: "Require a pull request to exist for the current branch before Claude stops",
1579
- fn: requirePrBeforeStop,
1580
- match: { events: ["Stop"] },
1581
- defaultEnabled: false,
1582
- category: "Workflow",
1583
- params: {
1584
- baseBranch: {
1585
- type: "string",
1586
- description: "Base branch to compare against (default: main)",
1587
- default: "main",
1588
- },
1589
- } satisfies PolicyParamsSchema,
1590
- },
1591
- {
1592
- name: "require-ci-green-before-stop",
1593
- description: "Require CI checks to pass on the current branch before Claude stops",
1594
- fn: requireCiGreenBeforeStop,
1595
- match: { events: ["Stop"] },
1596
- defaultEnabled: false,
1597
- category: "Workflow",
1598
- },
1599
- ];
1600
-
1601
- export function registerBuiltinPolicies(enabledNames: string[]): void {
1602
- const enabledSet = new Set(enabledNames);
1603
- for (const policy of BUILTIN_POLICIES) {
1604
- if (enabledSet.has(policy.name)) {
1605
- registerPolicy(policy.name, policy.description, policy.fn, policy.match);
1606
- }
1607
- }
1608
- }
1609
-
1610
- /** Clears the git branch cache. Exposed for test isolation only. */
1611
- export function clearGitBranchCache(): void {
1612
- gitBranchCache.clear();
1613
- }