Skip to main content

Parry Guard Parry-guard

ci audit codecov Mentioned in Awesome Claude Code

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:

  1. Create an account at huggingface.co.
  2. Accept the DeBERTa v3 license. All modes need it.
  3. For full mode, also accept the Llama Prompt Guard 2 license. Meta has to approve it.
  4. 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_HUB_CACHE if set, otherwise $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:

  1. Skip ignored or unknown repos
  2. Enforce taint
  3. Scan CLAUDE.md
  4. Block exfiltration
  5. Catch destructive operations
  6. Block sensitive paths
  7. 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.

  1. Unicode: invisible characters (PUA, unassigned codepoints), homoglyphs, RTL overrides.
  2. Substring: Aho-Corasick matching for known injection phrases.
  3. Secrets: 40+ regex patterns for credentials (AWS, GitHub/GitLab, cloud providers, database URIs, private keys, and more).
  4. 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.
  5. 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.
  6. 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.

patterns.toml also tunes destructive-command detection:

[destructive_paths]
add = ["~/work/shared/"]   # extra protected path prefixes (Write/Edit, chmod/chown)
remove = ["~/.cargo/"]     # stop protecting these prefixes

[destructive_commands]
add = ["terraform"]        # always ask before running these
remove = ["kill"]          # never flag these

destructive_paths.remove also unprotects built-in prefixes. For example, remove = ["~/.cargo/"] lets Write, Edit and chmod touch ~/.cargo/ without asking. rm outside the project still asks. Remove a prefix only if you trust every agent session with it. The file is only read from your user config dir, never from the project, so a repository can't ship its own overrides.

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

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.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for parry-guard 0.1.8
File
parry_guard-0.1.8-py3-none-musllinux_1_2_x86_64.whl Python 3 none Linux musl 1.2+ x86-64 Details
parry_guard-0.1.8-py3-none-musllinux_1_2_aarch64.whl Python 3 none Linux musl 1.2+ ARM64 Details
parry_guard-0.1.8-py3-none-macosx_14_0_x86_64.whl Python 3 none macOS 14.0+ x86-64 Details
parry_guard-0.1.8-py3-none-macosx_14_0_arm64.whl Python 3 none macOS 14.0+ ARM64 Details

Total release size: 48.2 MB

Release files / parry_guard-0.1.8-py3-none-musllinux_1_2_x86_64.whl

Download URL parry_guard-0.1.8-py3-none-musllinux_1_2_x86_64.whl
Size 12.8 MB
Tags Linux musl 1.2+ x86-64 Python 3
SHA-256 checksum
How to use checksums
a9b537e738d74beebc1ab879283bf4ec205a1af98ea5f54e8b25e2000b118ca4
BLAKE2b-256 checksum
How to use checksums
97c61f523b2a2c09855676847a9525774f01690210ea60d4c3014bcee8e78242
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 11, 2026.

Transparency log

Release files / parry_guard-0.1.8-py3-none-musllinux_1_2_aarch64.whl

Download URL parry_guard-0.1.8-py3-none-musllinux_1_2_aarch64.whl
Size 12.2 MB
Tags Linux musl 1.2+ ARM64 Python 3
SHA-256 checksum
How to use checksums
f30bbbe58a87fe852142d3312f308b223edfe5e48f4f9e4339dfa867330cbd99
BLAKE2b-256 checksum
How to use checksums
3cd76ae4041a838bfd77760c78a549efacab11dee4b499248dc4a057043b876c
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 11, 2026.

Transparency log

Release files / parry_guard-0.1.8-py3-none-macosx_14_0_x86_64.whl

Download URL parry_guard-0.1.8-py3-none-macosx_14_0_x86_64.whl
Size 11.7 MB
Tags Python 3 macOS 14.0+ x86-64
SHA-256 checksum
How to use checksums
44ca8019d56929600063ce9f3f9c4f36dc2a1ca23b1f889500f602d145b720ec
BLAKE2b-256 checksum
How to use checksums
0331f1b12863b07999fc877bce738d93c8b70662db0cfc36e3752a7d7b33a202
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 11, 2026.

Transparency log

Release files / parry_guard-0.1.8-py3-none-macosx_14_0_arm64.whl

Download URL parry_guard-0.1.8-py3-none-macosx_14_0_arm64.whl
Size 11.4 MB
Tags Python 3 macOS 14.0+ ARM64
SHA-256 checksum
How to use checksums
758d1f6a42edf0981eefb7962bb15c6393642cf3b4e965c49c4deeeef108875c
BLAKE2b-256 checksum
How to use checksums
3b68695742c4e22258ec1a0bfa3fd652780182d36588e3e73cb4416f79351a53
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 11, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page