# Security Hooks Guide

> **Guide Origin**: Official | **ArcKit Version**: [VERSION]

ArcKit includes three security hooks that provide layered protection against accidental secret exposure during Claude Code sessions. These hooks run automatically and require no manual configuration.

## Three-Layer Protection Model

The security hooks implement defence in depth with three complementary layers:

| Layer | Hook | Hook Type | Trigger | Purpose |
|-------|------|-----------|---------|---------|
| 1 | `secret-detection.py` | UserPromptSubmit | Every user message | Catches secrets pasted into prompts |
| 2 | `file-protection.py` | PreToolUse (Edit\|Write) | File edit/write operations | Blocks writes to sensitive file paths |
| 3 | `secret-file-scanner.py` | PreToolUse (Edit\|Write) | File edit/write operations | Scans content being written for embedded secrets |

### How the layers work together

1. **Layer 1 — Prompt scanning**: Before your message reaches the model, `secret-detection.py` scans the prompt text for known secret patterns (API keys, tokens, passwords, connection strings). If a secret is detected, the message is blocked before it is sent.

2. **Layer 2 — Path protection**: When Claude attempts to edit or write a file, `file-protection.py` checks whether the target file path is sensitive (environment files, credential stores, private keys, lock files). Protected files are blocked regardless of content.

3. **Layer 3 — Content scanning**: For files that pass the path check, `secret-file-scanner.py` examines the actual content being written for secret patterns. This catches cases where a secret is embedded in an otherwise safe file path.

## What Each Hook Detects

### secret-detection.py (Prompt Scanner)

Scans user prompts for:

- **Key-value secrets**: `pwd=`, `api_key=`, `auth_token=`, `api_secret=`
- **Provider-specific tokens**: OpenAI (`sk-`), Anthropic (`sk-ant-`), GitHub (`ghp_`, `gho_`, `ghs_`), AWS (`AKIA`), Notion (`ntn_`), Atlassian (`ATATT`), Slack (`xox`), Google (`AIza`)
- **Bearer tokens**: `bearer <value>`
- **Connection strings**: `mongodb://`, `postgres://`, `mysql://`, `redis://` with credentials
- **PEM private keys**: `-----BEGIN ... KEY-----` headers
- **High-entropy credentials**: Long base64-like strings assigned to key/secret variables

**Example block:**

```text
$ echo '{"userPrompt": "Use this key sk-ant-abc123def456ghi789"}' \
    | python3 plugins/arckit-claude/hooks/secret-detection.py
# Output: {"decision": "block", "reason": "Warning: Potential secrets detected: ..."}
```

### file-protection.py (Path Guard)

Blocks writes to:

- **Environment files**: `.env`, `.env.local`, `.env.production`, `.env.development`
- **Lock files**: `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `Gemfile.lock`, `poetry.lock`, `Cargo.lock`
- **Version control**: `.git/`
- **Credential directories**: `.aws/`, `.ssh/`, `.gnupg/`
- **Secret files**: `credentials`, `credentials.json`, `secrets.json`, `secrets.yaml`, `secrets.yml`, `.secrets`
- **Private keys**: `*.pem`, `*.key`, `*.p12`, `*.pfx`, `id_rsa`, `id_ed25519`, `id_ecdsa`
- **Auth config files**: `.npmrc`, `.pypirc`, `.netrc`
- **Files with sensitive keywords**: filenames containing terms like "credential", "api-key", etc.

**Example block:**

```text
$ echo '{"tool_name": "Write", "tool_input": {"file_path": ".env", "content": "DB_HOST=localhost"}}' \
    | python3 plugins/arckit-claude/hooks/file-protection.py
# Output: {"decision": "block", "reason": "Protected: Protected file: .env\nFile: .env\n..."}
```

### secret-file-scanner.py (Content Scanner)

Scans the content of files being written or edited using the same pattern library as `secret-detection.py`. This catches secrets that might be embedded in source code, configuration files, or documentation.

**Example block:**

```text
$ echo '{"tool_name": "Write", "tool_input": {"file_path": "config.py", "content": "db_host=localhost"}}' \
    | python3 plugins/arckit-claude/hooks/secret-file-scanner.py
