cc-safe-setup 29.6.32 → 29.6.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/COOKBOOK.md +70 -0
  2. package/README.md +6 -2
  3. package/examples/absolute-rule-enforcer.sh +42 -0
  4. package/examples/allow-claude-settings.sh +2 -0
  5. package/examples/allow-git-hooks-dir.sh +2 -0
  6. package/examples/allow-protected-dirs.sh +2 -0
  7. package/examples/allowlist.sh +2 -0
  8. package/examples/ansible-vault-guard.sh +2 -0
  9. package/examples/auto-approve-build.sh +2 -0
  10. package/examples/auto-approve-compound-git.sh +2 -0
  11. package/examples/auto-approve-docker.sh +2 -0
  12. package/examples/auto-approve-git-read.sh +2 -0
  13. package/examples/auto-approve-python.sh +2 -0
  14. package/examples/auto-approve-readonly-tools.sh +2 -0
  15. package/examples/auto-approve-ssh.sh +2 -0
  16. package/examples/auto-approve-test.sh +2 -0
  17. package/examples/auto-checkpoint.sh +2 -0
  18. package/examples/auto-git-checkpoint.sh +2 -0
  19. package/examples/auto-mode-safe-commands.sh +2 -0
  20. package/examples/auto-snapshot.sh +2 -0
  21. package/examples/backup-before-refactor.sh +2 -0
  22. package/examples/banned-command-guard.sh +3 -3
  23. package/examples/bash-domain-allowlist.sh +72 -0
  24. package/examples/bash-safety-auto-deny.sh +56 -0
  25. package/examples/bash-secret-output-detector.sh +68 -0
  26. package/examples/bash-timeout-guard.sh +2 -0
  27. package/examples/bashrc-safety-check.sh +59 -0
  28. package/examples/bg-task-cooldown-guard.sh +46 -0
  29. package/examples/block-database-wipe.sh +2 -0
  30. package/examples/branch-name-check.sh +2 -0
  31. package/examples/branch-naming-convention.sh +2 -0
  32. package/examples/cargo-publish-guard.sh +2 -0
  33. package/examples/check-abort-controller.sh +2 -0
  34. package/examples/check-accessibility.sh +2 -0
  35. package/examples/check-aria-labels.sh +2 -0
  36. package/examples/check-async-await-consistency.sh +2 -0
  37. package/examples/check-before-act-enforcer.sh +47 -0
  38. package/examples/check-charset-meta.sh +2 -0
  39. package/examples/check-cleanup-effect.sh +2 -0
  40. package/examples/check-content-type.sh +2 -0
  41. package/examples/check-controlled-input.sh +2 -0
  42. package/examples/check-cookie-flags.sh +2 -0
  43. package/examples/check-cors-config.sh +2 -0
  44. package/examples/check-csp-headers.sh +2 -0
  45. package/examples/check-csrf-protection.sh +2 -0
  46. package/examples/check-debounce.sh +2 -0
  47. package/examples/check-dependency-age.sh +2 -0
  48. package/examples/check-dependency-license.sh +2 -0
  49. package/examples/check-dockerfile-best-practice.sh +2 -0
  50. package/examples/check-error-boundaries.sh +2 -0
  51. package/examples/check-error-class.sh +2 -0
  52. package/examples/check-error-handling.sh +2 -0
  53. package/examples/check-error-logging.sh +2 -0
  54. package/examples/check-error-message.sh +2 -0
  55. package/examples/check-error-page.sh +2 -0
  56. package/examples/check-error-stack.sh +2 -0
  57. package/examples/check-favicon.sh +2 -0
  58. package/examples/check-form-validation.sh +2 -0
  59. package/examples/check-git-hooks-compat.sh +2 -0
  60. package/examples/check-https-redirect.sh +2 -0
  61. package/examples/check-image-optimization.sh +2 -0
  62. package/examples/check-input-validation.sh +2 -0
  63. package/examples/check-key-prop.sh +2 -0
  64. package/examples/check-lang-attribute.sh +2 -0
  65. package/examples/check-lazy-loading.sh +2 -0
  66. package/examples/check-loading-state.sh +2 -0
  67. package/examples/check-memo-deps.sh +2 -0
  68. package/examples/check-meta-description.sh +2 -0
  69. package/examples/check-npm-scripts-exist.sh +2 -0
  70. package/examples/check-null-check.sh +2 -0
  71. package/examples/check-package-size.sh +2 -0
  72. package/examples/check-pagination.sh +2 -0
  73. package/examples/check-port-availability.sh +2 -0
  74. package/examples/check-promise-all.sh +2 -0
  75. package/examples/check-prop-types.sh +2 -0
  76. package/examples/check-rate-limiting.sh +2 -0
  77. package/examples/check-responsive-design.sh +2 -0
  78. package/examples/check-retry-logic.sh +2 -0
  79. package/examples/check-return-types.sh +2 -0
  80. package/examples/check-semantic-html.sh +2 -0
  81. package/examples/check-semantic-versioning.sh +2 -0
  82. package/examples/check-suspense-fallback.sh +2 -0
  83. package/examples/check-test-naming.sh +2 -0
  84. package/examples/check-timeout-cleanup.sh +2 -0
  85. package/examples/check-tls-version.sh +2 -0
  86. package/examples/check-type-coercion.sh +2 -0
  87. package/examples/check-unsubscribe.sh +2 -0
  88. package/examples/check-viewport-meta.sh +2 -0
  89. package/examples/check-worker-terminate.sh +2 -0
  90. package/examples/checkpoint-tamper-guard.sh +2 -0
  91. package/examples/chmod-guard.sh +2 -0
  92. package/examples/chown-guard.sh +2 -0
  93. package/examples/ci-workflow-guard.sh +59 -0
  94. package/examples/classifier-fallback-allow.sh +2 -0
  95. package/examples/claude-cache-gc.sh +15 -0
  96. package/examples/claudeignore-enforce-guard.sh +60 -0
  97. package/examples/claudemd-enforcer.sh +2 -0
  98. package/examples/commit-message-check.sh +2 -0
  99. package/examples/compact-blocker.sh +25 -0
  100. package/examples/composer-guard.sh +2 -0
  101. package/examples/compound-command-allow.sh +2 -0
  102. package/examples/consecutive-failure-circuit-breaker.sh +49 -0
  103. package/examples/console-log-count.sh +2 -0
  104. package/examples/context-compact-advisor.sh +16 -0
  105. package/examples/cors-star-warn.sh +2 -0
  106. package/examples/credential-exfil-guard.sh +2 -0
  107. package/examples/credential-file-cat-guard.sh +2 -0
  108. package/examples/cron-modification-guard.sh +40 -0
  109. package/examples/cwd-project-boundary-guard.sh +50 -0
  110. package/examples/denied-action-retry-guard.sh +41 -0
  111. package/examples/dependency-install-guard.sh +2 -0
  112. package/examples/deploy-guard.sh +2 -0
  113. package/examples/deploy-path-verify-guard.sh +62 -0
  114. package/examples/django-migrate-guard.sh +2 -0
  115. package/examples/docker-volume-guard.sh +2 -0
  116. package/examples/dockerfile-latest-guard.sh +2 -0
  117. package/examples/dotenv-commit-guard.sh +44 -0
  118. package/examples/dotenv-example-sync.sh +55 -0
  119. package/examples/dotnet-build-on-edit.sh +2 -0
  120. package/examples/drizzle-migrate-guard.sh +2 -0
  121. package/examples/edit-counter-test-gate.sh +44 -0
  122. package/examples/edit-error-counter.sh +2 -0
  123. package/examples/edit-guard.sh +2 -0
  124. package/examples/edit-retry-loop-guard.sh +2 -0
  125. package/examples/edit-verify.sh +2 -0
  126. package/examples/enforce-tests.sh +2 -0
  127. package/examples/env-inherit-guard.sh +2 -0
  128. package/examples/env-inline-secret-guard.sh +36 -0
  129. package/examples/env-prod-guard.sh +2 -0
  130. package/examples/env-required-check.sh +2 -0
  131. package/examples/env-var-check.sh +2 -0
  132. package/examples/expo-eject-guard.sh +2 -0
  133. package/examples/export-overwrite-guard.sh +29 -0
  134. package/examples/file-change-tracker.sh +2 -0
  135. package/examples/file-change-undo-tracker.sh +46 -0
  136. package/examples/file-recycle-bin.sh +48 -0
  137. package/examples/file-size-limit.sh +2 -0
  138. package/examples/five-hundred-milestone.sh +2 -0
  139. package/examples/flask-debug-guard.sh +2 -0
  140. package/examples/gem-push-guard.sh +2 -0
  141. package/examples/git-checkout-safety-guard.sh +2 -0
  142. package/examples/git-config-guard.sh +2 -0
  143. package/examples/git-hook-bypass-guard.sh +2 -0
  144. package/examples/git-merge-conflict-prevent.sh +2 -0
  145. package/examples/git-message-length.sh +2 -0
  146. package/examples/git-show-flag-sanitizer.sh +41 -0
  147. package/examples/git-stash-before-danger.sh +2 -0
  148. package/examples/git-submodule-guard.sh +2 -0
  149. package/examples/git-tag-guard.sh +2 -0
  150. package/examples/github-actions-secret-guard.sh +59 -0
  151. package/examples/gitignore-check.sh +2 -0
  152. package/examples/gitops-drift-guard.sh +53 -0
  153. package/examples/go-mod-tidy-warn.sh +2 -0
  154. package/examples/hallucination-url-check.sh +2 -0
  155. package/examples/hardcoded-ip-guard.sh +2 -0
  156. package/examples/headless-empty-result-guard.sh +46 -0
  157. package/examples/headless-stop-guard.sh +43 -0
  158. package/examples/helm-install-guard.sh +2 -0
  159. package/examples/issue-draft-redact-guard.sh +45 -0
  160. package/examples/java-compile-on-edit.sh +2 -0
  161. package/examples/k8s-production-guard.sh +77 -0
  162. package/examples/laravel-artisan-guard.sh +2 -0
  163. package/examples/large-file-guard.sh +2 -0
  164. package/examples/log-level-guard.sh +2 -0
  165. package/examples/magic-number-warn.sh +2 -0
  166. package/examples/max-edit-size-guard.sh +2 -0
  167. package/examples/max-file-count-guard.sh +2 -0
  168. package/examples/max-file-delete-count.sh +2 -0
  169. package/examples/max-function-length.sh +2 -0
  170. package/examples/max-import-count.sh +2 -0
  171. package/examples/max-subagent-count.sh +2 -0
  172. package/examples/mcp-orphan-process-guard.sh +39 -0
  173. package/examples/mcp-server-allowlist.sh +45 -0
  174. package/examples/mcp-tool-audit-log.sh +41 -0
  175. package/examples/mcp-tool-guard.sh +2 -0
  176. package/examples/migration-verify-guard.sh +44 -0
  177. package/examples/monorepo-scope-guard.sh +2 -0
  178. package/examples/network-exfil-guard.sh +61 -0
  179. package/examples/network-guard.sh +2 -0
  180. package/examples/nextjs-env-guard.sh +2 -0
  181. package/examples/no-absolute-import.sh +2 -0
  182. package/examples/no-alert-confirm-prompt.sh +2 -0
  183. package/examples/no-any-type.sh +2 -0
  184. package/examples/no-any-typescript.sh +2 -0
  185. package/examples/no-assignment-in-condition.sh +2 -0
  186. package/examples/no-callback-hell.sh +2 -0
  187. package/examples/no-catch-all-route.sh +2 -0
  188. package/examples/no-circular-dependency.sh +2 -0
  189. package/examples/no-class-in-functional.sh +2 -0
  190. package/examples/no-cleartext-storage.sh +2 -0
  191. package/examples/no-commented-code.sh +2 -0
  192. package/examples/no-commit-fixup.sh +2 -0
  193. package/examples/no-console-assert.sh +2 -0
  194. package/examples/no-console-error-swallow.sh +2 -0
  195. package/examples/no-console-in-prod.sh +2 -0
  196. package/examples/no-console-log.sh +2 -0
  197. package/examples/no-console-time.sh +2 -0
  198. package/examples/no-cors-wildcard.sh +2 -0
  199. package/examples/no-curl-upload.sh +2 -0
  200. package/examples/no-dangerouslySetInnerHTML.sh +2 -0
  201. package/examples/no-dangling-await.sh +2 -0
  202. package/examples/no-debug-in-commit.sh +2 -0
  203. package/examples/no-deep-nesting.sh +2 -0
  204. package/examples/no-deep-relative-import.sh +2 -0
  205. package/examples/no-default-credentials.sh +2 -0
  206. package/examples/no-deprecated-api.sh +2 -0
  207. package/examples/no-direct-dom-manipulation.sh +2 -0
  208. package/examples/no-disabled-test.sh +2 -0
  209. package/examples/no-document-cookie.sh +2 -0
  210. package/examples/no-document-write.sh +2 -0
  211. package/examples/no-empty-function.sh +2 -0
  212. package/examples/no-eval-in-template.sh +2 -0
  213. package/examples/no-eval-template.sh +2 -0
  214. package/examples/no-eval.sh +2 -0
  215. package/examples/no-exec-user-input.sh +2 -0
  216. package/examples/no-expose-internal-ids.sh +2 -0
  217. package/examples/no-floating-promises.sh +2 -0
  218. package/examples/no-force-install.sh +2 -0
  219. package/examples/no-git-rebase-public.sh +2 -0
  220. package/examples/no-global-state.sh +2 -0
  221. package/examples/no-hardcoded-port.sh +2 -0
  222. package/examples/no-hardcoded-url.sh +2 -0
  223. package/examples/no-helmet-missing.sh +2 -0
  224. package/examples/no-http-url.sh +2 -0
  225. package/examples/no-http-without-https.sh +2 -0
  226. package/examples/no-index-as-key.sh +2 -0
  227. package/examples/no-infinite-scroll-mem.sh +2 -0
  228. package/examples/no-inline-event-handler.sh +2 -0
  229. package/examples/no-inline-handler.sh +2 -0
  230. package/examples/no-inline-style.sh +2 -0
  231. package/examples/no-inline-styles.sh +2 -0
  232. package/examples/no-innerhtml.sh +2 -0
  233. package/examples/no-install-global.sh +2 -0
  234. package/examples/no-jwt-in-url.sh +2 -0
  235. package/examples/no-large-commit.sh +2 -0
  236. package/examples/no-localhost-expose.sh +2 -0
  237. package/examples/no-long-switch.sh +2 -0
  238. package/examples/no-magic-number.sh +2 -0
  239. package/examples/no-md5-sha1.sh +2 -0
  240. package/examples/no-memory-leak-interval.sh +2 -0
  241. package/examples/no-mixed-line-endings.sh +2 -0
  242. package/examples/no-mutation-in-reducer.sh +2 -0
  243. package/examples/no-mutation-observer-leak.sh +2 -0
  244. package/examples/no-nested-subscribe.sh +2 -0
  245. package/examples/no-nested-ternary.sh +2 -0
  246. package/examples/no-network-exfil.sh +2 -0
  247. package/examples/no-new-array-fill.sh +2 -0
  248. package/examples/no-object-freeze-mutation.sh +2 -0
  249. package/examples/no-open-redirect.sh +2 -0
  250. package/examples/no-output-truncation.sh +44 -0
  251. package/examples/no-package-downgrade.sh +2 -0
  252. package/examples/no-package-lock-edit.sh +2 -0
  253. package/examples/no-path-join-user-input.sh +2 -0
  254. package/examples/no-port-bind.sh +2 -0
  255. package/examples/no-process-exit.sh +2 -0
  256. package/examples/no-prototype-pollution.sh +2 -0
  257. package/examples/no-push-without-ci.sh +2 -0
  258. package/examples/no-raw-ref.sh +2 -0
  259. package/examples/no-redundant-fragment.sh +2 -0
  260. package/examples/no-render-in-loop.sh +2 -0
  261. package/examples/no-root-user-docker.sh +2 -0
  262. package/examples/no-root-write.sh +2 -0
  263. package/examples/no-secrets-in-args.sh +2 -0
  264. package/examples/no-secrets-in-logs.sh +2 -0
  265. package/examples/no-sensitive-log.sh +2 -0
  266. package/examples/no-side-effects-in-render.sh +2 -0
  267. package/examples/no-sleep-in-hooks.sh +2 -0
  268. package/examples/no-star-import-python.sh +2 -0
  269. package/examples/no-string-concat-sql.sh +2 -0
  270. package/examples/no-sudo-guard.sh +2 -0
  271. package/examples/no-sync-external-call.sh +2 -0
  272. package/examples/no-sync-fs.sh +2 -0
  273. package/examples/no-table-layout.sh +2 -0
  274. package/examples/no-throw-string.sh +2 -0
  275. package/examples/no-todo-in-merge.sh +2 -0
  276. package/examples/no-todo-in-production.sh +2 -0
  277. package/examples/no-todo-without-issue.sh +2 -0
  278. package/examples/no-triple-slash-ref.sh +2 -0
  279. package/examples/no-unreachable-code.sh +2 -0
  280. package/examples/no-unused-import.sh +2 -0
  281. package/examples/no-unused-state.sh +2 -0
  282. package/examples/no-var-keyword.sh +2 -0
  283. package/examples/no-wildcard-cors.sh +2 -0
  284. package/examples/no-wildcard-import.sh +2 -0
  285. package/examples/no-window-location.sh +2 -0
  286. package/examples/no-with-statement.sh +2 -0
  287. package/examples/no-write-outside-src.sh +2 -0
  288. package/examples/no-xml-external-entity.sh +2 -0
  289. package/examples/notify-waiting.sh +2 -0
  290. package/examples/npm-audit-warn.sh +2 -0
  291. package/examples/npm-publish-guard.sh +2 -0
  292. package/examples/npm-script-injection.sh +2 -0
  293. package/examples/npm-supply-chain-guard.sh +92 -0
  294. package/examples/nuxt-config-guard.sh +2 -0
  295. package/examples/output-secret-mask.sh +2 -0
  296. package/examples/package-json-guard.sh +2 -0
  297. package/examples/parallel-session-guard.sh +2 -0
  298. package/examples/path-traversal-guard.sh +2 -0
  299. package/examples/permission-audit-log.sh +2 -0
  300. package/examples/permission-entry-validator.sh +48 -0
  301. package/examples/php-lint-on-edit.sh +2 -0
  302. package/examples/pip-publish-guard.sh +2 -0
  303. package/examples/plain-language-danger-warn.sh +37 -0
  304. package/examples/plan-mode-enforcer.sh +2 -0
  305. package/examples/plugin-process-cleanup.sh +50 -0
  306. package/examples/polyglot-rm-guard.sh +59 -0
  307. package/examples/pr-description-check.sh +2 -0
  308. package/examples/pre-compact-knowledge-save.sh +53 -0
  309. package/examples/pre-compact-transcript-export.sh +85 -0
  310. package/examples/prefer-builtin-tools.sh +2 -0
  311. package/examples/prefer-const.sh +2 -0
  312. package/examples/prefer-dedicated-tools.sh +55 -0
  313. package/examples/prefer-optional-chaining.sh +2 -0
  314. package/examples/prisma-migrate-guard.sh +2 -0
  315. package/examples/prompt-injection-detector.sh +2 -0
  316. package/examples/prompt-length-guard.sh +2 -0
  317. package/examples/protect-dotfiles.sh +2 -0
  318. package/examples/public-repo-push-guard.sh +58 -0
  319. package/examples/push-requires-test-pass-record.sh +2 -0
  320. package/examples/push-requires-test-pass.sh +2 -0
  321. package/examples/rails-migration-guard.sh +2 -0
  322. package/examples/rate-limit-guard.sh +2 -0
  323. package/examples/read-all-files-enforcer.sh +51 -0
  324. package/examples/readme-exists-check.sh +2 -0
  325. package/examples/redis-flushall-guard.sh +2 -0
  326. package/examples/rm-safety-net.sh +2 -0
  327. package/examples/role-tool-guard.sh +69 -0
  328. package/examples/ruby-lint-on-edit.sh +2 -0
  329. package/examples/schema-migration-guard.sh +57 -0
  330. package/examples/scope-guard.sh +2 -0
  331. package/examples/secret-file-read-guard.sh +74 -0
  332. package/examples/self-modify-bypass-guard.sh +54 -0
  333. package/examples/sensitive-log-guard.sh +2 -0
  334. package/examples/session-checkpoint.sh +2 -0
  335. package/examples/session-end-logger.sh +57 -0
  336. package/examples/session-error-rate-monitor.sh +65 -0
  337. package/examples/session-health-monitor.sh +61 -0
  338. package/examples/session-memory-watchdog.sh +17 -0
  339. package/examples/session-permission-reset-guard.sh +39 -0
  340. package/examples/session-resume-env-fix.sh +49 -0
  341. package/examples/session-state-saver.sh +2 -0
  342. package/examples/session-summary-stop.sh +2 -0
  343. package/examples/session-summary.sh +2 -0
  344. package/examples/session-token-counter.sh +2 -0
  345. package/examples/shell-wrapper-guard.sh +2 -0
  346. package/examples/skill-gate.sh +2 -0
  347. package/examples/skill-injection-detector.sh +41 -0
  348. package/examples/spec-file-scope-guard.sh +69 -0
  349. package/examples/spring-profile-guard.sh +2 -0
  350. package/examples/sql-injection-detect.sh +2 -0
  351. package/examples/subagent-budget-guard.sh +2 -0
  352. package/examples/subagent-claudemd-inject.sh +45 -0
  353. package/examples/subagent-tool-call-limiter.sh +48 -0
  354. package/examples/svelte-lint-on-edit.sh +2 -0
  355. package/examples/swift-build-on-edit.sh +2 -0
  356. package/examples/system-message-workaround.sh +44 -0
  357. package/examples/system-package-guard.sh +2 -0
  358. package/examples/temp-file-cleanup.sh +2 -0
  359. package/examples/terminal-state-restore.sh +23 -0
  360. package/examples/test-after-edit.sh +2 -0
  361. package/examples/test-before-commit.sh +2 -0
  362. package/examples/test-before-push.sh +2 -0
  363. package/examples/test-exit-code-verify.sh +2 -0
  364. package/examples/timeout-guard.sh +2 -0
  365. package/examples/timezone-guard.sh +2 -0
  366. package/examples/tmp-output-size-guard.sh +46 -0
  367. package/examples/todo-check.sh +2 -0
  368. package/examples/todo-deadline-warn.sh +48 -0
  369. package/examples/token-budget-per-task.sh +55 -0
  370. package/examples/token-usage-tracker.sh +14 -0
  371. package/examples/turbo-cache-guard.sh +2 -0
  372. package/examples/uncommitted-changes-stop.sh +2 -0
  373. package/examples/uncommitted-work-shield.sh +37 -0
  374. package/examples/usage-warn.sh +2 -0
  375. package/examples/verify-before-commit.sh +2 -0
  376. package/examples/vue-lint-on-edit.sh +2 -0
  377. package/examples/webfetch-domain-allow.sh +96 -0
  378. package/examples/worktree-memory-guard.sh +47 -0
  379. package/examples/worktree-project-unify.sh +19 -0
  380. package/examples/worktree-unmerged-guard.sh +2 -0
  381. package/examples/write-overwrite-confirm.sh +40 -0
  382. package/examples/write-secret-guard.sh +2 -0
  383. package/examples/write-test-ratio.sh +2 -0
  384. package/package.json +2 -2
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+ # ci-workflow-guard.sh — Prevent dangerous CI/CD workflow modifications
3
+ #
4
+ # Solves: Claude modifying CI workflows to add `--no-verify`, skip tests,
5
+ # disable security scanning, or add overly broad permissions.
6
+ # A compromised workflow can exfiltrate secrets or deploy malicious code.
7
+ #
8
+ # How it works: PostToolUse hook on Edit/Write that checks workflow files
9
+ # for dangerous patterns after modification.
10
+ #
11
+ # TRIGGER: PostToolUse
12
+ # MATCHER: "Edit|Write"
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT=$(cat)
17
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
18
+ case "$TOOL" in Edit|Write) ;; *) exit 0 ;; esac
19
+
20
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
21
+ [ -z "$FILE" ] && exit 0
22
+
23
+ # Only check CI workflow files
24
+ case "$FILE" in
25
+ .github/workflows/*.yml|.github/workflows/*.yaml) ;;
26
+ .gitlab-ci.yml|.circleci/config.yml|Jenkinsfile) ;;
27
+ */.github/workflows/*.yml) ;;
28
+ *) exit 0 ;;
29
+ esac
30
+
31
+ [ -f "$FILE" ] || exit 0
32
+
33
+ WARNINGS=""
34
+
35
+ # Check for dangerous patterns
36
+ if grep -qE 'permissions:\s*write-all|permissions:\s*\{[^}]*contents:\s*write' "$FILE" 2>/dev/null; then
37
+ WARNINGS="${WARNINGS} - Broad write permissions detected\n"
38
+ fi
39
+
40
+ if grep -qE '--no-verify|--skip-tests|--no-check|SKIP_CI|skip ci|\[ci skip\]' "$FILE" 2>/dev/null; then
41
+ WARNINGS="${WARNINGS} - Test/verification skip detected\n"
42
+ fi
43
+
44
+ if grep -qE 'curl.*\|.*sh|wget.*\|.*bash|bash\s*<\(curl' "$FILE" 2>/dev/null; then
45
+ WARNINGS="${WARNINGS} - Remote script execution (curl|sh) detected\n"
46
+ fi
47
+
48
+ if grep -qE 'dangerously-skip-permissions|--force|--no-verify' "$FILE" 2>/dev/null; then
49
+ WARNINGS="${WARNINGS} - Safety bypass flags detected\n"
50
+ fi
51
+
52
+ if [ -n "$WARNINGS" ]; then
53
+ echo "WARNING: Potentially dangerous CI workflow changes:" >&2
54
+ echo " File: $FILE" >&2
55
+ echo -e "$WARNINGS" >&2
56
+ echo " Review these changes carefully before committing." >&2
57
+ fi
58
+
59
+ exit 0
@@ -19,6 +19,8 @@
19
19
  # }]
20
20
  # }
21
21
  # }
22
+ #
23
+ # TRIGGER: PermissionRequest MATCHER: ""
22
24
 
23
25
  INPUT=$(cat)
24
26
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -0,0 +1,15 @@
1
+ CLAUDE_DIR="$HOME/.claude"
2
+ MAX_AGE_DAYS="${CC_GC_MAX_AGE:-30}"
3
+ MAX_SIZE_MB="${CC_GC_MAX_SIZE:-500}"
4
+ DRY_RUN="${CC_GC_DRY_RUN:-0}"
5
+ find "$CLAUDE_DIR/projects" -name "*.jsonl" -mtime +"$MAX_AGE_DAYS" -type f 2>/dev/null | while read f; do
6
+ [ "$DRY_RUN" = "1" ] && echo " [dry-run] would delete: $f" >&2 && continue
7
+ rm "$f"
8
+ done
9
+ find "$CLAUDE_DIR/projects" -maxdepth 1 -type d -empty -delete 2>/dev/null
10
+ find "$CLAUDE_DIR" -path "*/tool-results/*" -mtime +"$MAX_AGE_DAYS" -type f -delete 2>/dev/null
11
+ TOTAL_MB=$(du -sm "$CLAUDE_DIR" 2>/dev/null | cut -f1)
12
+ if [ "$TOTAL_MB" -gt "$MAX_SIZE_MB" ] 2>/dev/null; then
13
+ echo "~/.claude is ${TOTAL_MB}MB (cap: ${MAX_SIZE_MB}MB)" >&2
14
+ fi
15
+ exit 0
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+ # claudeignore-enforce-guard.sh — Enforce .claudeignore at the tool level
3
+ #
4
+ # Solves: .claudeignore patterns not blocking Read/Edit/Grep tool calls (#16704).
5
+ # Claude can directly access files listed in .claudeignore via tool calls.
6
+ # This hook enforces ignore rules that the built-in system misses.
7
+ #
8
+ # How it works: PreToolUse hook on Read/Edit/Write/Grep that checks if the
9
+ # target file matches any pattern in .claudeignore. Uses glob-style matching.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Read|Edit|Write|Grep|Glob"
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT=$(cat)
17
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
18
+
19
+ # Extract file path based on tool type
20
+ case "$TOOL" in
21
+ Read|Edit|Write)
22
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
23
+ ;;
24
+ Grep|Glob)
25
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.path // empty' 2>/dev/null)
26
+ ;;
27
+ *) exit 0 ;;
28
+ esac
29
+
30
+ [ -z "$FILE" ] && exit 0
31
+
32
+ # Find .claudeignore in project root or current directory
33
+ IGNORE_FILE=""
34
+ for candidate in ".claudeignore" "../.claudeignore" "../../.claudeignore"; do
35
+ if [ -f "$candidate" ]; then
36
+ IGNORE_FILE="$candidate"
37
+ break
38
+ fi
39
+ done
40
+
41
+ [ -z "$IGNORE_FILE" ] && exit 0
42
+
43
+ # Check each pattern in .claudeignore
44
+ while IFS= read -r pattern || [ -n "$pattern" ]; do
45
+ # Skip empty lines and comments
46
+ [[ -z "$pattern" || "$pattern" == \#* ]] && continue
47
+ # Strip trailing whitespace
48
+ pattern=$(echo "$pattern" | sed 's/[[:space:]]*$//')
49
+ [ -z "$pattern" ] && continue
50
+
51
+ # Match: exact path, basename, or glob
52
+ BASENAME=$(basename "$FILE")
53
+ if [[ "$FILE" == $pattern ]] || [[ "$BASENAME" == $pattern ]] || [[ "$FILE" == */$pattern ]]; then
54
+ echo "BLOCKED: File '$FILE' matches .claudeignore pattern '$pattern'." >&2
55
+ echo "This file is excluded from Claude Code access." >&2
56
+ exit 2
57
+ fi
58
+ done < "$IGNORE_FILE"
59
+
60
+ exit 0
@@ -1,4 +1,6 @@
1
1
  #!/bin/bash
