[FEATURE] Security: approved bash command that run scripts should check the script hash

Status Fixed / completed
Maintainer reply None cached
Activity 5 comments · opened Mar 7, 2026 · closed Mar 25, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

In order to improve agent independence while at the same time trying to avoid giving blanket permissions to claude, I usually allow claude to run pre-approved scripts. This is usually the difference between having the agent work independently for 5-10 minutes vs. having the agent run independently for half an hour or longer without the need for aggressive sandboxing and dangerous permission skips.

There is the remnant risk that the agent will edit the pre-approved scripts independently and then run whatever bash command comes to mind. This doesnt happen in short runs with clean context, however this might happen on very long sesions with context overrun, especially when claude rabbit holes into debugging a hallucinated issue.

Proposed Solution

Bash permissions that involve running scripts with direct invocation like this "./my-script.sh" should, when giving claude permanent permission to run the command, remember the sha-hash of the script at the time the permission was given. Subsequent auto-allows should only trigger, if the hash of the invoked script matches the approved hash, otherwise it is treated as an unknown command and claude must ask for permission.

Alternative Solutions

Using scripts that cant be edited by claude via ACL. This doesnt work well because the script could be moved, if claude has permission to the directory, and a new script with the same name can be created in the same place.

Priority

Low - Nice to have

Feature Category

Other

Use Case Example

_No response_

Additional Context

_No response_

View original on GitHub ↗

5 Comments

github-actions[bot] · 7 months ago

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/11226
  2. https://github.com/anthropics/claude-code/issues/23870

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

MaxwellCalkin · 7 months ago

Good idea. The core risk here — agent edits a pre-approved script and then runs it — is a form of TOCTOU (time-of-check, time-of-use) vulnerability. The permission was granted for script-at-hash-X, but the agent executes script-at-hash-Y.

Until hash-pinning is built into Claude Code's permission system, you can approximate this with a PreToolUse hook today:

#!/bin/bash
# hooks/check-script-hash.sh — pin pre-approved scripts to known hashes
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
[ "$TOOL" != "Bash" ] && exit 0

CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

# Extract script path from direct invocations
SCRIPT=$(echo "$CMD" | grep -oP '^\./[\w\-]+\.sh' || true)
[ -z "$SCRIPT" ] && exit 0

# Known good hashes (set these when you first approve the script)
declare -A APPROVED=(
  ["./build.sh"]="a1b2c3d4e5..."
  ["./deploy.sh"]="f6g7h8i9j0..."
)

EXPECTED="${APPROVED[$SCRIPT]}"
[ -z "$EXPECTED" ] && exit 0

ACTUAL=$(sha256sum "$SCRIPT" 2>/dev/null | cut -d' ' -f1)
if [ "$ACTUAL" != "$EXPECTED" ]; then
  echo "BLOCKED: $SCRIPT hash mismatch (expected ${EXPECTED:0:12}..., got ${ACTUAL:0:12}...)" >&2
  exit 2  # hard block
fi
exit 0

Add to .claude/settings.json:

{ "hooks": { "PreToolUse": [{ "matcher": "Bash", "command": "./hooks/check-script-hash.sh" }] } }

Exit code 2 = hard block that the model can't override.

For a more comprehensive approach, sentinel-ai's sentinel hook scans every Bash command for dangerous patterns (rm -rf, credential access, data exfiltration) at ~0.05ms latency — complements hash-pinning by catching cases where the agent bypasses scripts entirely and uses inline commands.

yurukusa · 7 months ago

Smart concern — the model could edit a pre-approved script to inject arbitrary commands, then run the "approved" script. You can enforce script integrity with hooks today:

1. PreToolUse hook that verifies script hashes before execution:

#!/bin/bash
# ~/.claude/hooks/script-hash-check.sh
# Verify pre-approved scripts haven't been modified before allowing execution

INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')

[ "$TOOL" != "Bash" ] && exit 0

CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Known approved scripts and their SHA256 hashes
declare -A APPROVED_HASHES=(
  ["./scripts/deploy.sh"]="a1b2c3d4e5..."
  ["./scripts/test.sh"]="f6g7h8i9j0..."
  ["./scripts/build.sh"]="k1l2m3n4o5..."
)

# Check if the command runs a known script
for script in "${!APPROVED_HASHES[@]}"; do
  if echo "$CMD" | grep -q "$script"; then
    CURRENT_HASH=$(sha256sum "$script" 2>/dev/null | cut -d' ' -f1)
    EXPECTED="${APPROVED_HASHES[$script]}"
    
    if [ "$CURRENT_HASH" != "$EXPECTED" ]; then
      echo "BLOCKED: Script $script has been modified since approval!" >&2
      echo "Expected hash: $EXPECTED" >&2
      echo "Current hash:  $CURRENT_HASH" >&2
      echo "Re-approve the script or revert the changes." >&2
      exit 2
    fi
  fi
done

exit 0

2. Auto-generate the hash file at session start:

#!/bin/bash
# ~/.claude/hooks/hash-approved-scripts.sh (SessionStart)
# Snapshot hashes of all approved scripts at session start

HASH_FILE="/tmp/.claude-script-hashes"
: > "$HASH_FILE"

# Hash all scripts in the approved directory
for script in ./scripts/*.sh; do
  sha256sum "$script" >> "$HASH_FILE" 2>/dev/null
done

cat <<EOF
{"systemMessage": "$(wc -l < "$HASH_FILE") approved scripts hashed. Any modification will be detected."}
EOF

Then the PreToolUse hook checks against /tmp/.claude-script-hashes:

# Simplified version using the hash file
SCRIPT=$(echo "$CMD" | grep -oP '\./scripts/\S+\.sh' | head -1)
if [ -n "$SCRIPT" ] && grep -q "$SCRIPT" /tmp/.claude-script-hashes; then
  if ! sha256sum --check /tmp/.claude-script-hashes 2>/dev/null | grep -q "$SCRIPT.*OK"; then
    echo "BLOCKED: $SCRIPT modified since session start!" >&2
    exit 2
  fi
fi

3. Protect scripts from modification (belt and suspenders):

#!/bin/bash
# ~/.claude/hooks/protect-scripts.sh (PreToolUse on Edit/Write)
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
[ "$TOOL" != "Edit" ] && [ "$TOOL" != "Write" ] && exit 0

FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
case "$FILE" in
  */scripts/*.sh)
    echo "BLOCKED: Cannot modify approved scripts. These are immutable during the session." >&2
    exit 2
    ;;
esac
exit 0

This gives you: hash verification before execution + modification prevention + session-start baseline. The model can run pre-approved scripts but cannot modify them and cannot run modified versions.

rbardak · 6 months ago

I actually found a much cleaner solution: Write skills that invoke scripts within the skills directory. Accept all edits does not grant access to the .claude directory, so there is no risk to overwrite scripts after approval.

github-actions[bot] · 6 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.