Saturday, April 4, 2026

Hardening Claude Code: Security Hooks for Package Managers & Web Access

In the previous post I introduced the Dynamic CDI Test Bean Addon — the first os890 CDI extension created entirely in an agentic AI workflow. During that and other longer multi-day sessions for larger projects with Claude, I also needed to harden the AI agent itself. Here's what came out of that: a set of PreToolUse hooks that improve security guardrails at runtime — suggested and implemented by Claude itself.

Disclaimer — Use at Your Own Risk and without warranty These hooks are a best-effort hardening measure, not a security guarantee. They are not a replacement for a proper outbound firewall, network-level controls, or enterprise security policies. Hook behavior may change with future Claude Code updates. URL extraction uses regex and may miss obfuscated network calls. Provided as-is, without warranty and at your own risk. Test thoroughly before relying on them.

The Two Risks

Claude Code can run shell commands, install packages, and fetch web content on your behalf. Two things which needed attention when called without scripts:

1. Supply-chain attacksnpm install / pnpm add / yarn add / bun install can execute arbitrary preinstall/postinstall scripts. A single malicious dependency can steal credentials or install backdoors.

2. Unrestricted web access — Claude Code can fetch any URL via WebFetch, WebSearch, or curl/wget. Prompt injection in fetched content could trick it into exfiltrating data.

There is also a third, subtler motivation: keeping research focused. In longer agentic sessions, an unrestricted agent will readily pull in information from wherever it lands — forums, random blogs, outdated docs. Limiting web access to a curated allowlist of trusted sources means Claude's research stays grounded in high-quality references rather than drifting toward whatever the search engine surfaces.

CLAUDE.md instructions help, but they can be ignored. Hooks can't — they intercept tool calls before execution.

The Setup

Two Node.js scripts in ~/.claude/, wired via settings.json:

1. package-install-check.js — blocks any npm/pnpm/yarn/bun install command that doesn't include --ignore-scripts:

// Receives tool call JSON on stdin, outputs decision to stdout
const input = JSON.parse(data);
const cmd   = input.tool_input.command || '';

if (
  /\b(npm|pnpm|yarn|bun)\s+(install|i|ci|add)\b/.test(cmd) &&
  !/--ignore-scripts/.test(cmd)
) {
  // deny — block before execution
} else {
  // allow
}

2. domain-check.js — checks URLs against ~/.claude/allowed-domains.txt. Handles WebFetch (URL check), WebSearch (allowed_domains parameter check), and Bash (curl/wget URL extraction).

3. settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/package-install-check.js"
          }
        ]
      },
      {
        "matcher": "WebFetch",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/domain-check.js"
          }
        ]
      },
      {
        "matcher": "WebSearch",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/domain-check.js"
          }
        ]
      }
    ]
  }
}

Lesson Learned: Hook Response Format

One thing that cost us time: the hook response format. Claude initially used {"decision":"allow"} which caused a PreToolUse:Bash hook error on every command. The correct format is:

// Allow
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow"
  }
}

// Block
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Explanation"
  }
}

Also: use file-based scripts with async stdin reading (process.stdin.on('data'/'end')), not inline node -e one-liners. The inline approach has stdin timing issues that cause spurious errors.

Full Setup & Scripts

The complete setup — including one-command install/uninstall scripts, the full domain-check.js, a starter domain whitelist, verification tests, and known limitations — will follow soon.

No comments:

Post a Comment

Hardening Claude Code: Security Hooks for Package Managers & Web Access

In the previous post I introduced the Dynamic CDI Test Bean Addon — the first os890 CDI extension created entirely in an agentic AI wor...