# Output: no output (safe content passes through)
```

## Adding Exceptions

### File protection exceptions

To allow edits to files that would normally be blocked by `file-protection.py`, you have three options:

**1. Add to `ALLOWED_EXCEPTIONS`** — for specific filenames that are legitimate security tools:

```python
ALLOWED_EXCEPTIONS = [
    ".secrets.baseline",
    ".pre-commit-config.yaml",
    "secret-detection.py",
    "secret-file-scanner.py",
    "your-new-exception.py",  # Add here
]
```

**2. Add to `ALLOWED_DIRECTORIES`** — for directories containing documentation or tool files that discuss secrets:

```python
ALLOWED_DIRECTORIES = [
    "plugins/arckit-claude/commands/",
    "plugins/arckit-claude/templates/",
    "plugins/arckit-claude/agents/",
    "plugins/arckit-claude/hooks/",
    "docs/",
    ".arckit/templates/",
    "your-project/docs/",  # Add here
]
```

### Content scanner skip patterns

To prevent `secret-file-scanner.py` from scanning certain files (e.g., documentation that legitimately discusses secret formats), add a regex to `SKIP_PATTERNS`:

```python
SKIP_PATTERNS = [
    r"\.pre-commit-config\.yaml$",
    r"secret-detection\.py$",
    r"secret-file-scanner\.py$",
    r"file-protection\.py$",
    r"\.secrets\.baseline$",
    r"plugins/arckit-claude/commands/.*\.md$",
    r"plugins/arckit-claude/templates/.*\.md$",
    r"docs/.*\.md$",
    r"CHANGELOG\.md$",
    r"README\.md$",
    r"your-custom-pattern\.md$",  # Add here
]
```

## Restricting web access for research agents

ArcKit's web-research agents (`research`, `datascout`, `aws-research`, `azure-research`, `gcp-research`, `gov-*`, `grants`) use `WebFetch`. For a regulated or OFFICIAL-SENSITIVE deployment you may want to confine that traffic to an approved set of domains. As of Claude Code v2.1.162, explicit `WebFetch(domain:...)` permission rules **take precedence over the built-in preapproved-host auto-allow** (previously the preapproved hosts leaked through), so a deny/allow policy now actually holds.

Add rules to `.claude/settings.json` (or org managed settings for fleet-wide enforcement — see [Fleet & Version Governance](enterprise-scale.md#fleet--version-governance-managed-settings)):

```json
{
  "permissions": {
    "deny": ["WebFetch"],
    "allow": [
      "WebFetch(domain:*.gov.uk)",
      "WebFetch(domain:*.service.gov.uk)",
      "WebFetch(domain:docs.aws.amazon.com)"
    ]
  }
}
```

This is a Claude Code permission policy, not an ArcKit hook — ArcKit ships no domain restrictions by default, leaving the choice to the deployment.

**Sandboxed commands can be restricted harder (Claude Code v2.1.219+).** `WebFetch` rules govern the model's own fetches. Commands that run inside the sandbox reach the network on their own, and `sandbox.network.strictAllowlist` denies any non-allowlisted host outright rather than prompting for it. Pair it with the MCP endpoint allowlist — see [the enterprise security baseline](enterprise-scale.md#5-set-the-security-and-network-baseline).

## Testing

You can test each hook by piping JSON to stdin. All hooks handle empty input gracefully.

### Test file protection blocks .env writes

```bash
echo '{"tool_name": "Write", "tool_input": {"file_path": ".env", "content": "VALUE=abc"}}' \
    | python3 plugins/arckit-claude/hooks/file-protection.py
```

Expected: `{"decision": "block", ...}`

### Test secret detection blocks API keys in prompts

```bash
echo '{"userPrompt": "Use this key sk-ant-abc123def456ghi789"}' \
    | python3 plugins/arckit-claude/hooks/secret-detection.py
```

Expected: `{"decision": "block", ...}`

### Test secret file scanner blocks secrets in content

```bash
echo '{"tool_name": "Write", "tool_input": {"file_path": "test.md", "content": "pwd=hunter2"}}' \
    | python3 plugins/arckit-claude/hooks/secret-file-scanner.py
```

Expected: `{"decision": "block", ...}`

### Test that allowed files pass through

```bash
echo '{"tool_name": "Write", "tool_input": {"file_path": "plugins/arckit-claude/commands/research.md", "content": "Use API key format: sk-xxx"}}' \
    | python3 plugins/arckit-claude/hooks/secret-file-scanner.py
```

Expected: no output (exit code 0, file is in a skip pattern)

### Test that empty input does not crash

```bash
echo '' | python3 plugins/arckit-claude/hooks/file-protection.py
echo '' | python3 plugins/arckit-claude/hooks/secret-detection.py
echo '' | python3 plugins/arckit-claude/hooks/secret-file-scanner.py
```

Expected: no output or `{}` (exit code 0 for all)