2
+ #
3
+ # TRIGGER: PostToolUse MATCHER: "Bash"
2
4
  INPUT=$(cat)
3
5
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
4
6
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -24,6 +24,8 @@
24
24
  #
25
25
  # The "if" field (v2.1.85+) skips this hook for non-commit commands.
26
26
  # Without "if", the hook still works — it checks internally and exits early.
27
+ #
28
+ # TRIGGER: PreToolUse MATCHER: "Bash"
27
29
 
28
30
  INPUT=$(cat)
29
31
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # compact-blocker.sh — Block auto-compaction entirely
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Power users who manage context manually (file-backed plans,
7
+ # checkpoint scripts) lose nuanced context when auto-compaction
8
+ # fires. This hook blocks compaction via exit code 2.
9
+ #
10
+ # For conditional blocking (e.g., only during plan mode), modify
11
+ # the guard condition below.
12
+ #
13
+ # TRIGGER: PreCompact
14
+ # MATCHER: (none — PreCompact has no matcher)
15
+ #
16
+ # DECISION: exit 2 = block compaction
17
+ #
18
+ # See: https://github.com/anthropics/claude-code/issues/6689
19
+ # ================================================================
20
+
21
+ # Unconditional block — compaction never fires
22
+ # To make it conditional, add logic here:
23
+ # e.g., [ -f /tmp/allow-compact ] && exit 0
24
+ echo '{"decision":"block","reason":"auto-compaction disabled by compact-blocker hook"}' >&2
25
+ exit 2
@@ -17,6 +17,8 @@
17
17
  # }
18
18
  # }
19
19
  # ================================================================
20
+ #
21
+ # TRIGGER: PreToolUse MATCHER: "Bash"
20
22
 
21
23
  INPUT=$(cat)
22
24
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -23,6 +23,8 @@
23
23
  # }]
24
24
  # }
25
25
  # }
26
+ #
27
+ # TRIGGER: PermissionRequest MATCHER: ""
26
28
 
27
29
  INPUT=$(cat)
28
30
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ # consecutive-failure-circuit-breaker.sh — Stop after repeated failures
3
+ #
4
+ # Solves: Claude escalating to destructive actions after repeated failures (#31946).
5
+ # Without this, Claude retries failing commands dozens of times,
6
+ # eventually trying increasingly dangerous alternatives.
7
+ #
8
+ # How it works: PostToolUse hook on Bash that tracks consecutive non-zero
9
+ # exit codes. After CC_MAX_CONSECUTIVE_FAILURES (default 3), blocks
10
+ # further Bash calls until a success resets the counter.
11
+ #
12
+ # TRIGGER: PostToolUse
13
+ # MATCHER: "Bash"
14
+
15
+ set -euo pipefail
16
+
17
+ INPUT=$(cat)
18
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
19
+ [ "$TOOL" != "Bash" ] && exit 0
20
+
21
+ EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_result.exitCode // "0"' 2>/dev/null)
22
+ MAX_FAILURES="${CC_MAX_CONSECUTIVE_FAILURES:-3}"
23
+ COUNTER_FILE="/tmp/claude-consecutive-failures-${PPID:-0}"
24
+
25
+ if [ "$EXIT_CODE" = "0" ]; then
26
+ # Success — reset counter
27
+ echo "0" > "$COUNTER_FILE"
28
+ exit 0
29
+ fi
30
+
31
+ # Failure — increment counter
32
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
33
+ COUNT=$((COUNT + 1))
34
+ echo "$COUNT" > "$COUNTER_FILE"
35
+
36
+ if [ "$COUNT" -ge "$MAX_FAILURES" ]; then
37
+ echo "CIRCUIT BREAKER: $COUNT consecutive Bash failures detected." >&2
38
+ echo "" >&2
39
+ echo "Stop and reassess your approach. Repeated failures often lead to" >&2
40
+ echo "increasingly risky workarounds. Consider:" >&2
41
+ echo " 1. Read the error messages carefully" >&2
42
+ echo " 2. Check your assumptions" >&2
43
+ echo " 3. Try a completely different approach" >&2
44
+ echo " 4. Ask the user for help" >&2
45
+ # Don't block (exit 0) — just warn strongly via stderr
46
+ # PostToolUse can't block, but the warning enters context
47
+ fi
48
+
49
+ exit 0
@@ -1,3 +1,5 @@
1
+ #
2
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
1
3
  INPUT=$(cat)
2
4
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
3
5
  [[ -z "$FILE" ]] && exit 0
@@ -0,0 +1,16 @@
1
+ INPUT=$(cat)
2
+ COUNTER="/tmp/cc-tool-count-$$"
3
+ COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0)
4
+ COUNT=$((COUNT + 1))
5
+ echo "$COUNT" > "$COUNTER"
6
+ THRESHOLD="${CC_COMPACT_THRESHOLD:-50}"
7
+ if [ "$((COUNT % THRESHOLD))" -eq 0 ]; then
8
+ TRANSCRIPT=$(ls -t ~/.claude/projects/*/sessions/*/transcript.jsonl 2>/dev/null | head -1)
9
+ if [ -f "$TRANSCRIPT" ]; then
10
+ SIZE_KB=$(($(wc -c < "$TRANSCRIPT") / 1024))
11
+ if [ "$SIZE_KB" -gt 200 ]; then
12
+ echo "Context ~${SIZE_KB}KB ($COUNT calls). Consider /compact." >&2
13
+ fi
14
+ fi
15
+ fi
16
+ exit 0
@@ -1,4 +1,6 @@
1
1
  #!/bin/bash
