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.
- package/COOKBOOK.md +70 -0
- package/README.md +6 -2
- package/examples/absolute-rule-enforcer.sh +42 -0
- package/examples/allow-claude-settings.sh +2 -0
- package/examples/allow-git-hooks-dir.sh +2 -0
- package/examples/allow-protected-dirs.sh +2 -0
- package/examples/allowlist.sh +2 -0
- package/examples/ansible-vault-guard.sh +2 -0
- package/examples/auto-approve-build.sh +2 -0
- package/examples/auto-approve-compound-git.sh +2 -0
- package/examples/auto-approve-docker.sh +2 -0
- package/examples/auto-approve-git-read.sh +2 -0
- package/examples/auto-approve-python.sh +2 -0
- package/examples/auto-approve-readonly-tools.sh +2 -0
- package/examples/auto-approve-ssh.sh +2 -0
- package/examples/auto-approve-test.sh +2 -0
- package/examples/auto-checkpoint.sh +2 -0
- package/examples/auto-git-checkpoint.sh +2 -0
- package/examples/auto-mode-safe-commands.sh +2 -0
- package/examples/auto-snapshot.sh +2 -0
- package/examples/backup-before-refactor.sh +2 -0
- package/examples/banned-command-guard.sh +3 -3
- package/examples/bash-domain-allowlist.sh +72 -0
- package/examples/bash-safety-auto-deny.sh +56 -0
- package/examples/bash-secret-output-detector.sh +68 -0
- package/examples/bash-timeout-guard.sh +2 -0
- package/examples/bashrc-safety-check.sh +59 -0
- package/examples/bg-task-cooldown-guard.sh +46 -0
- package/examples/block-database-wipe.sh +2 -0
- package/examples/branch-name-check.sh +2 -0
- package/examples/branch-naming-convention.sh +2 -0
- package/examples/cargo-publish-guard.sh +2 -0
- package/examples/check-abort-controller.sh +2 -0
- package/examples/check-accessibility.sh +2 -0
- package/examples/check-aria-labels.sh +2 -0
- package/examples/check-async-await-consistency.sh +2 -0
- package/examples/check-before-act-enforcer.sh +47 -0
- package/examples/check-charset-meta.sh +2 -0
- package/examples/check-cleanup-effect.sh +2 -0
- package/examples/check-content-type.sh +2 -0
- package/examples/check-controlled-input.sh +2 -0
- package/examples/check-cookie-flags.sh +2 -0
- package/examples/check-cors-config.sh +2 -0
- package/examples/check-csp-headers.sh +2 -0
- package/examples/check-csrf-protection.sh +2 -0
- package/examples/check-debounce.sh +2 -0
- package/examples/check-dependency-age.sh +2 -0
- package/examples/check-dependency-license.sh +2 -0
- package/examples/check-dockerfile-best-practice.sh +2 -0
- package/examples/check-error-boundaries.sh +2 -0
- package/examples/check-error-class.sh +2 -0
- package/examples/check-error-handling.sh +2 -0
- package/examples/check-error-logging.sh +2 -0
- package/examples/check-error-message.sh +2 -0
- package/examples/check-error-page.sh +2 -0
- package/examples/check-error-stack.sh +2 -0
- package/examples/check-favicon.sh +2 -0
- package/examples/check-form-validation.sh +2 -0
- package/examples/check-git-hooks-compat.sh +2 -0
- package/examples/check-https-redirect.sh +2 -0
- package/examples/check-image-optimization.sh +2 -0
- package/examples/check-input-validation.sh +2 -0
- package/examples/check-key-prop.sh +2 -0
- package/examples/check-lang-attribute.sh +2 -0
- package/examples/check-lazy-loading.sh +2 -0
- package/examples/check-loading-state.sh +2 -0
- package/examples/check-memo-deps.sh +2 -0
- package/examples/check-meta-description.sh +2 -0
- package/examples/check-npm-scripts-exist.sh +2 -0
- package/examples/check-null-check.sh +2 -0
- package/examples/check-package-size.sh +2 -0
- package/examples/check-pagination.sh +2 -0
- package/examples/check-port-availability.sh +2 -0
- package/examples/check-promise-all.sh +2 -0
- package/examples/check-prop-types.sh +2 -0
- package/examples/check-rate-limiting.sh +2 -0
- package/examples/check-responsive-design.sh +2 -0
- package/examples/check-retry-logic.sh +2 -0
- package/examples/check-return-types.sh +2 -0
- package/examples/check-semantic-html.sh +2 -0
- package/examples/check-semantic-versioning.sh +2 -0
- package/examples/check-suspense-fallback.sh +2 -0
- package/examples/check-test-naming.sh +2 -0
- package/examples/check-timeout-cleanup.sh +2 -0
- package/examples/check-tls-version.sh +2 -0
- package/examples/check-type-coercion.sh +2 -0
- package/examples/check-unsubscribe.sh +2 -0
- package/examples/check-viewport-meta.sh +2 -0
- package/examples/check-worker-terminate.sh +2 -0
- package/examples/checkpoint-tamper-guard.sh +2 -0
- package/examples/chmod-guard.sh +2 -0
- package/examples/chown-guard.sh +2 -0
- package/examples/ci-workflow-guard.sh +59 -0
- package/examples/classifier-fallback-allow.sh +2 -0
- package/examples/claude-cache-gc.sh +15 -0
- package/examples/claudeignore-enforce-guard.sh +60 -0
- package/examples/claudemd-enforcer.sh +2 -0
- package/examples/commit-message-check.sh +2 -0
- package/examples/compact-blocker.sh +25 -0
- package/examples/composer-guard.sh +2 -0
- package/examples/compound-command-allow.sh +2 -0
- package/examples/consecutive-failure-circuit-breaker.sh +49 -0
- package/examples/console-log-count.sh +2 -0
- package/examples/context-compact-advisor.sh +16 -0
- package/examples/cors-star-warn.sh +2 -0
- package/examples/credential-exfil-guard.sh +2 -0
- package/examples/credential-file-cat-guard.sh +2 -0
- package/examples/cron-modification-guard.sh +40 -0
- package/examples/cwd-project-boundary-guard.sh +50 -0
- package/examples/denied-action-retry-guard.sh +41 -0
- package/examples/dependency-install-guard.sh +2 -0
- package/examples/deploy-guard.sh +2 -0
- package/examples/deploy-path-verify-guard.sh +62 -0
- package/examples/django-migrate-guard.sh +2 -0
- package/examples/docker-volume-guard.sh +2 -0
- package/examples/dockerfile-latest-guard.sh +2 -0
- package/examples/dotenv-commit-guard.sh +44 -0
- package/examples/dotenv-example-sync.sh +55 -0
- package/examples/dotnet-build-on-edit.sh +2 -0
- package/examples/drizzle-migrate-guard.sh +2 -0
- package/examples/edit-counter-test-gate.sh +44 -0
- package/examples/edit-error-counter.sh +2 -0
- package/examples/edit-guard.sh +2 -0
- package/examples/edit-retry-loop-guard.sh +2 -0
- package/examples/edit-verify.sh +2 -0
- package/examples/enforce-tests.sh +2 -0
- package/examples/env-inherit-guard.sh +2 -0
- package/examples/env-inline-secret-guard.sh +36 -0
- package/examples/env-prod-guard.sh +2 -0
- package/examples/env-required-check.sh +2 -0
- package/examples/env-var-check.sh +2 -0
- package/examples/expo-eject-guard.sh +2 -0
- package/examples/export-overwrite-guard.sh +29 -0
- package/examples/file-change-tracker.sh +2 -0
- package/examples/file-change-undo-tracker.sh +46 -0
- package/examples/file-recycle-bin.sh +48 -0
- package/examples/file-size-limit.sh +2 -0
- package/examples/five-hundred-milestone.sh +2 -0
- package/examples/flask-debug-guard.sh +2 -0
- package/examples/gem-push-guard.sh +2 -0
- package/examples/git-checkout-safety-guard.sh +2 -0
- package/examples/git-config-guard.sh +2 -0
- package/examples/git-hook-bypass-guard.sh +2 -0
- package/examples/git-merge-conflict-prevent.sh +2 -0
- package/examples/git-message-length.sh +2 -0
- package/examples/git-show-flag-sanitizer.sh +41 -0
- package/examples/git-stash-before-danger.sh +2 -0
- package/examples/git-submodule-guard.sh +2 -0
- package/examples/git-tag-guard.sh +2 -0
- package/examples/github-actions-secret-guard.sh +59 -0
- package/examples/gitignore-check.sh +2 -0
- package/examples/gitops-drift-guard.sh +53 -0
- package/examples/go-mod-tidy-warn.sh +2 -0
- package/examples/hallucination-url-check.sh +2 -0
- package/examples/hardcoded-ip-guard.sh +2 -0
- package/examples/headless-empty-result-guard.sh +46 -0
- package/examples/headless-stop-guard.sh +43 -0
- package/examples/helm-install-guard.sh +2 -0
- package/examples/issue-draft-redact-guard.sh +45 -0
- package/examples/java-compile-on-edit.sh +2 -0
- package/examples/k8s-production-guard.sh +77 -0
- package/examples/laravel-artisan-guard.sh +2 -0
- package/examples/large-file-guard.sh +2 -0
- package/examples/log-level-guard.sh +2 -0
- package/examples/magic-number-warn.sh +2 -0
- package/examples/max-edit-size-guard.sh +2 -0
- package/examples/max-file-count-guard.sh +2 -0
- package/examples/max-file-delete-count.sh +2 -0
- package/examples/max-function-length.sh +2 -0
- package/examples/max-import-count.sh +2 -0
- package/examples/max-subagent-count.sh +2 -0
- package/examples/mcp-orphan-process-guard.sh +39 -0
- package/examples/mcp-server-allowlist.sh +45 -0
- package/examples/mcp-tool-audit-log.sh +41 -0
- package/examples/mcp-tool-guard.sh +2 -0
- package/examples/migration-verify-guard.sh +44 -0
- package/examples/monorepo-scope-guard.sh +2 -0
- package/examples/network-exfil-guard.sh +61 -0
- package/examples/network-guard.sh +2 -0
- package/examples/nextjs-env-guard.sh +2 -0
- package/examples/no-absolute-import.sh +2 -0
- package/examples/no-alert-confirm-prompt.sh +2 -0
- package/examples/no-any-type.sh +2 -0
- package/examples/no-any-typescript.sh +2 -0
- package/examples/no-assignment-in-condition.sh +2 -0
- package/examples/no-callback-hell.sh +2 -0
- package/examples/no-catch-all-route.sh +2 -0
- package/examples/no-circular-dependency.sh +2 -0
- package/examples/no-class-in-functional.sh +2 -0
- package/examples/no-cleartext-storage.sh +2 -0
- package/examples/no-commented-code.sh +2 -0
- package/examples/no-commit-fixup.sh +2 -0
- package/examples/no-console-assert.sh +2 -0
- package/examples/no-console-error-swallow.sh +2 -0
- package/examples/no-console-in-prod.sh +2 -0
- package/examples/no-console-log.sh +2 -0
- package/examples/no-console-time.sh +2 -0
- package/examples/no-cors-wildcard.sh +2 -0
- package/examples/no-curl-upload.sh +2 -0
- package/examples/no-dangerouslySetInnerHTML.sh +2 -0
- package/examples/no-dangling-await.sh +2 -0
- package/examples/no-debug-in-commit.sh +2 -0
- package/examples/no-deep-nesting.sh +2 -0
- package/examples/no-deep-relative-import.sh +2 -0
- package/examples/no-default-credentials.sh +2 -0
- package/examples/no-deprecated-api.sh +2 -0
- package/examples/no-direct-dom-manipulation.sh +2 -0
- package/examples/no-disabled-test.sh +2 -0
- package/examples/no-document-cookie.sh +2 -0
- package/examples/no-document-write.sh +2 -0
- package/examples/no-empty-function.sh +2 -0
- package/examples/no-eval-in-template.sh +2 -0
- package/examples/no-eval-template.sh +2 -0
- package/examples/no-eval.sh +2 -0
- package/examples/no-exec-user-input.sh +2 -0
- package/examples/no-expose-internal-ids.sh +2 -0
- package/examples/no-floating-promises.sh +2 -0
- package/examples/no-force-install.sh +2 -0
- package/examples/no-git-rebase-public.sh +2 -0
- package/examples/no-global-state.sh +2 -0
- package/examples/no-hardcoded-port.sh +2 -0
- package/examples/no-hardcoded-url.sh +2 -0
- package/examples/no-helmet-missing.sh +2 -0
- package/examples/no-http-url.sh +2 -0
- package/examples/no-http-without-https.sh +2 -0
- package/examples/no-index-as-key.sh +2 -0
- package/examples/no-infinite-scroll-mem.sh +2 -0
- package/examples/no-inline-event-handler.sh +2 -0
- package/examples/no-inline-handler.sh +2 -0
- package/examples/no-inline-style.sh +2 -0
- package/examples/no-inline-styles.sh +2 -0
- package/examples/no-innerhtml.sh +2 -0
- package/examples/no-install-global.sh +2 -0
- package/examples/no-jwt-in-url.sh +2 -0
- package/examples/no-large-commit.sh +2 -0
- package/examples/no-localhost-expose.sh +2 -0
- package/examples/no-long-switch.sh +2 -0
- package/examples/no-magic-number.sh +2 -0
- package/examples/no-md5-sha1.sh +2 -0
- package/examples/no-memory-leak-interval.sh +2 -0
- package/examples/no-mixed-line-endings.sh +2 -0
- package/examples/no-mutation-in-reducer.sh +2 -0
- package/examples/no-mutation-observer-leak.sh +2 -0
- package/examples/no-nested-subscribe.sh +2 -0
- package/examples/no-nested-ternary.sh +2 -0
- package/examples/no-network-exfil.sh +2 -0
- package/examples/no-new-array-fill.sh +2 -0
- package/examples/no-object-freeze-mutation.sh +2 -0
- package/examples/no-open-redirect.sh +2 -0
- package/examples/no-output-truncation.sh +44 -0
- package/examples/no-package-downgrade.sh +2 -0
- package/examples/no-package-lock-edit.sh +2 -0
- package/examples/no-path-join-user-input.sh +2 -0
- package/examples/no-port-bind.sh +2 -0
- package/examples/no-process-exit.sh +2 -0
- package/examples/no-prototype-pollution.sh +2 -0
- package/examples/no-push-without-ci.sh +2 -0
- package/examples/no-raw-ref.sh +2 -0
- package/examples/no-redundant-fragment.sh +2 -0
- package/examples/no-render-in-loop.sh +2 -0
- package/examples/no-root-user-docker.sh +2 -0
- package/examples/no-root-write.sh +2 -0
- package/examples/no-secrets-in-args.sh +2 -0
- package/examples/no-secrets-in-logs.sh +2 -0
- package/examples/no-sensitive-log.sh +2 -0
- package/examples/no-side-effects-in-render.sh +2 -0
- package/examples/no-sleep-in-hooks.sh +2 -0
- package/examples/no-star-import-python.sh +2 -0
- package/examples/no-string-concat-sql.sh +2 -0
- package/examples/no-sudo-guard.sh +2 -0
- package/examples/no-sync-external-call.sh +2 -0
- package/examples/no-sync-fs.sh +2 -0
- package/examples/no-table-layout.sh +2 -0
- package/examples/no-throw-string.sh +2 -0
- package/examples/no-todo-in-merge.sh +2 -0
- package/examples/no-todo-in-production.sh +2 -0
- package/examples/no-todo-without-issue.sh +2 -0
- package/examples/no-triple-slash-ref.sh +2 -0
- package/examples/no-unreachable-code.sh +2 -0
- package/examples/no-unused-import.sh +2 -0
- package/examples/no-unused-state.sh +2 -0
- package/examples/no-var-keyword.sh +2 -0
- package/examples/no-wildcard-cors.sh +2 -0
- package/examples/no-wildcard-import.sh +2 -0
- package/examples/no-window-location.sh +2 -0
- package/examples/no-with-statement.sh +2 -0
- package/examples/no-write-outside-src.sh +2 -0
- package/examples/no-xml-external-entity.sh +2 -0
- package/examples/notify-waiting.sh +2 -0
- package/examples/npm-audit-warn.sh +2 -0
- package/examples/npm-publish-guard.sh +2 -0
- package/examples/npm-script-injection.sh +2 -0
- package/examples/npm-supply-chain-guard.sh +92 -0
- package/examples/nuxt-config-guard.sh +2 -0
- package/examples/output-secret-mask.sh +2 -0
- package/examples/package-json-guard.sh +2 -0
- package/examples/parallel-session-guard.sh +2 -0
- package/examples/path-traversal-guard.sh +2 -0
- package/examples/permission-audit-log.sh +2 -0
- package/examples/permission-entry-validator.sh +48 -0
- package/examples/php-lint-on-edit.sh +2 -0
- package/examples/pip-publish-guard.sh +2 -0
- package/examples/plain-language-danger-warn.sh +37 -0
- package/examples/plan-mode-enforcer.sh +2 -0
- package/examples/plugin-process-cleanup.sh +50 -0
- package/examples/polyglot-rm-guard.sh +59 -0
- package/examples/pr-description-check.sh +2 -0
- package/examples/pre-compact-knowledge-save.sh +53 -0
- package/examples/pre-compact-transcript-export.sh +85 -0
- package/examples/prefer-builtin-tools.sh +2 -0
- package/examples/prefer-const.sh +2 -0
- package/examples/prefer-dedicated-tools.sh +55 -0
- package/examples/prefer-optional-chaining.sh +2 -0
- package/examples/prisma-migrate-guard.sh +2 -0
- package/examples/prompt-injection-detector.sh +2 -0
- package/examples/prompt-length-guard.sh +2 -0
- package/examples/protect-dotfiles.sh +2 -0
- package/examples/public-repo-push-guard.sh +58 -0
- package/examples/push-requires-test-pass-record.sh +2 -0
- package/examples/push-requires-test-pass.sh +2 -0
- package/examples/rails-migration-guard.sh +2 -0
- package/examples/rate-limit-guard.sh +2 -0
- package/examples/read-all-files-enforcer.sh +51 -0
- package/examples/readme-exists-check.sh +2 -0
- package/examples/redis-flushall-guard.sh +2 -0
- package/examples/rm-safety-net.sh +2 -0
- package/examples/role-tool-guard.sh +69 -0
- package/examples/ruby-lint-on-edit.sh +2 -0
- package/examples/schema-migration-guard.sh +57 -0
- package/examples/scope-guard.sh +2 -0
- package/examples/secret-file-read-guard.sh +74 -0
- package/examples/self-modify-bypass-guard.sh +54 -0
- package/examples/sensitive-log-guard.sh +2 -0
- package/examples/session-checkpoint.sh +2 -0
- package/examples/session-end-logger.sh +57 -0
- package/examples/session-error-rate-monitor.sh +65 -0
- package/examples/session-health-monitor.sh +61 -0
- package/examples/session-memory-watchdog.sh +17 -0
- package/examples/session-permission-reset-guard.sh +39 -0
- package/examples/session-resume-env-fix.sh +49 -0
- package/examples/session-state-saver.sh +2 -0
- package/examples/session-summary-stop.sh +2 -0
- package/examples/session-summary.sh +2 -0
- package/examples/session-token-counter.sh +2 -0
- package/examples/shell-wrapper-guard.sh +2 -0
- package/examples/skill-gate.sh +2 -0
- package/examples/skill-injection-detector.sh +41 -0
- package/examples/spec-file-scope-guard.sh +69 -0
- package/examples/spring-profile-guard.sh +2 -0
- package/examples/sql-injection-detect.sh +2 -0
- package/examples/subagent-budget-guard.sh +2 -0
- package/examples/subagent-claudemd-inject.sh +45 -0
- package/examples/subagent-tool-call-limiter.sh +48 -0
- package/examples/svelte-lint-on-edit.sh +2 -0
- package/examples/swift-build-on-edit.sh +2 -0
- package/examples/system-message-workaround.sh +44 -0
- package/examples/system-package-guard.sh +2 -0
- package/examples/temp-file-cleanup.sh +2 -0
- package/examples/terminal-state-restore.sh +23 -0
- package/examples/test-after-edit.sh +2 -0
- package/examples/test-before-commit.sh +2 -0
- package/examples/test-before-push.sh +2 -0
- package/examples/test-exit-code-verify.sh +2 -0
- package/examples/timeout-guard.sh +2 -0
- package/examples/timezone-guard.sh +2 -0
- package/examples/tmp-output-size-guard.sh +46 -0
- package/examples/todo-check.sh +2 -0
- package/examples/todo-deadline-warn.sh +48 -0
- package/examples/token-budget-per-task.sh +55 -0
- package/examples/token-usage-tracker.sh +14 -0
- package/examples/turbo-cache-guard.sh +2 -0
- package/examples/uncommitted-changes-stop.sh +2 -0
- package/examples/uncommitted-work-shield.sh +37 -0
- package/examples/usage-warn.sh +2 -0
- package/examples/verify-before-commit.sh +2 -0
- package/examples/vue-lint-on-edit.sh +2 -0
- package/examples/webfetch-domain-allow.sh +96 -0
- package/examples/worktree-memory-guard.sh +47 -0
- package/examples/worktree-project-unify.sh +19 -0
- package/examples/worktree-unmerged-guard.sh +2 -0
- package/examples/write-overwrite-confirm.sh +40 -0
- package/examples/write-secret-guard.sh +2 -0
- package/examples/write-test-ratio.sh +2 -0
- 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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
package/examples/deploy-guard.sh
CHANGED
|
@@ -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
|
|
@@ -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
|