# How the PreToolUse Hook Guardrail Detects Hardcoded Secrets in Claude Code Harness

> Discover how the PreToolUse hook guardrail in claude-code-harness detects hardcoded secrets. It scans code diffs for credentials, preventing exposure before execution.

- Repository: [Chachamaru/claude-code-harness](https://github.com/Chachamaru127/claude-code-harness)
- Tags: how-to-guide
- Published: 2026-05-28

---

**The PreToolUse hook guardrail intercepts tool operations before execution by scanning code diffs with regex pattern R09 to identify hardcoded API keys, tokens, and passwords, immediately denying any operation that would expose secrets to the repository.**

The claude-code-harness repository implements a security-focused PreToolUse guardrail that prevents hardcoded secrets from entering your codebase. This Go-based hook analyzes file changes in real-time using declarative regex rules to catch credential assignments before they reach version control. The system evaluates every file modification through rule `R09:warn-secret-file-read`, which specifically targets secret-like variable assignments in the changed lines.

## How the Detection Pipeline Works

The guardrail engine processes potential secrets through a multi-stage pipeline that executes before any tool can modify the repository.

### Building the Hook Input Context

When a tool like Write, Edit, or Bash is invoked, the runtime constructs a `hookproto.HookInput` structure containing the command details, target file paths, and the diff of proposed changes. This input is passed to the `EvaluatePreTool` function in [`go/internal/guardrail/pre_tool.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/guardrail/pre_tool.go) (lines 180-210), which serves as the entry point for all PreToolUse validations.

### Evaluating Declarative Guardrail Rules

The `EvaluatePreTool` function forwards the input to the generic guardrail engine via `guardrail.Evaluate`. This engine iterates over the declarative rule list defined in [`go/internal/guardrail/rules.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/guardrail/rules.go). The specific rule for secret detection is **R09:warn-secret-file-read**, which contains a case-insensitive regex pattern that matches assignments such as `API_KEY = "abcd1234…"`, `secret = "s3cr3t!"`, or `token="..."`.

When the regex matches any line in the diff, the rule returns a `hookproto.HookResult` with `DecisionDeny` and a human-readable reason explaining the detection.

### Converting Results to Hook Responses

The `PreToolToOutput` function (lines 195-214 in [`go/internal/guardrail/pre_tool.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/guardrail/pre_tool.go)) converts the `HookResult` into an official JSON payload conforming to the `PreToolOutput` structure defined in [`go/pkg/hookproto/types.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/pkg/hookproto/types.go). The resulting JSON includes `"permissionDecision":"deny"` and `"permissionDecisionReason":"hardcoded secret …"`.

### Hook Wiring and Execution

The PreToolUse hook is declared in [`hooks/hooks.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/hooks/hooks.json) with a matcher covering `"Write|Edit|MultiEdit|Bash|Read"` operations. When triggered, it executes [`scripts/pretooluse-guard.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/scripts/pretooluse-guard.sh), which reads the JSON response and aborts the tool execution with exit code 2 if a denial decision is returned. This prevents the secret from ever being committed to the repository.

## The Secret Detection Regex Pattern

The core detection logic resides in the regex definition within rule R09 in [`go/internal/guardrail/rules.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/guardrail/rules.go):

```go
{
    ID:          "R09:warn-secret-file-read",
    Description: "Detect hard‑coded secret‑like assignments",
    // Matches: api_key = "....", secret: '....', token = "....", etc.
    Regex:       `(?i)\b(api[_-]?key|secret|token|password|passwd|client[_-]?secret)\b[^:=\n]{0,20}[:=][[:space:]]*['"][^'"]{8,}['"]`,
    Decision:    DecisionDeny,
}

```

This pattern is case-insensitive and looks for common secret identifiers including `api_key`, `secret`, `token`, `password`, `passwd`, and `client_secret`. The regex allows up to 20 characters between the identifier and the assignment operator (`=` or `:`), and requires a quoted value of at least 8 characters. This minimum length requirement filters out trivial false positives while catching real credentials.

## Code Examples

### Blocked Change with Hardcoded API Key

Consider a modification to [`src/config.js`](https://github.com/Chachamaru127/claude-code-harness/blob/main/src/config.js) that attempts to hardcode sensitive credentials:

```diff
--- a/src/config.js
+++ b/src/config.js
@@
-// TODO: inject from environment
-const API_KEY = "abcd1234efgh5678ijkl9012mnop3456";
+const API_KEY = process.env.API_KEY;   // ✅ fixed

```

When running `claude-code write src/config.js`, the PreToolUse guardrail triggers and returns a denial response:

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "hardcoded secret assignment (API_KEY)",
    "additionalContext": null
  }
}

```

The [`pretooluse-guard.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/pretooluse-guard.sh) script receives this JSON and exits with code 2, aborting the write operation before the secret enters the repository.

### Allowed Change Without Secrets

A modification containing no secret patterns proceeds normally:

```diff
--- a/src/util.js
+++ b/src/util.js
@@
-const DEBUG = true;
+const DEBUG = false;

```

The guardrail returns an allowance response:

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow"
  }
}

```

The tool execution continues without interruption.

## Summary

- The PreToolUse guardrail operates before any tool execution, intercepting Write, Edit, Bash, Read, and MultiEdit operations as configured in [`hooks/hooks.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/hooks/hooks.json).
- Detection relies on rule **R09:warn-secret-file-read** in [`go/internal/guardrail/rules.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/guardrail/rules.go), which uses a case-insensitive regex to identify hardcoded credentials.
- The system analyzes only the actual diff of changes rather than scanning the entire repository, ensuring fast performance with minimal false positives.
- Upon detection, the guardrail returns a JSON response with `permissionDecision: deny`, causing [`scripts/pretooluse-guard.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/scripts/pretooluse-guard.sh) to exit with code 2 and abort the operation.
- All secret detection logic is declarative and centralized in Go structs, making pattern updates straightforward without modifying the underlying hook infrastructure.

## Frequently Asked Questions

### What tools does the PreToolUse guardrail monitor?

The guardrail monitors all file-modifying tools including Write, Edit, MultiEdit, Bash, and Read operations. This coverage is defined in the matcher field of [`hooks/hooks.json`](https://github.com/Chachamaru127/claude-code-harness/blob/main/hooks/hooks.json), ensuring comprehensive protection across every interface that could introduce hardcoded secrets into the repository.

### How does the regex avoid false positives on short strings?

The regex pattern in rule R09 requires a minimum quoted value length of 8 characters (`[^'"]{8,}`) and restricts the match to specific secret-related keywords like `api_key`, `secret`, and `token`. This design filters out common variable names like `secret = true` or short placeholders while catching actual credential material.

### Can I customize the secret detection patterns?

Yes, because the guardrail uses a declarative rule system defined in [`go/internal/guardrail/rules.go`](https://github.com/Chachamaru127/claude-code-harness/blob/main/go/internal/guardrail/rules.go). You can modify the regex field in the R09 rule definition or add additional rules with different pattern IDs to extend detection for specific credential types used in your organization.

### What happens when a secret is detected?

When the `EvaluatePreTool` function identifies a hardcoded secret, it returns a `DecisionDeny` result that `PreToolToOutput` converts to a JSON response with `"permissionDecision":"deny"`. The [`pretooluse-guard.sh`](https://github.com/Chachamaru127/claude-code-harness/blob/main/pretooluse-guard.sh) script interprets this response and exits with code 2, causing the Claude runtime to abort the tool operation immediately before any file changes are persisted.