2
+ #
3
+ # TRIGGER: PreToolUse MATCHER: "Bash"
2
4
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
3
5
  [ -z "$COMMAND" ] && exit 0
4
6
  # PostToolUse matcher: Edit|Write
@@ -21,6 +21,8 @@
21
21
  # }]
22
22
  # }
23
23
  # }
24
+ #
25
+ # TRIGGER: PreToolUse MATCHER: "Bash"
24
26
 
25
27
  INPUT=$(cat)
26
28
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -18,6 +18,8 @@
18
18
  # }]
19
19
  # }
20
20
  # }
21
+ #
22
+ # TRIGGER: PreToolUse MATCHER: "Bash"
21
23
 
22
24
  INPUT=$(cat)
23
25
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # cron-modification-guard.sh — Block unreviewed cron job modifications
3
+ #
4
+ # Solves: Claude creating cron jobs that sabotage live services (#40421).
5
+ # A cron job that polls 200 content IDs against a single-connection
6
+ # proxy caused stream resets every 10 minutes for days.
7
+ #
8
+ # Why this matters:
9
+ # Cron jobs are persistent, invisible, and run unattended. A bad cron
10
+ # can cause sustained damage long after the session ends. This hook
11
+ # blocks crontab edits, /etc/cron.d writes, and systemd timer creation.
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Bash"
15
+
16
+ INPUT=$(cat)
17
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
18
+ [ -z "$COMMAND" ] && exit 0
19
+
20
+ # Detect cron/timer modifications
21
+ if echo "$COMMAND" | grep -qEi '(crontab\s+-[elr]|crontab\s+[^-]|systemctl\s+(enable|start|restart)\s+.*\.timer|(^|\s|;|&&|\|)at\s+)'; then
22
+ echo "BLOCKED: Cron/timer modification requires manual review." >&2
23
+ echo "" >&2
24
+ echo "Command: $COMMAND" >&2
25
+ echo "" >&2
26
+ echo "Cron jobs are persistent and run unattended. Before creating one:" >&2
27
+ echo " 1. Will this interfere with live services?" >&2
28
+ echo " 2. Does it access shared resources (DB, API, proxy)?" >&2
29
+ echo " 3. What happens if it fails silently?" >&2
30
+ exit 2
31
+ fi
32
+
33
+ # Also catch writing to cron directories
34
+ if echo "$COMMAND" | grep -qE '>\s*/etc/cron\.|tee\s+/etc/cron\.'; then
35
+ echo "BLOCKED: Direct write to cron directory." >&2
36
+ echo "Command: $COMMAND" >&2
37
+ exit 2
38
+ fi
39
+
40
+ exit 0
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+ # cwd-project-boundary-guard.sh — Warn when cd leaves the project directory
3
+ #
4
+ # Solves: Claude navigating to system directories or other projects
5
+ # and making changes outside the intended scope. Especially
6
+ # dangerous with auto-approve, where writes to /etc or
7
+ # other projects go unchallenged.
8
+ #
9
+ # How it works: CwdChanged hook that tracks the project root and warns
10
+ # when the working directory moves outside it.
11
+ #
12
+ # CONFIG:
13
+ # CC_PROJECT_ROOT (auto-detected if not set)
14
+ #
15
+ # TRIGGER: CwdChanged
16
+ # MATCHER: "" (CwdChanged has no matcher support)
17
+
18
+ set -euo pipefail
19
+
20
+ INPUT=$(cat)
21
+ NEW_CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
22
+ [ -z "$NEW_CWD" ] && exit 0
23
+
24
+ # Determine project root
25
+ PROJECT_ROOT="${CC_PROJECT_ROOT:-}"
26
+ if [ -z "$PROJECT_ROOT" ]; then
27
+ # Auto-detect: find nearest .git directory
28
+ PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
29
+ fi
30
+ [ -z "$PROJECT_ROOT" ] && exit 0
31
+
32
+ # Normalize paths
33
+ PROJECT_ROOT=$(realpath "$PROJECT_ROOT" 2>/dev/null || echo "$PROJECT_ROOT")
34
+ NEW_CWD=$(realpath "$NEW_CWD" 2>/dev/null || echo "$NEW_CWD")
35
+
36
+ # Check if new cwd is inside project
37
+ case "$NEW_CWD" in
38
+ "$PROJECT_ROOT"|"$PROJECT_ROOT"/*)
39
+ # Inside project — OK
40
+ exit 0
41
+ ;;
42
+ *)
43
+ echo "WARNING: Working directory left project boundary." >&2
44
+ echo " Project: $PROJECT_ROOT" >&2
45
+ echo " New cwd: $NEW_CWD" >&2
46
+ echo " Changes outside the project may affect other systems." >&2
47
+ ;;
48
+ esac
49
+
50
+ exit 0
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+ # denied-action-retry-guard.sh — Block re-attempts of denied tool calls
3
+ #
4
+ # Solves: Model retries the same operation after user explicitly denied it (#40156).
5
+ # Claude asks "shall I git push?", user says no, Claude tries git push anyway.
6
+ #
7
+ # How it works: PreToolUse hook that tracks denied operations via a state file.
8
+ # When a tool call is blocked (exit 2 from any hook), the command signature
9
+ # is recorded. If the same signature appears again, it's auto-blocked.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Bash|Edit|Write"
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT=$(cat)
17
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
18
+ COMMAND=""
19
+
20
+ case "$TOOL" in
21
+ Bash) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null) ;;
22
+ Edit|Write) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null) ;;
23
+ *) exit 0 ;;
24
+ esac
25
+
26
+ [ -z "$COMMAND" ] && exit 0
27
+
28
+ DENY_FILE="/tmp/claude-denied-actions-${PPID:-0}"
29
+
30
+ # Check if this action was previously denied
31
+ if [ -f "$DENY_FILE" ]; then
32
+ # Normalize: take first 80 chars as signature
33
+ SIG=$(echo "$COMMAND" | head -c 80 | md5sum | cut -d' ' -f1)
34
+ if grep -q "$SIG" "$DENY_FILE" 2>/dev/null; then
35
+ echo "BLOCKED: This action was previously denied in this session." >&2
36
+ echo "Do not retry denied actions. Try a different approach." >&2
37
+ exit 2
38
+ fi
39
+ fi
40
+
41
+ exit 0
@@ -16,6 +16,8 @@
16
16
  # - Packages in ALLOWLIST (customize below)
17
17
  #
18
18
  # Usage: Add to settings.json as a PreToolUse hook on "Bash"
19
+ #
20
+ # TRIGGER: PreToolUse MATCHER: "Bash"
19
21
 
20
22
  INPUT=$(cat)
21
23
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -17,6 +17,8 @@
17
17
  # }]
18
18
  # }
19
19
  # }
20
+ #
21
+ # TRIGGER: PreToolUse MATCHER: "Bash"
20
22
 
21
23
  INPUT=$(cat)
22
24
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ # deploy-path-verify-guard.sh — Verify deployment target path before writes
3
+ #
4
+ # Solves: Writing files to wrong filesystem path in Docker setups (#40421).
5
+ # Claude wrote to host /srv/ instead of Docker mount /opt/acestream/public/
6
+ # three times, each time claiming deployment was successful.
7
+ #
8
+ # How it works: Before writing to paths matching CC_DEPLOY_PATHS pattern,
9
+ # verifies the target is actually mounted/accessible. Catches host-vs-container
10
+ # path confusion.
11
+ #
12
+ # CONFIG:
13
+ # CC_DEPLOY_PATHS="/srv:/opt/acestream/public:/var/www"
14
+ # CC_DEPLOY_VERIFY_CMD="docker inspect --format '{{.Mounts}}' mycontainer"
15
+ #
16
+ # TRIGGER: PreToolUse
17
+ # MATCHER: "Bash|Write"
18
+
19
+ set -euo pipefail
20
+
21
+ INPUT=$(cat)
22
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
23
+
24
+ DEPLOY_PATHS="${CC_DEPLOY_PATHS:-/srv:/var/www:/opt}"
25
+
26
+ # For Write tool, check file_path
27
+ if [ "$TOOL" = "Write" ]; then
28
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
29
+ [ -z "$FILE" ] && exit 0
30
+
31
+ IFS=':' read -ra PATHS <<< "$DEPLOY_PATHS"
32
+ for dp in "${PATHS[@]}"; do
33
+ if [[ "$FILE" == "$dp"* ]]; then
34
+ echo "WARNING: Writing to deployment path $dp." >&2
35
+ echo "File: $FILE" >&2
36
+ echo "" >&2
37
+ echo "Verify this is the correct target:" >&2
38
+ echo " - Is this inside a Docker container or on the host?" >&2
39
+ echo " - Run 'docker inspect' to check bind mounts first." >&2
40
+ # Warning only (exit 0), not blocking — change to exit 2 to block
41
+ exit 0
42
+ fi
43
+ done
44
+ fi
45
+
46
+ # For Bash tool, check commands that write to deploy paths
47
+ if [ "$TOOL" = "Bash" ]; then
48
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
49
+ [ -z "$COMMAND" ] && exit 0
50
+
51
+ IFS=':' read -ra PATHS <<< "$DEPLOY_PATHS"
52
+ for dp in "${PATHS[@]}"; do
53
+ if echo "$COMMAND" | grep -qE "(cp|mv|tee|cat.*>|echo.*>).*${dp}"; then
54
+ echo "WARNING: Bash command targets deployment path $dp." >&2
55
+ echo "Command: $COMMAND" >&2
56
+ echo "Verify the target path is correct (host vs container)." >&2
57
+ exit 0
58
+ fi
59
+ done
60
+ fi
61
+
62
+ exit 0
@@ -17,6 +17,8 @@
17
17
  # }
18
18
  # }
19
19
  # ================================================================
20
+ #
21
+ # TRIGGER: PreToolUse MATCHER: "Bash"
20
22
 
21
23
  INPUT=$(cat)
22
24
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -1,4 +1,6 @@
1
1
  #!/bin/bash
2
+ #
3
+ # TRIGGER: PreToolUse MATCHER: "Bash"
2
4
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
3
5
  [ -z "$COMMAND" ] && exit 0
4
6
  if echo "$COMMAND" | grep -qE '\bdocker\s+volume\s+(rm|prune)\b'; then
@@ -1,3 +1,5 @@
1
+ #
2
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
1
3
  INPUT=$(cat)
2
4
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
3
5
  [[ -z "$FILE" ]] && exit 0
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # dotenv-commit-guard.sh — Prevent committing .env files with secrets
3
+ #
4
+ # Solves: Claude adding .env files to git staging and committing them.
5
+ # Even with .gitignore, Claude can `git add -f .env` to force-add.
6
+ #
7
+ # How it works: PreToolUse hook on Bash that detects git add/commit
8
+ # commands including .env files and blocks them.
9
+ #
10
+ # TRIGGER: PreToolUse
11
+ # MATCHER: "Bash"
12
+
13
+ set -euo pipefail
14
+
15
+ INPUT=$(cat)
16
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
17
+ [ -z "$COMMAND" ] && exit 0
18
+
19
+ # Check for git add with .env files
20
+ # Allow .env.example, .env.sample, .env.template
21
+ if echo "$COMMAND" | grep -qE 'git\s+add\s+.*\.env\.(example|sample|template)'; then
22
+ exit 0
23
+ fi
24
+ if echo "$COMMAND" | grep -qE 'git\s+add\s+.*\.env'; then
25
+ echo "BLOCKED: Adding .env file to git staging." >&2
26
+ echo " .env files contain secrets and should not be committed." >&2
27
+ echo " Use .env.example with placeholder values instead." >&2
28
+ exit 2
29
+ fi
30
+
31
+ # Check for git add -A/-f that might include .env
32
+ if echo "$COMMAND" | grep -qE 'git\s+add\s+(-A|--all|-f|--force)\b'; then
33
+ # Check if .env exists in the working directory
34
+ if [ -f ".env" ] || [ -f ".env.local" ] || [ -f ".env.production" ]; then
35
+ # Check if .gitignore excludes it
36
+ if ! git check-ignore -q .env 2>/dev/null; then
37
+ echo "WARNING: 'git add -A' with .env file not in .gitignore." >&2
38
+ echo " Add .env to .gitignore before staging all files." >&2
39
+ # Warning only for git add -A
40
+ fi
41
+ fi
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # dotenv-example-sync.sh — Warn when .env changes but .env.example doesn't
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # When Claude edits .env (adding/removing variables), .env.example
7
+ # should be updated to match. This hook warns if .env was modified
8
+ # but .env.example still has different keys, preventing deployment
9
+ # failures when team members don't have the new variables.
10
+ #
11
+ # TRIGGER: PostToolUse
12
+ # MATCHER: "Edit|Write"
13
+ #
14
+ # DECISION: Advisory only (exit 0). Warns via stderr.
15
+ # ================================================================
16
+
17
+ INPUT=$(cat)
18
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
19
+ [ -z "$FILE" ] && exit 0
20
+
21
+ # Only trigger when .env is edited (not .env.example itself)
22
+ case "$(basename "$FILE")" in
23
+ .env|.env.local|.env.development|.env.production) ;;
24
+ *) exit 0 ;;
25
+ esac
26
+
27
+ # Find corresponding .env.example
28
+ DIR=$(dirname "$FILE")
29
+ EXAMPLE=""
30
+ for candidate in "$DIR/.env.example" "$DIR/.env.sample" "$DIR/.env.template"; do
31
+ if [ -f "$candidate" ]; then
32
+ EXAMPLE="$candidate"
33
+ break
34
+ fi
35
+ done
36
+
37
+ [ -z "$EXAMPLE" ] && exit 0
38
+ [ ! -f "$FILE" ] && exit 0
39
+
40
+ # Extract variable names (lines with KEY=)
41
+ ENV_KEYS=$(grep -oE '^[A-Z_][A-Z0-9_]*=' "$FILE" 2>/dev/null | sort -u)
42
+ EXAMPLE_KEYS=$(grep -oE '^[A-Z_][A-Z0-9_]*=' "$EXAMPLE" 2>/dev/null | sort -u)
43
+
44
+ # Find keys in .env but not in .env.example
45
+ MISSING=$(comm -23 <(echo "$ENV_KEYS") <(echo "$EXAMPLE_KEYS"))
46
+
47
+ if [ -n "$MISSING" ]; then
48
+ echo "⚠ .env has variables not in $(basename "$EXAMPLE"):" >&2
49
+ echo "$MISSING" | sed 's/=$//' | while read key; do
50
+ echo " + $key" >&2
51
+ done
52
+ echo " Update $(basename "$EXAMPLE") so teammates have the full variable list." >&2
53
+ fi
54
+
55
+ exit 0
@@ -17,6 +17,8 @@
17
17
  # }
18
18
  # }
19
19
  # ================================================================
20
+ #
21
+ # TRIGGER: PreToolUse MATCHER: "Bash"
20
22
 
21
23
  INPUT=$(cat)
22
24
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)