Agent Hooks
What Are Agent Hooks
AI coding agents interact with your codebase in two directions: they read files to understand context, and they write patches and run shell commands to act on it. Both directions leak secrets. A file the agent reads can carry a credential into conversations, logs, and generated snippets; a patch or a shell command the agent writes can put a fresh credential into your repository.
sekretbarilo integrates into the agent’s tool pipeline. Blocking hooks run before a tool call executes:
- The agent triggers a sekretbarilo hook
- sekretbarilo scans the file, the patch, or the command
- If secrets are found, the tool call is blocked and the agent is told why
- If it is clean, the tool call proceeds normally
| Agent / mode | Coverage | Event / tools | Hook command |
|---|---|---|---|
Claude Code block |
blocks file reads | PreToolUse / Read |
<absolute-path-to-running-sekretbarilo> check-file --stdin-json |
Claude Code redact |
masks successful text results | PostToolUse / Bash, Read, Grep |
<absolute-path-to-running-sekretbarilo> redact-claude --stdin-json |
| Codex CLI | blocks patches and shell commands | PreToolUse / apply_patch, Bash |
sekretbarilo check-codex --stdin-json |
In Claude redact mode, the tool executes normally and sekretbarilo edits its result in memory before it reaches the model. Coverage depends on the selected tools, detectors, and the hook runtime; see the limitations for each integration below.
Claude Code Integration
Claude Code is an official CLI tool from Anthropic that brings Claude AI directly into your development workflow. sekretbarilo integrates with Claude Code through its hooks system:
Block Mode
block is the default for a new installation. Hook configuration:
- Hook type:
PreToolUse(triggered before the Read tool executes) - Tool matcher:
Read(applies to file read operations) - Command:
<absolute-path-to-running-sekretbarilo> check-file --stdin-json - Timeout: 10 seconds
- Status message: “Scanning file for secrets…”
When Claude Code is about to read a file, it automatically calls sekretbarilo, sends the file path as JSON on stdin, and waits for the scan result. A clean file (exit code 0) allows the read to proceed; a blocked file (exit code 2) prevents Claude Code from accessing the content.
Redact Mode: Output Editor
Install with sekretbarilo install agent-hook claude --mode redact. This mode requires Claude Code 2.1.121 or later, which introduced replacement of built-in tool output through hookSpecificOutput.updatedToolOutput. The installer checks the installed version before modifying Claude settings; an unknown or unsupported version leaves the previous protection in place. See the Claude hook contract and 2.1.121 release notes.
The hook is synchronous, runs on successful PostToolUse events, matches ^(Bash|Read|Grep)$, and has a 10-second timeout. Editor means editing the returned text in memory. The tool runs normally and source files are unchanged; this mode does not edit files on disk or prevent a Bash command’s side effects.
{
"hooks": {
"PostToolUse": [
{
"matcher": "^(Bash|Read|Grep)$",
"hooks": [
{
"type": "command",
"command": "<absolute-path-to-running-sekretbarilo> redact-claude --stdin-json",
"timeout": 10
}
]
}
]
}
}
The scanner processes supported text inside tool_response, using the tool output schemas:
| Tool | Text scanned |
|---|---|
Bash |
stdout, stderr, and text content blocks |
Read |
Text-file content; images, PDFs, and notebooks are outside this version’s scope |
Grep |
Returned content, result lines, and filename arrays across content, files_with_matches, and count modes; counters are preserved |
Only supported text fields change. The replacement preserves the original JSON structure, service fields, and unknown metadata, because Claude falls back to the original output when replacement validation fails. Unknown metadata is preserved, not scanned as arbitrary text.
For example, a Bash response containing host = localhost, a detected password assignment, and port = 5432 becomes:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"updatedToolOutput": {
"stdout": "host = localhost\npassword = \"[REDACTED]\"\nport = 5432\n",
"stderr": "",
"interrupted": false
}
}
}
The whole captured secret value becomes [REDACTED]; no prefix or suffix remains. Repeated findings are masked and overlapping ranges are merged. Surrounding text and UTF-8 are preserved, as are CR, LF, and CRLF line endings, including line breaks inside a multiline secret. PEM and PGP headers extend the masked range through the corresponding end marker. If the end marker is missing, masking covers the remainder of that text field. Public-key blocks receive the same treatment when detect_public_keys is enabled. With public-key detection disabled, an unterminated public-key header does not suppress scanning of the remaining output.
redact uses the current rules, custom capture groups, entropy thresholds, password heuristics, stopwords, and per-rule value exceptions through the trusted configuration loader. Path allowlists, audit path exclusions, and documentation relaxations do not apply to tool output. An .env file is scanned by its returned content, rather than blocked by its filename. This mode includes a bounded generic high-entropy-value detector for long, high-entropy, non-whitespace values regardless of variable name; it does not search for every random-looking string.
Environment dumps and similar tool output also redact eligible high-entropy values regardless of the variable name. Some harmless high-entropy data, such as base64 blobs or checksums, may be redacted; use [allowlist].stopwords or a per-rule [[allowlist.rules]] regexes exception to suppress a specific case. An in-workspace .sekretbarilo.toml exception must be committed before hooks honor it; see Config Inside the Repository Must Be Committed.
Output and Errors
sekretbarilo redact-claude --stdin-json requires the flag and reads the hook payload from stdin. A clean result exits 0 with no stdout. A masked result exits 0 with replacement JSON on stdout.
Input and the complete serialized hook response are each limited to 10 MiB (10,485,760 bytes). Parsing, configuration, scanning, and size errors return exit 0 with continue: false and a fixed safe stopReason. When the response structure is available and the fallback fits the limit, the error response also replaces all supported text with redacted text. Diagnostics never include original secret values. If stdout cannot accept the JSON, the command exits 1 with a fixed safe stderr message when that channel is writable. Closed output channels are handled without panicking, but a response that cannot be delivered cannot provide a masking guarantee.
Exit 2 is not a way to remove PostToolUse output: the tool has already executed. Stopping continuation is also not a substitute for a valid replacement when the original result exists.
Limits of This Version
- Only successful
PostToolUseresults fromBash, textRead, andGrepare supported.PostToolUseFailuredoes not offer this replacement contract. - MCP tools, images, PDFs, notebooks, and results delivered through other tools are outside scope. There is no separate command to edit source files.
- Redaction does not remove original data from tool-side telemetry or other storage outside the model-facing replacement.
- A hook crash, timeout, failure to deliver output, or another hook replacing the same result can leave the original output exposed. These cases are outside the masking guarantee. Claude’s fallback for an invalid replacement also matters; preserving response structure is required.
- Detection is rule-based and uses configured value exceptions. A secret not detected by those rules is not masked.
- Password assignments support double quotes, single quotes, backticks, and unquoted values. Unquoted captures end at whitespace or a syntax delimiter; escaped whitespace and delimiters remain part of the value. Quote values that contain literal delimiters. Existing password-strength and placeholder filters still apply.
- Ambiguous raw versus escaped quotes can mask adjacent text: a backslash before a possible closing single quote or backtick favors the longer escaped interpretation. Password assignments with multiline or mixed shell quoting are not parsed. See password rules for detection boundaries.
These boundaries follow the Claude hooks lifecycle and failure behavior.
Synthetic Smoke Check
Verified with the sekretbarilo 0.6.0 release build and Claude Code 2.1.261 on 2026-09-05: a synthetic AWS key and passwords without quotes and in double, single, and backtick quotes were masked in the model-visible results of Bash, text Read, and Grep content mode. The source file remained unchanged. Other result shapes and error cases are covered by the automated hook tests.
Use a disposable project and a synthetic credential that matches an enabled detector. Keep the synthetic value out of the prompt so the model cannot recover it from the request itself.
- Check
claude --version, install with--mode redact, and runsekretbarilo doctor. Resolve any local/global blocking-hook conflict. - Create a text fixture containing safe surrounding lines and the synthetic credential, and record its checksum outside Claude.
- In a fresh Claude session, ask it to inspect the fixture separately with
Bash(cat),Read, andGrepin content mode. Check that each tool was actually used and the model receives[REDACTED]with the safe lines intact. - Repeat with Bash output on stderr, Grep’s file/count modes, repeated secrets, and a multiline key fixture. Inspect the actual tool results, not only the model’s final paraphrase.
- Compare the fixture checksum afterward. The file must be unchanged.
Codex CLI Integration
Codex CLI is OpenAI’s terminal coding agent. It has a hooks system of its own, and sekretbarilo integrates with it through a PreToolUse hook:
Hook Configuration:
- Hook type:
PreToolUse(triggered before the matched tool executes) - Tool matcher:
^(apply_patch|Bash)$— the matcher is a regex, not a literal tool name - Command:
sekretbarilo check-codex --stdin-json - Timeout: 10 seconds
- Status message: “Scanning tool input for secrets…”
- Config file:
hooks.json, global or project-local (see Where the Configuration Lives)
Where the Claude Code hook guards the read direction, the Codex hook guards the write direction: it inspects the changes the agent is about to apply and the shell commands it is about to run. You never invoke check-codex yourself — Codex calls it and sends the PreToolUse payload on stdin.
--stdin-json is mandatory for check-codex. The bare command has no other input to read, so it refuses rather than guessing:
$ sekretbarilo check-codex
[ERROR] check-codex reads its payload from stdin and requires --stdin-json
The command the installer writes already includes the flag, so an existing installation needs no change.
Two things about this integration are easy to get wrong, and both are covered below: an installed hook does not run until you approve it (see Hook Trust), and the hook cannot stop Codex from reading a file (see Limitations).
What the Hook Covers
apply_patch — sekretbarilo parses the patch and scans the lines being added. Context lines and removed lines are parsed and then discarded, so deleting an existing secret never blocks the patch. Target paths that cannot hold a meaningful secret are skipped, using the same set check-file uses: binary extensions, vendor directories, lock files, generated files, and anything matching your configured path patterns.
A patch that only renames a file — *** Update File: followed by *** Move to: with no change lines — adds nothing, so it is accepted.
The .env policy is checked first, before any configuration is loaded. A patch writing to a .env file is blocked outright regardless of its content, and no allowlist entry can override it — the same rule the pre-commit hook and check-file enforce. For a move, it is enough that either the original path or the destination is a blocked .env name, so renaming a file into .env is blocked even though the patch itself carries no added lines. The safe templates .env.example, .env.sample, and .env.template remain allowed.
Bash — sekretbarilo scans the raw command string. This catches the usual ways a secret ends up on a command line:
# an exported credential
export AWS_SECRET_ACCESS_KEY=...
# a bearer token in a request header
curl -H "Authorization: Bearer ..." https://api.example.com/v1/status
# a heredoc writing secrets into a file
cat > config.yml << 'EOF'
api_key: ...
EOF
Blocking
The hook blocks by exiting with code 2 and writing the reason to stderr. Codex surfaces that reason to the model, so the agent learns why the patch or command was refused and can correct itself instead of retrying blindly. Secret values in the reason are masked (first two and last two characters), exactly as in every other sekretbarilo output.
| Exit Code | Meaning | Codex Action |
|---|---|---|
| 0 | no secrets in the patch or command | Allow the tool call |
| 2 | secrets found, .env target, or error |
Block the tool call |
A secret block looks like this:
[AGENT] Codex apply_patch blocked: secret(s) detected
file: config.py
line: 1
rule: aws-access-key-id
match: AK****************FG
file: config.py
line: 2
rule: aws-secret-access-key
match: wJ************************************+a
apply_patch action blocked to prevent secret exposure. total findings: 2.
A Bash block has the same shape, with Bash in place of apply_patch and the pseudo-path <bash-command> instead of a file. A .env block is shorter, because no scanning took place:
[AGENT] Codex apply_patch blocked by .env policy
file: .env
.env files may contain environment secrets; writing was blocked.
The Reason Is Capped at 20 Findings
A patch that adds a hundred credentials would otherwise produce a hundred-entry reason string, which is then injected into the model’s context. Only the first 20 findings are rendered; the rest are summarized:
... (20 findings)
... and 5 more finding(s) omitted
apply_patch action blocked to prevent secret exposure. total findings: 25.
The cap affects the reason text only. The decision is still a block, and the total findings: count on the last line is the true total — that line is present on every secret block, capped or not.
Attacker-Controlled Strings Are Sanitized
The file paths and rule names in the reason come from the patch, which the agent wrote. They pass through the same control-character and bidirectional-override stripping the audit output uses before being printed. A path such as spoof\u001b[2K\rname\u202e.rs is reported as spoof[2Kname.rs: the escape, the carriage return, and the right-to-left override are gone.
This matters more here than elsewhere, because the reason has two audiences that both interpret escape sequences — the terminal it is printed to, and the model whose context it lands in. A rule name that can move the cursor is a rule name that can rewrite what the block appears to say.
Payload Size
The hook reads at most 10 MiB from stdin. An oversized PreToolUse payload is rejected and the tool call blocked, rather than scanned in part — the same fail-closed choice made everywhere else:
Codex hook payload truncated: input exceeds 10485760 bytes
Claude block mode (check-file) has a smaller 1 MB cap because it receives a file path. Claude redact mode limits both its input and serialized response to 10 MiB.
Where the Configuration Lives
sekretbarilo writes the hook into a hooks.json file:
| Scope | Path |
|---|---|
Global (--global) |
$CODEX_HOME/hooks.json, defaulting to ~/.codex/hooks.json when CODEX_HOME is unset |
| Project-local (default) | .codex/hooks.json in the repository root |
Layers are additive: when a global hook and a project hook are both present, Codex runs both. This is different from pre-commit hooks, where a local hook overrides the global one.
This is what sekretbarilo writes:
{
"hooks": {
"PreToolUse": [
{
"matcher": "^(apply_patch|Bash)$",
"hooks": [
{
"type": "command",
"command": "sekretbarilo check-codex --stdin-json",
"timeout": 10,
"statusMessage": "Scanning tool input for secrets..."
}
]
}
]
}
}
Points worth knowing if you ever hand-edit the file:
- The root object accepts exactly two keys,
hooksanddescription, and rejects anything else. An unrecognised top-level key makes Codex drop that layer’s hooks entirely — with only a log warning and nothing visible at the point of use. A typo at the root silently disarms every hook in that file. - Event keys are PascalCase (
PreToolUse). timeoutis in seconds, and defaults to 600 when omitted.statusMessageis camelCase.matcheris a regex and is optional; omitting it matches every tool.- It is standard JSON: no comments, no trailing commas.
Codex CLI 0.145.0 has no hooks list or hooks validate subcommand, so there is no way to ask Codex whether it accepted your file. It does have a codex doctor, but that diagnoses installation, config, auth, and runtime health — it does not look at hooks.json. sekretbarilo doctor is the check available.
Codex also accepts a second, equivalent representation of the same hooks — a [hooks] table in the config.toml of the same layer. sekretbarilo deliberately writes only hooks.json and never touches config.toml. If you already keep hooks in config.toml, expect to see a second hook definition appear in hooks.json; that is the sekretbarilo one. When both representations exist in a single layer, Codex loads both and prints a warning.
Hook Trust
Codex does not run a newly installed hook until you approve it. An unapproved hook is skipped silently — no error, no warning at the point of use — so the installation looks complete while nothing is actually being scanned. This is the single most common reason for “I installed the hook and it never fires”.
Approve the hook from the Codex TUI:
/hooks
sekretbarilo does not write the trust state for you, and this is deliberate:
- the trust hash is an internal, undocumented Codex implementation detail — writing it means guessing at a format that can change in any release;
- a security tool that grants itself trust defeats the purpose of the trust model. The approval has to come from a human.
Codex records the approval in a [hooks.state] table inside the config.toml of the user layer, keyed by source file path, event, and index.
For non-interactive environments where no one can answer a prompt — CI jobs, containers, automation — Codex offers --dangerously-bypass-hook-trust, which runs hooks without approval. It disables the trust check for every hook in that session, not just sekretbarilo’s, so use it only where you fully control the hook configuration. Interactively, approve through /hooks instead.
Version Requirements
The integration is verified against codex-cli 0.145.0. Older releases may not deliver PreToolUse for apply_patch. If patches are being applied without ever reaching sekretbarilo, upgrade Codex CLI before debugging anything else.
Limitations
The Codex hook is a narrower instrument than the Claude Code hook. Know what it does not do:
- No read protection. Codex’s hook surface has no equivalent of Claude Code’s
Readtool, so there is no way to stop the agent from reading a file that contains a secret. This is an upstream capability gap, not a sekretbarilo choice. - The
Bashcheck is textual. The command string is scanned as text. sekretbarilo does not parse shell syntax, does not expand variables, and does not analyse redirect targets, so the check can be circumvented deliberately. Treat it as a guardrail against accidental leakage, not as a sandbox. - Write direction only. Coverage is
apply_patchandBash. Nothing else Codex does is intercepted.
Installation
Install agent hooks using the install command, one agent at a time. Each hook can be installed locally (per-project) or globally (for all projects).
Project-Local Installation (Claude Code)
Install in the current project’s .claude/settings.json:
sekretbarilo install agent-hook claude
This creates or modifies ./.claude/settings.json in your project root. The hook only applies when Claude Code is run from this project.
Global Installation (Claude Code)
Install in your user-level ~/.claude/settings.json:
sekretbarilo install agent-hook claude --global
This applies the hook to all projects where Claude Code runs under your user account.
Selecting and Switching Claude Modes
sekretbarilo install agent-hook claude --mode redact
sekretbarilo install agent-hook claude --mode block
sekretbarilo install agent-hook claude --mode redact --global
sekretbarilo install agent-hook claude --settings .claude/settings.local.json --mode redact
Without --mode, installation preserves the mode already present in the selected settings file; a new installation uses block. install all follows the same rule and accepts --mode block|redact for its Claude step. --mode does not change Codex behavior.
Switching modes replaces only sekretbarilo’s handlers in the selected file, atomically and without duplicates. Other hooks and their order are preserved. It does not switch hooks in another settings scope. A global blocking Read hook can still block a read before a local redaction hook gets any result, and the reverse scope combination has the same issue. Installation and doctor report this conflict; choose the intended mode explicitly in each affected scope.
Targeting an explicit file. --settings <path> installs into exactly that file instead of the local or global default, and is mutually exclusive with --global. A relative path resolves against the current directory of the invocation, not the repository root, and the flag works outside a git repository too; the file is created if absent, and existing content and other hooks are preserved exactly as with the default locations. Scope-conflict warnings from installation and doctor treat the explicit file as one more scope alongside local and global.
Writing into an arbitrary file does not register a new Claude Code profile by itself: Claude only loads a settings file when it is one of its standard locations, when Claude is launched with its own --settings <path> flag, or when the file is the settings.json of the profile directory named by CLAUDE_CONFIG_DIR. See the Claude Code CLI reference and the configuration directory docs. sekretbarilo doctor --settings <path> inspects the file’s contents; it is not proof that a running Claude Code session has actually loaded it.
Project-Local Installation (Codex CLI)
Install in the current project’s .codex/hooks.json:
sekretbarilo install agent-hook codex
This creates or modifies .codex/hooks.json in your repository root. The hook only applies when Codex CLI runs in this project.
Global Installation (Codex CLI)
Install in your user-level Codex configuration directory:
sekretbarilo install agent-hook codex --global
This writes $CODEX_HOME/hooks.json — ~/.codex/hooks.json when CODEX_HOME is unset — and applies to every project where Codex CLI runs under your user account.
Installing is not enough. Codex will not run the hook until you approve it with
/hooksin the Codex TUI, and it says nothing when it skips an unapproved hook. See Hook Trust.
Install All Hooks at Once
To install the pre-commit hook and every supported agent hook in one command:
# install locally (project pre-commit + project agent hooks)
sekretbarilo install all
# install globally (global pre-commit + global agent hooks)
sekretbarilo install all --global
install all covers the pre-commit hook, the Claude Code hook, and the Codex CLI hook, in that order, reporting each one as it goes.
Only the Codex step is conditional. Codex is looked for on PATH and at $CODEX_HOME (default ~/.codex); when neither is present the step is skipped rather than failing:
installing codex cli agent hook...
[SKIP] codex cli not detected on this machine; skipping codex agent hook install
In block mode, the Claude settings file can be installed even when Claude Code is absent. Selecting or preserving redact requires a known supported Claude version before the Claude settings can change.
Remember to approve the Codex hook afterwards with /hooks.
How It Works (Claude Code Block Pipeline)
The following pipeline, path policies, and check-file examples describe Claude block mode. For tool-output masking, see Redact Mode.
1. Hook Trigger
Claude Code is about to execute the Read tool to read a file. The PreToolUse hook fires, invoking:
sekretbarilo check-file --stdin-json
2. JSON Payload
Claude Code sends a JSON payload on stdin with the file path and optional working directory:
{
"tool_input": { "file_path": "path/to/file" },
"cwd": "/optional/working/directory"
}
3. Path Resolution
sekretbarilo parses the JSON, extracts the file path, and resolves it:
- Absolute paths are converted to relative paths when possible (using
cwdcontext) - Relative paths are resolved against
cwdor the current directory - Path traversal attempts (e.g.,
../../etc/passwd) are rejected
4. Fast-Path Check: Binary Files, Vendor Dirs, Lock Files
Before scanning, sekretbarilo checks if the file is one that cannot contain readable secrets:
Binary extensions (images, executables, archives):
.png, .jpg, .jpeg, .gif, .bmp, .svg, .ico, .webp
.pdf
.exe, .dll, .so, .dylib
.zip, .tar, .gz, .bz2, .7z, .rar, .xz
.mp3, .mp4, .avi, .mov, .wav, .webm, .ogg
.woff, .woff2, .ttf, .eot, .otf
.min.js, .min.css
Vendor directories (dependencies, generated code):
node_modules/, vendor/, .bundle/, bower_components/
__pycache__/, .git/
Lock files (package manifests, checksums):
package-lock.json, yarn.lock, pnpm-lock.yaml
Cargo.lock, go.sum, Gemfile.lock, poetry.lock
composer.lock, Pipfile.lock
If the file matches any fast-path pattern, sekretbarilo returns exit code 0 immediately without reading the file. This avoids unnecessary scanning overhead for files that pose no secret risk.
5. .env File Blocking
Files matching the .env pattern are always blocked unconditionally, regardless of content:
Blocked:
.env
.env.local
.env.production
.env.development
.env.staging
.env.test
Allowed (safe templates):
.env.example
.env.sample
.env.template
.env files almost always contain secrets (API keys, database passwords, tokens). Rather than scan them, sekretbarilo blocks them outright to prevent any possibility of exposure.
6. Full Scanning
If the file passes fast-path checks and isn’t a .env file, sekretbarilo reads it and runs the full detection engine:
- Aho-corasick keyword pre-filter identifies candidate rules
- Regex matching extracts potential secrets
- Shannon entropy analysis filters low-randomness strings
- Hash detection skips known hash formats (SHA-1, SHA-256, MD5, git commits)
- Stopword filtering removes known-safe values like
example,test,placeholder - Variable reference detection skips patterns like
${VAR},process.env.VAR
7. Exit Code
sekretbarilo returns an exit code to Claude Code:
| Exit Code | Meaning | Claude Code Action |
|---|---|---|
| 0 | Clean (no secrets found, or file skipped via fast-path) | Allow read |
| 2 | Secrets found, or error (file not found, JSON parse error, config failure) | Block read |
Exit code 2 is used for both secrets and errors to block the Read call through PreToolUse.
Stdin JSON Payload
In block mode, Claude Code sends a JSON payload on stdin when the hook is triggered. check-file parses this payload to extract the file path and working directory.
Schema
{
"tool_input": { "file_path": "path/to/file" },
"cwd": "/optional/working/directory"
}
Fields
| Field | Required | Description |
|---|---|---|
tool_input.file_path |
Yes | Path to the file Claude Code wants to read (absolute or relative) |
cwd |
No | Working directory context (used to resolve relative paths and vendor dirs) |
Example Payloads
Absolute path with cwd:
{
"tool_input": { "file_path": "/home/user/project/src/config.py" },
"cwd": "/home/user/project"
}
Relative path:
{
"tool_input": { "file_path": "src/config.py" },
"cwd": "/home/user/project"
}
Absolute path without cwd:
{
"tool_input": { "file_path": "/home/user/project/src/config.py" }
}
Extra Fields
sekretbarilo tolerates extra fields in the JSON payload and ignores them. This ensures forward compatibility if Claude Code adds new fields in the future:
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Read",
"tool_input": { "file_path": "src/config.py" },
"cwd": "/home/user/project"
}
Size Limit
stdin input is limited to 1 MB to prevent unbounded memory consumption. This is more than sufficient for JSON payloads containing file paths.
Fast-Path Skipping
Fast-path skipping applies to check-file / Claude block mode and file-based scans, not redact. It allows files considered low-risk by the configured path policy to pass through without scanning.
Why Fast-Path Skipping Matters
- Performance: skipping binary files avoids reading megabytes of image/video data
- Accuracy: scanning binary content produces garbage matches (false positives)
- Usability: AI agents need to read lock files, images, and dependencies without friction
Fast-path decisions are made based on file path patterns only, before the file is read. This keeps the check extremely fast.
Binary Files
Binary files cannot contain readable secrets in a form that matters for leakage. The complete list:
images .png, .jpg, .jpeg, .gif, .bmp, .svg, .ico, .webp
documents .pdf
executables .exe, .dll, .so, .dylib
archives .zip, .tar, .gz, .bz2, .7z, .rar, .xz
media .mp3, .mp4, .avi, .mov, .wav, .webm, .ogg
fonts .woff, .woff2, .ttf, .eot, .otf
generated .min.js, .min.css
Even if a binary file somehow embeds a secret (e.g., an API key in image metadata), it’s not accessible to the AI agent in a way that creates risk.
Extensions outside this list are scanned, including ones that are usually binary — .o, .class, .jar, .wasm, .db, .sqlite, .tgz, .docx, .flac. Scanning a compressed or binary file is cheap and finds nothing, so the fast path stays deliberately short rather than trying to enumerate every binary format in existence. Add your own entries under [allowlist] paths if a particular format shows up often enough to matter.
Vendor Directories
Vendor directories contain third-party dependencies that are not part of your codebase:
node_modules/
vendor/
.bundle/
bower_components/
__pycache__/
.git/
These directories often contain thousands of files. Scanning them would:
- Slow down the agent significantly
- Produce false positives from test data in dependencies
- Block legitimate reads of library code
sekretbarilo skips these paths entirely.
Lock Files
Lock files are package manifests and checksums, not source code:
package-lock.json
yarn.lock
pnpm-lock.yaml
Cargo.lock
go.sum
Gemfile.lock
poetry.lock
composer.lock
Pipfile.lock
Lock files are often large (thousands of lines) and contain cryptographic hashes that can trigger false positives. They never contain user secrets, so they’re safe to skip.
User-Configured Patterns
In addition to built-in patterns, sekretbarilo respects user-configured allowlists and audit exclude patterns from .sekretbarilo.toml:
[allowlist]
paths = ["test/fixtures/.*", "docs/examples/.*"]
[audit]
exclude_patterns = ["^build/", "^dist/"]
These patterns are evaluated during the fast-path check, so you can customize which files are allowed through without scanning.
.env File Blocking
.env files are a special case: they are always blocked, regardless of content.
Why Block .env Files?
.env files are designed to store secrets. They typically contain:
- Database passwords
- API keys
- Service tokens
- Encryption keys
- OAuth secrets
Allowing an AI agent to read a .env file is almost always a mistake. Even if the file happens to be clean at the moment, it’s likely to contain secrets in the future.
Blocked Patterns
.env
.env.local
.env.production
.env.development
.env.staging
.env.test
.env.ci
Any file whose name matches these patterns is blocked with exit code 2.
Allowed Template Files
Template files are not blocked, because they contain placeholder values:
.env.example
.env.sample
.env.template
These files are safe for AI agents to read because they document the expected structure without exposing real secrets.
Output When Blocked
When sekretbarilo blocks a .env file, it writes a message to stderr:
[AGENT] .env file blocked: /home/user/project/.env
file likely contains environment secrets. reading blocked.
Claude Code will see this message and inform you that the file cannot be read.
Output Format
When check-file detects secrets, it writes diagnostic output to stderr (not stdout) with an [AGENT] prefix. This ensures Claude Code can display the error to you.
Clean File (Exit 0)
No output. The file is allowed through silently.
Secrets Detected (Exit 2)
[AGENT] secret(s) detected in src/config.py
file: src/config.py
line: 1
rule: aws-access-key-id
match: AK****************FG
file: src/config.py
line: 2
rule: stripe-secret-key-live
match: sk*******************************ij
file contains 2 secret(s). reading blocked to prevent secret exposure.
Output Fields
- file: path of the scanned file
- line: line number where the secret was found
- rule: which detection rule matched (helps identify the secret type)
- match: partially redacted secret (first 2 and last 2 characters visible)
One line can produce more than one finding when several rules match it — a Stripe key, for example, matches both the specific stripe-secret-key-live rule and the catch-all generic-api-key.
.env File Blocked
[AGENT] .env file blocked: .env
file likely contains environment secrets. reading blocked.
Error (Exit 2)
Errors (file not found, JSON parse failure, config load failure) also produce stderr output and exit with code 2:
[ERROR] failed to read /path/to/missing.py: No such file or directory
This fail-closed behavior ensures that errors don’t accidentally allow secrets through.
Using check-file Directly
The check-file command can be used standalone, outside of the Claude Code hook context. This is useful for testing, CI pipelines, or integrating with other tools.
Scan a File by Path
sekretbarilo check-file src/config.rs
Exit code 0 = clean, exit code 2 = secrets found.
Read File Path from Stdin JSON (Agent Hook Mode)
Simulate the Claude Code hook payload:
echo '{"tool_input":{"file_path":"src/config.rs"}}' | sekretbarilo check-file --stdin-json
This is the same mode used by the agent hook.
Example: CI Pipeline
Run check-file on all source files in CI:
#!/bin/sh
# scan all python files for secrets
for file in $(find src -name '*.py'); do
sekretbarilo check-file "$file"
if [ $? -eq 2 ]; then
echo "secret detected in $file"
exit 1
fi
done
echo "all files clean"
Example: Pre-Read Script
Use check-file in a script before opening files in an editor:
#!/bin/sh
# check file before opening in vim
sekretbarilo check-file "$1"
if [ $? -eq 2 ]; then
echo "file contains secrets. open anyway? (y/n)"
read -r answer
if [ "$answer" != "y" ]; then
exit 1
fi
fi
vim "$1"
Configuration
check-file, check-codex, and redact-claude load hierarchical configuration from .sekretbarilo.toml files through the trusted loader. Custom rules, stopwords, value exceptions, and entropy thresholds apply across the hooks. Path policy differs by command, as described below.
Config Inside the Repository Must Be Committed
The agent hooks — and only the agent hooks — require a .sekretbarilo.toml inside the git working tree to be git-tracked and unmodified relative to HEAD. An untracked or dirty in-workspace config is dropped whole, with one line on stderr:
[WARN] ignoring untrusted in-workspace config: /home/user/project/.sekretbarilo.toml
The reason is the obvious attack: an agent that can write files can write a permissive config, and that patch carries no secret, so it passes. Requiring a commit puts a human between the agent and the rules the agent is checked against.
Layers above the repo root — a parent directory, ~/.sekretbarilo.toml, the XDG user config, /etc/sekretbarilo.toml — are loaded normally, and scan/audit are not affected at all. If a value exception works under sekretbarilo audit but the hook still detects it, check whether the config is committed. Full detail in Configuration.
Path Allowlists and Tool Text
Path allowlists do not apply to Codex Bash command text or any Claude redact output. [allowlist] paths and per-rule paths cannot suppress findings in those text modes. Stopwords, per-rule value regexes, entropy thresholds, and detect_public_keys still apply. Path allowlists work normally for apply_patch target paths and check-file. Redaction also ignores audit path exclusions and documentation-specific relaxations.
Hierarchical Config Discovery
Config files are loaded in priority order (highest priority last):
/etc/sekretbarilo.toml(system-wide)~/.config/sekretbarilo/sekretbarilo.toml(user-level)~/.sekretbarilo.toml(home directory)- Parent directories from
$HOMEdown to project root .sekretbarilo.tomlin project root (highest priority)
Settings are merged across all levels. Scalar values (like entropy_threshold) use the most local value; lists (like allowlist.paths) are concatenated.
Allowlists
In check-file and Codex patch scanning, path allowlist patterns apply during fast-path checks:
[allowlist]
paths = ["test/fixtures/.*", "docs/examples/.*"]
stopwords = ["my-safe-token"]
Files matching allowlist.paths patterns are allowed through without scanning in those file-based modes. redact still scans their returned text.
Audit Exclude Patterns
Audit exclude patterns also apply to file-based agent scanning, but not redact:
[audit]
exclude_patterns = ["^build/", "^vendor/"]
Files matching these patterns are skipped.
Custom Rules
Custom detection rules are loaded and applied:
[[rules]]
id = "internal-api-key"
description = "Internal service API key"
regex = "(MYCO_[A-Z0-9]{32})"
secret_group = 1
keywords = ["myco_"]
Entropy Threshold
Override the entropy threshold globally or per-rule:
[settings]
entropy_threshold = 4.0
Example: Project-Specific Stopwords
Add project-specific stopwords to reduce false positives:
[allowlist]
stopwords = ["project-test-token", "known-safe-key"]
These stopwords apply to both pre-commit scanning and agent hooks.
Idempotent Installation
Running an install agent-hook command multiple times is safe. Both installers detect an existing hook and report one of three outcomes.
First Install
$ sekretbarilo install agent-hook claude
[OK] created claude code hook configuration
Writes the hook to .claude/settings.json.
Second Install (Already Installed)
$ sekretbarilo install agent-hook claude
[OK] sekretbarilo already installed in claude code hooks
No changes are made when the existing handler is current. Its mode is preserved when --mode is omitted.
Upgrade (Outdated Command Detected)
If an older version of sekretbarilo installed a hook with a different command format, the new installer updates it in place:
$ sekretbarilo install agent-hook claude
[OK] updated claude code hook configuration
The installer writes the absolute path reported by the OS for the running executable, followed by check-file --stdin-json for block or redact-claude --stdin-json for redact, and quotes shell-sensitive paths. On macOS, an invocation through a symlink such as Homebrew’s /usr/local/bin/sekretbarilo keeps that symlink path. On Linux, current_exe reports the resolved target, so Homebrew-on-Linux records a Cellar path; brew upgrade then breaks the hook when that target disappears, until sekretbarilo install agent-hook claude is rerun from the new binary. Doctor reports the old path as a missing target. If the executable path cannot be read or is not valid UTF-8, installation warns and falls back to the bare sekretbarilo command. Doctor warns for a bare command because Claude Code resolves it with its own PATH. For an absolute command, doctor checks filesystem metadata, requires a regular executable file, and compares its canonical identity with the running binary; it never executes the configured path and never claims its version. Redaction version validation happens before changing the file, and duplicate sekretbarilo handlers are removed without disturbing other handlers.
The Codex Installer
The Codex installer has the same three outcomes, worded for its own target:
$ sekretbarilo install agent-hook codex
[OK] created codex cli hook configuration
[WARN] IMPORTANT: Codex will silently skip this hook until you approve it.
In the Codex TUI, run /hooks and approve the sekretbarilo hook.
For non-interactive automation only, --dangerously-bypass-hook-trust bypasses this protection.
[INFO] detected Codex version: codex-cli 0.145.0
Re-running it reports [OK] sekretbarilo already installed in codex cli hooks; finding an older command in hooks.json produces [OK] updated codex cli hook configuration. Existing hooks in the file are preserved either way, as with Claude Code — a PreToolUse group of your own stays where it is, and sekretbarilo’s group is appended after it.
The trust reminder is printed on every run, including the already-installed one, because installing and approving are separate steps and only the first is something sekretbarilo can do (see Hook Trust).
The last line reports what sekretbarilo could see of Codex itself. When the codex binary is not on PATH, it changes to a warning that the file was written but the tool was not found:
[NOTE] hook file was written to ./.codex/hooks.json, but Codex was not found on PATH
Preserves Other Hooks
If .claude/settings.json already contains hooks for other tools (e.g., Write, Bash), sekretbarilo preserves them:
Before:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [{"type": "command", "command": "echo write hook"}]
}
]
}
}
After:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [{"type": "command", "command": "echo write hook"}]
},
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "<absolute-path-to-running-sekretbarilo> check-file --stdin-json",
"timeout": 10,
"statusMessage": "Scanning file for secrets..."
}
]
}
]
}
}
The existing Write hook is untouched.
Doctor Diagnostics
The doctor command checks the health of your sekretbarilo installation, including agent hooks. Use it to diagnose issues with hook configuration.
Run Doctor
sekretbarilo doctor
Sample Output
git pre-commit hook:
[NOT INSTALLED] local pre-commit hook not found
[NOT INSTALLED] global pre-commit hook not found
claude code agent hook:
[NOT INSTALLED] local claude code hook not found
[NOT INSTALLED] global claude code hook not found
codex cli agent hook:
[NOT INSTALLED] local codex cli hook not found
[NOT INSTALLED] global codex cli hook not found
[OK] codex found in PATH (codex-cli 0.145.0)
configuration:
[OK] no custom config files found (using defaults)
[OK] 113 rules loaded successfully
[OK] rules compile successfully
sekretbarilo binary:
[OK] sekretbarilo found in PATH
Five groups, each printed with the same status labels. The codex group carries an extra line for the codex binary itself, with its version when it can be read.
What Doctor Checks (Claude Code Hook)
For both local (./.claude/settings.json) and global (~/.claude/settings.json):
- File exists: settings.json is present
- Valid JSON: file parses correctly
- Mode and event: finds sekretbarilo handlers across events, including
PreToolUseandPostToolUse - Matcher and command: checks
Read/check-file --stdin-jsonforblock, or^(Bash|Read|Grep)$/redact-claude --stdin-jsonforredact - Hook settings: checks the mode’s configuration, including synchronous redaction and timeout
- Outdated command detection: warns if an older sekretbarilo command is found
- Stale duplicate detection: warns if a second, older sekretbarilo handler is left behind at another position in the file
- Scope conflict: warns when a blocking Read hook in another scope can prevent a redaction hook from receiving the result
- Explicit scope (
--settings <path>): when given, inspects that file as an additional scope next to local and global, deduplicated when it is the same file; the same mode, Claude Code redact-version, and scope-conflict checks apply, doctor never creates or edits the file, and a missing or malformed file is reported as an issue - Bare hook binary command: warns that Claude Code resolves the name under its own
PATHand that reinstalling pins an absolute path; doctor does not resolve it under its ownPATH - Absolute hook binary command: checks the path’s filesystem metadata and executable bit, then canonicalizes it only to compare its identity with the running binary; doctor never executes the configured path and never reports a version for it
What Doctor Checks (Codex CLI Hook)
For both local (./.codex/hooks.json) and global ($CODEX_HOME/hooks.json, by default ~/.codex/hooks.json):
- File exists: hooks.json is present
- Valid JSON: file parses correctly
- PreToolUse entry exists: the hooks structure is present under the
hooksroot key - Matcher covers the tools: the entry matches
apply_patchandBash - Command matches: the command is
sekretbarilo check-codex --stdin-json - Unrecognised root key: warns if
hooks.jsonhas a top-level key other thanhooksordescription, which makes Codex discard that file’s hooks entirely - Approval entry: looks for the
[hooks.state]entry Codex writes when you approve the hook - Codex on PATH: reports the
codexbinary and its version
Doctor also reports, in the configuration group, any in-workspace .sekretbarilo.toml that is untracked or has uncommitted changes — the agent hooks ignore such a layer entirely. See Config Inside the Repository Must Be Committed.
The Approval Check Is Positional
Codex keys its approval by source file, event, and index — <path>:pre_tool_use:<group>:<handler>. Doctor looks for the key matching the position sekretbarilo’s hook actually occupies. That distinction matters on a machine that already has Codex hooks of its own: sekretbarilo’s group is appended after them, so it sits at a non-zero group index and needs its own approval. Approving somebody else’s hook, or approving ours before the indices shifted, does not count:
[WARN] local codex cli hook: an approval entry exists in ~/.codex/config.toml but not for this hook's position (group 1, handler 0); codex silently skips unapproved hooks; the indices may have shifted; re-approve with /hooks in the Codex TUI
A found entry is reported as OK, with a caveat, because Codex re-checks its own trust hash at run time:
[OK] local codex cli hook approval entry found in ~/.codex/config.toml (group 0, handler 0); codex re-checks its own trust hash at run time, so this is not proof the hook runs
Doctor reports whether the hook is installed and whether an approval entry exists. Whether Codex will actually run it stays Codex’s decision, so confirm with /hooks in the Codex TUI. See Hook Trust. Codex CLI has no hook-validation subcommand of its own, which makes doctor the only mechanical check available on the sekretbarilo side.
Status Levels
| Status | Meaning |
|---|---|
[OK] |
Check passed |
[WARN] |
Non-critical issue (e.g., outdated command, non-executable hook) |
[ERROR] |
Critical issue (e.g., malformed JSON, config parse failure) |
[NOT INSTALLED] |
Hook not found (informational, not an error) |
Exit Code
- 0: all checks passed (or only NOT INSTALLED status, which is informational)
- 1: one or more WARN or ERROR issues found
Example: Outdated Hook Detected
claude code agent hook:
[WARN] local claude code hook has outdated sekretbarilo command: sekretbarilo scan-file --old-flag
[NOT INSTALLED] global claude code hook not found
Fix by running:
sekretbarilo install agent-hook claude
The installer will update the command in place.
Practical Examples
The sessions below are illustrative: they show the sequence of events and how the agent reacts, not literal program output.
Example 1: Developer Installs Hook, Claude Code Tries to Read .env
Setup:
# install agent hook
sekretbarilo install agent-hook claude
# create .env file
echo 'DB_PASSWORD=supersecret123' > .env
Claude Code session:
User: Read the .env file and show me the database password
Claude Code: [attempts to read .env]
[AGENT] .env file blocked: .env
file likely contains environment secrets. reading blocked.
Claude Code: I was unable to read the .env file because it was blocked by sekretbarilo. This is a security measure to prevent exposing secrets. If you need to share configuration, consider reading .env.example instead, or tell me what specific non-secret information you need.
Result: The .env file is never exposed to Claude Code. The developer is informed that the file was blocked.
Example 2: Claude Code Reads a Python File with AWS Key
Setup:
# install agent hook
sekretbarilo install agent-hook claude
# create file with a real (not placeholder) aws key on line 3
cat > src/config.py << 'EOF'
import os
aws_access_key = "AKIA...." # a live key id, not a documentation placeholder
aws_secret_key = os.environ.get('AWS_SECRET_KEY')
def init_client():
return boto3.client('s3', aws_access_key_id=aws_access_key)
EOF
The key has to be a real one for this example to work.
AKIAIOSFODNN7EXAMPLE, the key from AWS’s own documentation, is allowlisted by a built-in rule allowlist and passes through untouched.
Claude Code session:
User: Read src/config.py and explain how the AWS client is initialized
Claude Code: [attempts to read src/config.py]
[AGENT] secret(s) detected in src/config.py
file: src/config.py
line: 3
rule: aws-access-key-id
match: AK****************YZ
file contains 1 secret(s). reading blocked to prevent secret exposure.
Claude Code: I was unable to read src/config.py because it contains an AWS access key. This is a security measure to prevent accidentally exposing the key. If you'd like help with this file, please remove the hardcoded key and use environment variables instead, then I'll be able to read it.
Result: The AWS key is never exposed. Claude Code provides actionable feedback (use environment variables).
Example 3: Claude Code Reads a Normal Source File
Setup:
# install agent hook
sekretbarilo install agent-hook claude
# create clean file
cat > src/utils.py << 'EOF'
import json
def load_config(path):
with open(path, 'r') as f:
return json.load(f)
def save_config(path, data):
with open(path, 'w') as f:
json.dump(data, f, indent=2)
EOF
Claude Code session:
User: Read src/utils.py and explain what it does
Claude Code: [attempts to read src/utils.py]
[sekretbarilo scans silently, exits 0, no output]
Claude Code: [reads file successfully]
This module provides two utility functions for working with JSON configuration files:
1. load_config(path): Reads a JSON file and returns the parsed data
2. save_config(path, data): Writes data to a JSON file with pretty-printing (2-space indent)
The functions use Python's built-in json module and file I/O.
Result: The file passes through transparently. Claude Code reads it normally and provides the requested explanation.
Example 4: Claude Code Reads a Binary/Lock File (Fast-Path Skip)
Setup:
# install agent hook
sekretbarilo install agent-hook claude
# copy a real lock file (thousands of lines)
cp package-lock.json /tmp/test-lock.json
Claude Code session:
User: Read package-lock.json and tell me which version of react is installed
Claude Code: [attempts to read package-lock.json]
[sekretbarilo fast-path detects lock file, exits 0 immediately, no scanning]
Claude Code: [reads file successfully]
The project is using React version 18.2.0. It's listed as a direct dependency with the following details:
- Version: 18.2.0
- Resolved from: https://registry.npmjs.org/react/-/react-18.2.0.tgz
- Integrity: sha512-...
...
Result: The lock file is recognized during fast-path checks and allowed through without scanning. The read completes quickly with no overhead, and Claude Code can answer the question normally.
Example 5: Codex CLI Tries to Write a Secret into .env
Setup:
# install and approve the codex hook
sekretbarilo install agent-hook codex
# then, inside codex: /hooks -> approve
Codex session:
User: Put my AWS credentials in .env so the deploy script can read them
Codex: [attempts apply_patch on .env]
[sekretbarilo blocks the patch: .env target, exit 2, reason on stderr]
Codex: I can't write to .env — sekretbarilo blocks patches targeting .env files
outright, because they are where credentials end up. If you want the deploy
script configured, I can add the variable names to .env.example and read the
values from your shell environment at deploy time instead.
Result: The credential never reaches the repository. Codex sees the reason, so it proposes a workable alternative instead of retrying the same patch.
Example 6: Codex CLI Runs a Shell Command Containing a Token
Setup:
sekretbarilo install agent-hook codex --global
# then, inside codex: /hooks -> approve
Codex session:
User: Check whether the staging API is up, here's my token: sk_live_...
Codex: [attempts Bash: curl -H "Authorization: Bearer sk_live_..." https://staging.example.com/health]
[sekretbarilo blocks the command: secret in command string, exit 2, masked in the reason]
Codex: That request would put a live API token on the command line, where it
lands in shell history and process listings. sekretbarilo blocked it. Export the
token as an environment variable first and I'll reference it as
"$STAGING_TOKEN" in the request header.
Result: The token is not written into shell history or a process list. The value quoted back in the block reason is masked, so it is not re-exposed by the error itself.
Example 7: Codex CLI Applies a Clean Patch
Setup:
sekretbarilo install agent-hook codex
# then, inside codex: /hooks -> approve
Codex session:
User: Add a retry wrapper around the HTTP client in src/client.py
Codex: [attempts apply_patch on src/client.py]
[sekretbarilo scans the added lines, finds nothing, exits 0]
Codex: [patch applied]
Added a retry wrapper with exponential backoff around the request call, retrying
on connection errors and 5xx responses up to three times.
Result: The patch passes through transparently. Ordinary edits carry no friction — only the lines being added are scanned, and clean lines produce no output at all.
Troubleshooting
Claude Code Hook Not Firing
Symptom: Claude Code reads files without triggering sekretbarilo.
These checks describe block mode. For redact, verify the synchronous PostToolUse handler, supported Claude version, and actual tool result using the synthetic smoke check. Successful redaction lets the tool run; it does not block the call.
Check:
- Verify hook is installed:
sekretbarilo doctor - Check
.claude/settings.jsonexists and contains the hook - Ensure sekretbarilo binary is in PATH:
which sekretbarilo - Test
check-filemanually:sekretbarilo check-file path/to/file
Hook Fails with “command not found”
Symptom: Claude Code shows error: sekretbarilo: command not found.
Fix:
- Add sekretbarilo to PATH:
export PATH="$HOME/.cargo/bin:$PATH" - Or use absolute path in hook: edit
.claude/settings.jsonand change command to/full/path/to/sekretbarilo check-file --stdin-json
False Positives Blocking Clean Files
Symptom: sekretbarilo blocks a file that doesn’t contain real secrets.
Fix:
- Add the false positive value to stopwords in
.sekretbarilo.toml:[allowlist] stopwords = ["known-safe-value"] - In
blockmode, you can instead allowlist the file path (redactignores path exclusions):[allowlist] paths = ["path/to/false-positive-file.py"] - Commit the config. A
.sekretbarilo.tomlinside the repository is ignored by the agent hooks until it is git-tracked and clean, so an edit that has not been committed yet changes nothing.sekretbarilo doctorsays so explicitly, and the hook prints[WARN] ignoring untrusted in-workspace config: ...on every run. See Config Inside the Repository Must Be Committed.
Slow Hook Execution
Symptom: Claude Code shows “Scanning file for secrets…” for several seconds.
The fast-path and exclusion advice below applies to block mode. Redaction scans supported output text regardless of its source path and uses a 10 MiB input/response limit.
Check:
- Verify fast-path patterns are working: binary files, vendor dirs, and lock files should skip instantly
- Large source files (>10k lines) may take longer to scan
- Check if custom rules have expensive regexes
Fix:
- Exclude large generated files in
.sekretbarilo.toml:[audit] exclude_patterns = ["^build/", "^dist/"]
Hook Installed but doctor Shows Outdated
Symptom: sekretbarilo doctor reports outdated command.
Fix: Run the installer again to update the command:
sekretbarilo install agent-hook claude
This will detect the outdated command and replace it with the current format.
Codex CLI Hook Not Firing
Symptom: Codex applies patches and runs commands without triggering sekretbarilo, and nothing is reported anywhere.
Check, in this order:
- Approve the hook. This is the usual cause. Run
/hooksin the Codex TUI and approve the sekretbarilo entry. An unapproved hook is skipped without any message — see Hook Trust. If you already approved it once, approve it again: the approval is tied to the hook’s position in the file, and adding or removing another hook shifts it. - Verify it is installed:
sekretbarilo doctorreports the local and global codex groups, and tells you whether it can find an approval entry for the position your hook occupies. Codex itself has nohooks listorhooks validatecommand to cross-check with. - Check the layer you expect: project-local hooks live in
.codex/hooks.jsonat the repository root, global ones in$CODEX_HOME/hooks.json(by default~/.codex/hooks.json). If you installed globally but run Codex somewhere with its own configuration, check both. - Look for a stray root key if you edited
hooks.jsonby hand. Onlyhooksanddescriptionare accepted at the root; anything else makes Codex discard that file’s hooks completely, with no visible error. - Check the Codex version:
codex --version. The integration is verified on0.145.0; older releases may not deliverPreToolUseforapply_patch. - Ensure the binary is in PATH:
which sekretbarilo. The hook command is resolved by Codex, in Codex’s environment.
Codex Warns About Duplicate Hook Definitions
Symptom: Codex prints a warning that hooks are defined in more than one place.
Cause: the same layer has both a hooks.json file and a [hooks] table in config.toml. Codex loads both representations and warns. sekretbarilo only ever writes hooks.json, so the config.toml definition is one you (or another tool) added.
Fix: keep your hooks in one representation. If you consolidate into config.toml, note that re-running sekretbarilo install agent-hook codex will write hooks.json again.
Codex Blocks a Patch or Command You Know Is Safe
Symptom: a patch containing a fixture value, or a command containing a harmless-looking high-entropy string, is refused.
Fix: check-codex reads the same .sekretbarilo.toml hierarchy as every other command, so the same allowlist tools apply:
[allowlist]
stopwords = ["known-safe-value"]
paths = ["test/fixtures/.*"]
Commit the config afterwards — an uncommitted .sekretbarilo.toml inside the repository is ignored by check-codex entirely.
Three things that will not work, however hard you try:
- An uncommitted in-workspace config. The layer is dropped whole, and the hook says so on stderr. See Config Inside the Repository Must Be Committed.
- A path allowlist against a
Bashblock.pathsentries are ignored when scanning command text — usestopwordsor a per-rule value regex instead. Forapply_patch,pathsworks as usual against the patch target. - Anything against a
.envblock. The.envcheck runs before configuration is even loaded, so no allowlist entry changes it. Write to.env.exampleinstead, or set the value in your shell rather than through the agent.
Summary
Agent hooks provide a critical security layer for AI-assisted development:
- Claude modes: Block file reads or mask detected secrets in successful Bash/Read/Grep text results
- Prevent secret injection: Block patches and shell commands that would write secrets through Codex CLI
- File scanning fast paths: Skip configured binary, dependency, and lock-file paths in
blockmode - Shared detectors: Rules and value exceptions apply to all hook modes; redaction ignores path exclusions and documentation relaxations
- Configuration the agent cannot rewrite: an in-workspace
.sekretbarilo.tomlcounts only once it is committed - Easy installation: One command per agent, or
install allfor everything at once - Diagnostic support:
doctorcommand validates hook configuration for both agents
Install agent hooks today to protect your secrets from accidental exposure through AI coding agents:
# project-local
sekretbarilo install agent-hook claude
sekretbarilo install agent-hook codex
# global
sekretbarilo install agent-hook claude --global
sekretbarilo install agent-hook codex --global
# or install everything at once
sekretbarilo install all --global
Then approve the Codex hook with /hooks in the Codex TUI — until you do, Codex skips it silently.
For more information, see:
- Getting Started - Overview and quick setup
- CLI Reference - Complete command reference
- Configuration - Customizing detection rules and allowlists