Parry-guard
A prompt injection scanner for AI coding tool hooks. It catches injection attacks, leaked secrets, and data exfiltration in tool inputs and outputs. It's built for Claude Code, and also works with Codex or any tool that runs command hooks with the same JSON input and output.
Early development: expect bugs and false positives. Tested on Linux and macOS.
Prerequisites
The ML models are gated on HuggingFace. Before installing:
- Create an account at huggingface.co.
- Accept the DeBERTa v3 license. All modes need it.
- For
fullmode, also accept the Llama Prompt Guard 2 license. Meta has to approve it. - Create an access token at huggingface.co/settings/tokens.
Usage
Claude Code
Add to ~/.claude/settings.json:
With uvx:
{
"hooks": {
"PreToolUse": [{ "command": "uvx parry-guard hook", "timeout": 1000 }],
"PostToolUse": [{ "command": "uvx parry-guard hook", "timeout": 5000 }],
"UserPromptSubmit": [{ "command": "uvx parry-guard hook", "timeout": 2000 }]
}
}
With rvx:
{
"hooks": {
"PreToolUse": [{ "command": "rvx parry-guard hook", "timeout": 1000 }],
"PostToolUse": [{ "command": "rvx parry-guard hook", "timeout": 5000 }],
"UserPromptSubmit": [{ "command": "rvx parry-guard hook", "timeout": 2000 }]
}
}
With parry-guard on your PATH (from Nix, cargo install, or a release binary):
{
"hooks": {
"PreToolUse": [{ "command": "parry-guard hook", "timeout": 1000 }],
"PostToolUse": [{ "command": "parry-guard hook", "timeout": 5000 }],
"UserPromptSubmit": [{ "command": "parry-guard hook", "timeout": 2000 }]
}
}
Codex
Add to ~/.codex/config.toml:
[[hooks.PreToolUse]]
matcher = "Bash|Read|Write|Edit|Glob|Grep|WebFetch|WebSearch|apply_patch|mcp__.*"
[[hooks.PreToolUse.hooks]]
type = "command"
command = "parry-guard hook"
timeout = 1
[[hooks.PostToolUse]]
matcher = "Bash|Read|WebFetch|Edit|apply_patch|mcp__.*"
[[hooks.PostToolUse.hooks]]
type = "command"
command = "parry-guard hook"
timeout = 5
[[hooks.UserPromptSubmit]]
matcher = ""
[[hooks.UserPromptSubmit.hooks]]
type = "command"
command = "parry-guard hook"
timeout = 2
Other hook runners
parry-guard hook reads one JSON payload from stdin and writes a JSON decision to stdout. Any tool with command hooks can use it, as long as it sends Claude/Codex-style fields (hook_event_name, tool_name, tool_input, tool_response, prompt) and respects block/deny responses.
Claude Code can ask you to confirm a PreToolUse call. Codex can't yet, so for Codex payloads parry-guard turns an "ask" into a block: it exits with a blocking code and prints the reason to stderr.
Other installation methods
From source:
# Default: ONNX backend, statically linked, 5-6x faster than Candle
cargo install --path bin
# Candle backend: pure Rust, no native deps, portable
cargo install --path bin --no-default-features --features candle
Nix (home-manager)
# flake.nix
{
inputs.parry.url = "github:vaporif/parry-guard";
outputs = { parry, ... }: {
# pass parry to your home-manager config via extraSpecialArgs, overlays, etc.
};
}
# home-manager module
{ inputs, pkgs, config, ... }: {
imports = [ inputs.parry.homeManagerModules.default ];
programs.parry-guard = {
enable = true;
package = inputs.parry.packages.${pkgs.system}.default; # onnx (default)
# package = inputs.parry.packages.${pkgs.system}.candle; # candle (pure Rust, portable, ~5-6x slower)
hfTokenFile = config.sops.secrets.hf-token.path;
# hfTokenCommand = "pass show huggingface/token"; # or fetch from a keychain / secret manager
ignoreDirs = [ "/home/user/repos/trusted" ];
# askOnNewProject = true; # ask before monitoring new projects (default: monitor right away)
# claudeMdThreshold = 0.9; # ML threshold for CLAUDE.md scanning (default 0.9)
# scanMode = "full"; # fast (default) | full | custom
# Custom models (auto-sets scanMode to "custom")
# models = [
# { repo = "ProtectAI/deberta-v3-small-prompt-injection-v2"; }
# { repo = "meta-llama/Llama-Prompt-Guard-2-86M"; threshold = 0.5; }
# ];
};
}
Setup
HuggingFace token
Set your token one of these ways (first match wins):
export HF_TOKEN="hf_..." # direct value
export HF_TOKEN_COMMAND="pass show huggingface/token" # command that prints the token
export HF_TOKEN_PATH="/path/to/token" # file path
# or place token at /run/secrets/hf-token-scan-injection
HF_TOKEN_COMMAND lets the token stay in a keychain or secret manager. The hook passes the command to the daemon instead of running it. The daemon runs it with sh -c (cmd /C on Windows) only when a model file isn't in the local HuggingFace cache yet, at most once per model load, and uses the trimmed stdout as the token. A cached model never runs it. The token is kept only in memory. Some examples:
export HF_TOKEN_COMMAND="security find-generic-password -s huggingface -w" # macOS Keychain
export HF_TOKEN_COMMAND="secret-tool lookup service huggingface" # GNOME Keyring / KWallet
export HF_TOKEN_COMMAND="op read op://Private/HuggingFace/token" # 1Password
export HF_TOKEN_COMMAND="bw get password huggingface" # Bitwarden
The command runs with no stdin and must finish within 30 seconds. When it exits or times out, anything it left running in its process group is killed (Unix only). It must exit 0 with non-empty stdout. If it fails, the error and the last line of its stderr are logged, and the daemon downloads without it (hf-hub still uses a token saved by huggingface-cli login, if any). That works for public models such as the fast mode one. Gated models then fail to download, scans fail closed, and the daemon tries again on a later scan (up to 3 attempts per daemon). Because the daemon is detached, a command that waits for an interactive prompt (for example, a locked 1Password CLI) will time out. Unlock the store first or use a non-interactive credential.
Models are cached under $HF_HOME/hub (default ~/.cache/huggingface/hub). If HF_ENDPOINT is set, models are downloaded from that mirror and the token is sent to it.
With HF_TOKEN or a token file, the hook copies the token to ~/.parry-guard/.hf-token (mode 0600) so the daemon can read it. With HF_TOKEN_COMMAND, nothing is written, and the hook deletes any leftover copy when it next starts the daemon.
The daemon starts on the first scan, downloads the model on the first run, and stops after 30 idle minutes. Without Nix, set the env vars in your shell profile or pass flags (see Config).
Project scanning
By default, every new project is scanned from its first session, with no prompt. To skip a repo, run parry-guard ignore <path>.
To be asked first instead, set PARRY_ASK_ON_NEW_PROJECT=true (askOnNewProject = true in Nix). docs/opt-in-flow.md shows the full flow.
| Command | Effect |
|---|---|
parry-guard monitor [path] |
Turn scanning on for a repo |
parry-guard ignore [path] |
Turn scanning off for a repo |
parry-guard reset [path] |
Clear state and caches, back to unknown |
parry-guard status [path] |
Show current repo state and findings |
parry-guard repos |
List all known repos and their states |
path defaults to the current directory.
What each hook does
PreToolUse runs 7 checks in order and stops at the first hit:
- Skip ignored or unknown repos
- Enforce taint
- Scan CLAUDE.md
- Block exfiltration
- Catch destructive operations
- Block sensitive paths
- Scan input content for injection (Write, Edit, Bash, MCP tools)
PostToolUse scans tool output for injection and secrets. A hit taints the project.
UserPromptSubmit audits your .claude/ directory for dangerous permissions, injected commands, and hook scripts.
Daemon and cache
Hook calls start the daemon if it isn't running. You can also run it yourself with parry-guard serve --idle-timeout 1800.
Scan results are cached in ~/.parry-guard/scan-cache.redb for 30 days. A cache hit takes about 8ms, versus 70ms+ for inference. All projects share the cache, and it's pruned hourly.
Detection layers
The scanner fails closed: if it can't tell whether something is safe, it treats it as unsafe.
- Unicode: invisible characters (PUA, unassigned codepoints), homoglyphs, RTL overrides.
- Substring: Aho-Corasick matching for known injection phrases.
- Secrets: 40+ regex patterns for credentials (AWS, GitHub/GitLab, cloud providers, database URIs, private keys, and more).
- ML classification: a DeBERTa v3 model. Text is split into 256-char chunks with 25 chars of overlap, and long texts are scanned head and tail. Default threshold is 0.7.
- Bash exfiltration: tree-sitter AST analysis for network sinks, command substitution, obfuscation (base64, hex, ROT13), DNS tunneling, cloud storage, 60+ sensitive paths, and 40+ exfil domains.
- Script exfiltration: the same source-to-sink analysis for script files in 16 languages.
Scan modes
| Mode | Models | Latency per chunk | Backend |
|---|---|---|---|
fast (default) |
DeBERTa v3 | ~50-70ms | any |
full |
DeBERTa v3 + Llama Prompt Guard 2 | ~1.5s | candle only |
custom |
User-defined (parry-guard/models.toml in your config dir) |
varies | any |
Use fast for interactive work. Use full for high security or batch scans (parry-guard diff --full). DeBERTa v3 is good at common injection patterns; Llama Prompt Guard 2 is better at subtle ones like role-play jailbreaks and indirect injection. full flags text if either model does, so it misses less, but each chunk takes about 20x longer.
full mode needs the candle backend, since Llama Prompt Guard 2 has no ONNX export. Build with --features candle --no-default-features.
Config
Global flags
| Flag | Env | Default | Effect |
|---|---|---|---|
--threshold |
PARRY_THRESHOLD |
0.7 | ML detection threshold (0.0-1.0) |
--claude-md-threshold |
PARRY_CLAUDE_MD_THRESHOLD |
0.9 | ML threshold for CLAUDE.md scanning (0.0-1.0) |
--scan-mode |
PARRY_SCAN_MODE |
fast | ML scan mode: fast, full, custom |
--hf-token |
HF_TOKEN |
HuggingFace token (direct value) | |
--hf-token-command |
HF_TOKEN_COMMAND |
Shell command that prints the HuggingFace token. The daemon runs it, and the token is never written to disk | |
--hf-token-path |
HF_TOKEN_PATH |
/run/secrets/hf-token-scan-injection |
HuggingFace token file |
--ask-on-new-project |
PARRY_ASK_ON_NEW_PROJECT |
false | Ask before monitoring new projects instead of monitoring them right away |
--ignore-dirs |
PARRY_IGNORE_DIRS |
Comma-separated parent directories. Every repo under them is skipped. |
Subcommand flags
| Flag | Env | Default | Effect |
|---|---|---|---|
serve --idle-timeout |
PARRY_IDLE_TIMEOUT |
1800 | Daemon idle timeout in seconds |
diff --full |
false | Add the ML scan (default is fast scan only) | |
diff -e, --extensions |
Filter by file extension (comma-separated) |
Environment-only variables
| Env | Default | Effect |
|---|---|---|
PARRY_LOG |
warn | Tracing filter (trace, debug, info, warn, error) |
PARRY_LOG_FILE |
~/.parry-guard/parry-guard.log |
Override log file path |
Custom patterns go in parry-guard/patterns.toml under your config dir: ~/.config on Linux, ~/Library/Application Support on macOS. You can add or remove sensitive paths, exfil domains, and secret patterns.
Custom models go in parry-guard/models.toml in the same dir and are used with --scan-mode custom. See examples/models.toml.
ML backends
You need exactly one backend; the build fails without one. Nix uses ONNX on x86_64-linux, aarch64-linux, and aarch64-darwin. On other platforms, use the candle package.
| Feature | |
|---|---|
onnx-fetch |
ONNX, statically linked. Downloads ORT at build time. Default. |
candle |
Pure Rust ML. Portable, no native deps. About 5-6x slower. |
onnx |
ONNX, you provide ORT_DYLIB_PATH. |
onnx-coreml |
ONNX with CoreML on Apple Silicon. Experimental. |
Performance
Apple Silicon, release build, fast mode (DeBERTa v3 only). To reproduce, run just bench-candle or just bench-onnx with HF_TOKEN set.
| Scenario | ONNX (default) | Candle |
|---|---|---|
| Short text (1 chunk) | ~10ms | ~61ms |
| Medium text (2 chunks) | ~32ms | ~160ms |
| Long text (6 chunks) | ~136ms | ~683ms |
| Cold start (daemon + model load) | ~580ms | ~1s |
| Fast scan short-circuit | ~7ms | ~7ms |
| Cached result | ~8ms | ~8ms |
Contributing
See CONTRIBUTING.md.
Credits
- ML model: ProtectAI/deberta-v3-small-prompt-injection-v2, also used by LLM Guard
- Exfil patterns: inspired by GuardDog, Datadog's malicious package scanner
- Full scan mode optionally uses Llama Prompt Guard 2 86M by Meta, licensed under the Llama 4 Community License. Built with Llama.
License
MIT
Llama Prompt Guard 2 (used in full mode) has its own license, the Llama 4 Community License. See LICENSE-LLAMA.
Metadata
Release files for parry-guard 0.1.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| parry_guard-0.1.7-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| parry_guard-0.1.7-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| parry_guard-0.1.7-py3-none-macosx_14_0_x86_64.whl | Python 3 | none | macOS 14.0+ x86-64 | Details |
| parry_guard-0.1.7-py3-none-macosx_14_0_arm64.whl | Python 3 | none | macOS 14.0+ ARM64 | Details |
Total release size: 33.8 MB
Release files / parry_guard-0.1.7-py3-none-musllinux_1_2_x86_64.whl
| Download URL | parry_guard-0.1.7-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 8.9 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
83727dee78c2b4ab8af3a7e901a61b677e35db6f56de875123eea6c84691b654
|
|
BLAKE2b-256 checksum How to use checksums |
0afea4e42a2f1dd942c7c9949915ffff068c2024c286929aa38f2ccfd45fe1e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / parry_guard-0.1.7-py3-none-musllinux_1_2_aarch64.whl
| Download URL | parry_guard-0.1.7-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 8.6 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
ebaf60261f1b5b02320c65eaaec84e64d5106591ccd373ffed8d2225c5191ef0
|
|
BLAKE2b-256 checksum How to use checksums |
468ee9f9d2cf43297cb7eb190ff911f57f37d250ccca2b9b0f6f3397918b8bfc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / parry_guard-0.1.7-py3-none-macosx_14_0_x86_64.whl
| Download URL | parry_guard-0.1.7-py3-none-macosx_14_0_x86_64.whl |
|---|---|
| Size | 8.2 MB |
| Tags | Python 3 macOS 14.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
69d42830b5c220913cb386d2726ac944cf89441aa61bff71b3bbb1fdf1d632d5
|
|
BLAKE2b-256 checksum How to use checksums |
ee065d0738ee332022a691035817deed49ac4b9fa72d57ec03af5883c3767c98
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / parry_guard-0.1.7-py3-none-macosx_14_0_arm64.whl
| Download URL | parry_guard-0.1.7-py3-none-macosx_14_0_arm64.whl |
|---|---|
| Size | 8.1 MB |
| Tags | Python 3 macOS 14.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e4f2195c3c8a837c254331df0a639f698079d0e7636208974d8be141830c814f
|
|
BLAKE2b-256 checksum How to use checksums |
b0b8c64de1f5612c5c90ef65e1457ab1a6bc18e51d489064cebf57ae50628560
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log