Tue May 5 03:45:13 PM EDT 2026

From Aqua Frog, 1 Week ago, written in Plain Text, viewed 1 times.
URL https://pste.us/view/5acd3a47 Embed
Download Paste or View Raw
  1. #!/usr/bin/env bash
  2. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  3. ##@Version           :  202604281009-git
  4. # @@Author           :  Jason Hempstead
  5. # @@Contact          :  git-admin@casjaysdev.pro
  6. # @@ReadME           :  protect-host.sh --help
  7. # @@Copyright        :  Copyright: (c) 2026 Jason Hempstead, Casjays Developments
  8. # @@Created          :  Friday, May 01, 2026 10:22 EDT
  9. # @@File             :  protect-host.sh
  10. # @@Description      :  Claude Code PreToolUse hook - block destructive Bash ops on host system paths
  11. # @@Changelog        :  Initial version
  12. # @@TODO             :  See project issues
  13. # @@Other            :  Container-mediated commands (docker/incus/podman/kubectl exec) are exempted
  14. # @@Resource         :  github.com/casapps/claude-code-hooks
  15. # @@Terminal App     :  no
  16. # @@sudo/root        :  no
  17. # @@Template         :  bash/simple
  18. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  19. # shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329
  20. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  21. VERSION="202602020740-git"
  22. # - - - - - - - - - - - - - - - - - - - - - - - - -
  23. set -uo pipefail
  24. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  25. # Globals
  26. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  27. # Protected host-path prefixes. Anything under these roots is off limits to destructive ops on the host. Inside containers, anything goes.
  28. PROTECTED_PATHS='(/|/bin|/sbin|/usr|/etc|/lib|/lib32|/lib64|/boot|/var|/root|/opt|/dev|/proc|/sys|/srv|/run)'
  29. # Destructive verbs that, paired with a protected path argument, get blocked.
  30. DESTRUCTIVE_VERBS='rm|rmd|rmdir|chmod|chown|chgrp|shred|truncate|wipefs'
  31. # Move/copy/link verbs that, when writing INTO a protected path, get blocked.
  32. WRITE_VERBS='mv|cp|install|ln'
  33. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  34. # Helpers
  35. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  36. # __require_cmd <name> - bail with a clear error if a required tool
  37. # is missing. A broken hook exits 0 (no-op) so we never silently
  38. # block every Bash call.
  39. __require_cmd() {
  40.   if ! command -v "$1" >/dev/null 2>&1; then
  41.     printf 'protect-host.sh: required command not found: %s\n' "$1" >&2
  42.     exit 0
  43.   fi
  44. }
  45. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  46. # __extract_command - read the JSON payload on stdin, print tool_input.command from the Claude Code PreToolUse event.
  47. __extract_command() {
  48.   python3 -c '
  49. import json, sys
  50. try:
  51.     d = json.load(sys.stdin)
  52.     print(d.get("tool_input", {}).get("command", ""))
  53. except Exception:
  54.     print("")
  55. '
  56. }
  57. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  58. # __is_container_mediated <command> - 0 if the command is mediated through a container/sandbox runtime (docker/incus/etc.).
  59. __is_container_mediated() {
  60.   case "$1" in
  61.   "docker exec "* | "docker run "*) return 0 ;;
  62.   "docker compose exec "* | "docker compose run "*) return 0 ;;
  63.   "docker-compose exec "* | "docker-compose run "*) return 0 ;;
  64.   "incus exec "* | "incus shell "*) return 0 ;;
  65.   "lxc exec "*) return 0 ;;
  66.   "podman exec "* | "podman run "*) return 0 ;;
  67.   "kubectl exec "*) return 0 ;;
  68.   "nsenter "* | "chroot "*) return 0 ;;
  69.   esac
  70.   return 1
  71. }
  72. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  73. # __block <reason> - emit a structured BLOCKED message and exit 2.
  74. __block() {
  75.   printf 'BLOCKED: %s\n' "$1" >&2
  76.   printf "Use 'docker exec', 'incus exec', or work inside a VM for destructive ops.\n" >&2
  77.   exit 2
  78. }
  79. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  80. # __match <regex> - return 0 if "$CMD" matches the extended regex.
  81. __match() {
  82.   printf '%s' "$CMD" | grep -qE "$1"
  83. }
  84. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  85. __require_cmd python3
  86. __require_cmd grep
  87. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  88. INPUT="$(cat)"
  89. CMD="$(printf '%s' "$INPUT" | __extract_command)"
  90. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  91. # Empty / non-Bash payload - nothing to inspect.
  92. [ -z "$CMD" ] && exit 0
  93. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  94. # Container-mediated commands are explicitly trusted at this layer.
  95. if __is_container_mediated "$CMD"; then
  96.   exit 0
  97. fi
  98. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  99. # Word-boundary fragment reused across rules. Matches the start of a logical command position: line start, whitespace, or a shell separator.
  100. WORD_START='(^|[[:space:];|&`(])'
  101. # Up to 8 non-flag, non-operator tokens between a verb and its target path, so things like 'chmod -R 777 /etc' or 'find -L /etc -delete' match.
  102. TOKEN_GAP='([[:space:]]+[^[:space:]&|;()`<>]+){0,8}'
  103. # Tail fragment that anchors a protected path as a real argument boundary (followed by whitespace, slash, or end of string).
  104. PATH_TAIL="${PROTECTED_PATHS}([[:space:]/]|\$)"
  105. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  106. # Rule 1: destructive verb followed (eventually) by a protected path.
  107. if __match "${WORD_START}(${DESTRUCTIVE_VERBS})${TOKEN_GAP}[[:space:]]+${PATH_TAIL}"; then
  108.   __block "destructive command targeting host system path"
  109. fi
  110. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  111. # Rule 2: shell redirect (> or >>) that truncates / appends a protected file.
  112. # Exempts /dev/null, /dev/std{in,out,err}, /dev/tty, /dev/fd/N, /dev/pts/N — these are
  113. # I/O pseudo-devices, not real files. Walks every redirect target via python and blocks
  114. # only if a target is under a protected root AND not a safe pseudo-device.
  115. __check_redirects() {
  116.   python3 - "$1" <<'PYSCRIPT'
  117. import re, sys
  118. cmd = sys.argv[1]
  119. protected = re.compile(r"^(/|/bin|/sbin|/usr|/etc|/lib|/lib32|/lib64|/boot|/var|/root|/opt|/dev|/proc|/sys|/srv|/run)(/|$)")
  120. safe = re.compile(r"^/dev/(null|stdin|stdout|stderr|tty|fd/\d+|pts/\d+)$")
  121. for m in re.finditer(r"(?:^|[\s;|&`(])(?:\d+|&)?>>?\s*([^\s;|&`()<>]+)", cmd):
  122.     target = m.group(1).strip("\"'")
  123.     if protected.match(target) and not safe.match(target):
  124.         print(target)
  125.         sys.exit(1)
  126. sys.exit(0)
  127. PYSCRIPT
  128. }
  129. if BAD_REDIRECT="$(__check_redirects "$CMD")"; then
  130.   : # all redirects safe
  131. else
  132.   __block "shell redirect to host system path: $BAD_REDIRECT"
  133. fi
  134. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  135. # Rule 3: 'find <syspath> ... -delete' or '... -exec rm ...'.
  136. if __match "${WORD_START}find[[:space:]]+${PROTECTED_PATHS}([[:space:]/].*-delete|.*-exec[[:space:]]+rm)"; then
  137.   __block "'find -delete' / '-exec rm' targeting host system path"
  138. fi
  139. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  140. # Rule 4: write-verb (mv/cp/install/ln) with a protected destination.
  141. if __match "${WORD_START}(${WRITE_VERBS})([[:space:]]+[^[:space:]&|;()\`<>]+){1,8}[[:space:]]+${PATH_TAIL}"; then
  142.   __block "mv/cp/install/ln with host system path destination"
  143. fi
  144. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  145. # Rule 5: raw block-device writers targeting host disks (sda/hda/nvme/of=).
  146. if __match "${WORD_START}(dd|mkfs[^[:space:]]*)[[:space:]].*((of|of=)/dev/|[[:space:]]/dev/[sh]d|[[:space:]]/dev/nvme)"; then
  147.   __block "raw disk writer targeting host device"
  148. fi
  149. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  150. # Rule 6: 'cd /<syspath> && rm ...' style cwd-shift attempts.
  151. if __match "${WORD_START}cd[[:space:]]+${PROTECTED_PATHS}([[:space:]/][^&;|]*)?[[:space:]]*(&&|;|\|\|)[[:space:]]*(${DESTRUCTIVE_VERBS}|>|>>)"; then
  152.   __block "'cd' into host system path followed by destructive op"
  153. fi
  154. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  155. exit 0
  156.  

Reply to "Tue May 5 03:45:13 PM EDT 2026"

Here you can reply to the paste above

captcha