Packs / Credential Guard

Credential Guard

Hookv1.0.0

Blocks shell and HTTP tool calls that would ship a literal API key or token to an outbound command.

Install

# once per machine
curl -fsSL https://hookcrate.com/install | bash
# then
hookcrate install credential-guard

Get access

One license covers all packs.

What it does

A PreToolUse hook that inspects Bash commands and HTTP-style tool inputs before they run. If an outbound client (curl, wget, httpie, Invoke-WebRequest, scp, ...) is invoked with a literal secret matching common key shapes — sk-…, sk_live_…, AKIA…, ghp_…, xox…, AIza…, raw Bearer tokens, private-key blocks — the call is denied and Claude is told why. $VAR, ${VAR} and %VAR% references pass, so the secure pattern (secrets in env) keeps working. Every decision is appended to a local JSONL audit log with the secret redacted.

Usage: Install, then try `curl -H 'Authorization: Bearer sk-abc...' https://api.example.com` in a session — it is denied. `curl -H "Authorization: Bearer $API_KEY"` passes.

When to use this

Use Credential Guard the moment a coding agent can run shell commands in a repository that has a .env file, a CI token, or a cloud key anywhere on disk. The failure mode is mundane: the agent reads the key to check it exists, then pastes the literal value into a curl command to test the endpoint. That command now lives in your shell history, the session transcript, any log shipper watching stdout, and sometimes a pull-request description. Nothing malicious happened, but the key is burned. The hook intercepts the call before it runs, denies it with a reason the agent can act on, and lets the same command through when the secret is referenced as $VAR. It matters most in three places: projects that talk to paid APIs (Stripe, cloud providers, LLM vendors), monorepos where keys for several services sit side by side, and any setup where agent sessions are recorded or shared. It is also useful as an audit trail — every in-scope decision lands in a local JSONL file with the secret redacted, so you can see how often the agent reached for a literal key. It does not replace secret scanning in CI; it closes the gap before code is even written.

When it triggers

  • PreToolUse on Bash — any shell command
  • PreToolUse on WebFetch and any tool whose name contains fetch/http/curl/request (MCP HTTP clients included)

Preview — a sample session

Illustrative transcript showing the pack in use.

User: Check whether our billing API accepts the new key. It's in .env as BILLING_KEY.

Claude: I'll read the key and call the health endpoint.

● Bash(grep BILLING_KEY .env)
  ⎿  BILLING_KEY=sk_live_51Hxq2LkTe9zR0pQ7bN4mW8vC3yA1sD6fG
● Bash(curl -s -H "Authorization: Bearer sk_live_51Hxq2LkTe9zR0pQ7bN4mW8vC3yA1sD6fG" https://api.billing.example/v1/health)
⛔ PreToolUse hook (credential-guard) blocked the tool call
  credential-guard: blocked — literal secret in shell command
  (stripe-live-secret sk_liv…(42 chars), bearer-token Bearer…(49 chars)).
  Never paste keys into outbound calls. Put the value in an environment
  variable and reference it (e.g. -H "Authorization: Bearer $API_KEY").

Claude: Good catch — I shouldn't put the live key into the command line where it lands in shell history and the transcript. Sourcing it from the environment instead.

● Bash(set -a; source .env; set +a; curl -s -H "Authorization: Bearer $BILLING_KEY" https://api.billing.example/v1/health)
  ⎿  {"status":"ok","key_last4":"sD6f","mode":"live"}
✓ PreToolUse hook (credential-guard) allowed
  (logged to .claude/logs/credential-guard.jsonl: decision=allow, outbound=true)

Claude: The billing API accepts the key: status: ok, mode live, key ending sD6f. The literal value never left the environment variable.

settings.json registration

The CLI prints this after install; --apply merges it for you.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|WebFetch|.*(fetch|http|curl|request).*",
        "hooks": [
          {
            "type": "command",
            "command": "bun \"${CLAUDE_PROJECT_DIR}/.claude/hooks/credential-guard.ts\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

FAQ

Does it block every command that contains a key?

No. It blocks only outbound commands — curl, wget, scp, httpie, Invoke-WebRequest and friends — and HTTP-style tool inputs. Grepping a file for a key, or writing one into .env, is allowed; the hook is about data leaving the machine.

What if I need to send a published test key?

Add it to .claude/credential-guard.allow, one literal or /regex/ per line. Stripe's sk_test_ prefix is the typical entry. The allowlist is per project and never silences a whole detector.

Will `Authorization: Bearer $TOKEN` be blocked?

No. Every pattern that could match a bearer token or a URL password explicitly excludes values that start with $, ${, % or $env:. Referencing a secret through the environment is the pattern the hook is steering you toward.