Skip to content

Fix host-protection hooks failing closed and report jq dependency - #28

Merged
aniongithub merged 1 commit into
mainfrom
aniongithub-fix-guard-hook-failopen-and-jq
Aug 2, 2026
Merged

Fix host-protection hooks failing closed and report jq dependency#28
aniongithub merged 1 commit into
mainfrom
aniongithub-fix-guard-hook-failopen-and-jq

Conversation

@aniongithub

Copy link
Copy Markdown
Owner

Problem

The host-protection hooks could fail closed — crashing in a way that Copilot/Claude interpret as a deny, which blocks all bash commands everywhere, even in repos without a devcontainer.

Root cause was two-fold:

  1. jq not found under the hook's minimal PATH. The hooks run with a minimal PATH that omits Homebrew's /opt/homebrew/bin, so even an installed jq wasn't found.
  2. No fail-open safety. With set -euo pipefail, a missing/failing jq made the hook exit non-zero → treated as a deny.

A deny is meant to be signaled by stdout JSON content, with the script exiting 0 in all intended cases. So any non-zero exit is really a crash and should fail open, not block.

Changes

  • PATH-prepend common bin dirs (/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin) in both hooks so jq resolves under the minimal PATH.
  • Subshell-capture fail-open in both hooks:
    • devcontainer-guard.sh runs all decision logic in a decide() subshell (set -euo pipefail) that prints deny JSON on stdout or nothing for allow. The parent uses if DECISION=$(decide "$INPUT"); any failure → allow.
    • devcontainer-skill-loader.sh uses the same pattern via build_context(); any failure → no context injected.
    • A deny now has exactly one path (successfully print deny JSON); every other outcome fails open.
  • Removed a fragile ERR-trap approach. macOS ships bash 3.2, where exit inside an ERR trap fired by a var=$(cmd) assignment does not reliably terminate the script — the subshell-capture pattern doesn't depend on trap-exit at all.
  • Report jq in install.sh / install.ps1 using the same ✓ / ✗ — <url> style as the other dependencies (devpod, gh, devcontainer), warning that the host-protection hooks are inactive until jq is installed.

Testing

Verified 11 cases against both hooks:

  • Deny in devcontainer repo (Copilot and Claude payload formats)
  • git/gh allowlist pass-through
  • Mixed chain git && npm → deny
  • Non-devcontainer repo → allow
  • USER_CONFIRMED_HOST_OPERATION=1 bypass
  • Non-bash tool → allow
  • Malformed JSON → single clean fail-open
  • jq genuinely absent → single fail-open, exit 0, no spurious deny
  • Skill-loader injects context when devcontainer + SKILL.md present; allows otherwise

The PreToolUse guard hook and SessionStart skill-loader hook relied on
jq under `set -euo pipefail`. When jq was missing or simply not on the
hook's minimal PATH (e.g. Homebrew's /opt/homebrew/bin), the hook
crashed with a non-zero exit, which Copilot/Claude treat as a deny --
blocking ALL bash commands everywhere, even in non-devcontainer repos.

Changes:
- Prepend common bin dirs (/opt/homebrew/bin, /usr/local/bin, /usr/bin,
  /bin) to PATH so jq is found under the hooks' minimal PATH.
- Move all decision logic into a subshell (decide()/build_context())
  run under set -euo pipefail. The parent captures stdout via
  `if VAR=$(decide ...)`. A deny is signaled ONLY by stdout content;
  any failure (missing jq, malformed payload, etc.) makes the subshell
  exit non-zero and the parent falls open (allow / no context).
  This replaces a fragile ERR-trap approach that does not reliably
  terminate on macOS bash 3.2 during command-substitution assignments.
- Report jq in install.sh/install.ps1 using the same checkmark style as
  the other dependencies, warning that hooks are inactive without it.

Copilot-Session: 1061ab5c-0b78-4baa-87e2-2ba93359413f
@aniongithub
aniongithub merged commit 9ae81e0 into main Aug 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant