Skip to main content

🦀 Ferroguard

Rust checks for Python. Install ferroguard. Import llm_guardrails.

A library that redacts some PII, scores a few prompt patterns, and checks model output with a small set of rules. The checks run in Rust. A FastAPI app in api/ shows the pipeline. It does not call a model.

License Rust Python PyPI Docs

⚡ Technology Stack and Features

  • 🦀 Rust on the stable channel in rust-toolchain.toml, with the regex crate.
    • 🔗 PyO3 and maturin build llm_guardrails._core.
    • 🕵️ Eight injection patterns, scored after the text is normalised.
    • 🎭 A small bias scorer and a small output checker. Neither one is a proof of safety.
  • 🐍 Python 3.11, 3.12, 3.13, or 3.14. Development uses 3.14 (.python-version).
    • 📦 uv installs the interpreter and the locked environment.
    • 🧱 Pydantic v2 models on the Python side of each guard.
    • 🐍 The PyPI project is ferroguard. The import name stays llm_guardrails.
  • 🚀 FastAPI demo in api/. It does not call a model.
    • 📏 text is at most 8000 characters (422 above that).
    • 📦 A request body over 32768 bytes is 413.
    • 🗂️ Placeholder maps stay in the process: at most 1024 maps, 262144 UTF-8 bytes in total, and 65536 bytes for one map. A map that does not fit is 503.
  • 🧪 CI on pull requests and on pushes to main.
    • 🐍 ruff, mypy on llm_guardrails/, and pytest on Python 3.11 and 3.14.
    • 🎡 One abi3 wheel, imported on 3.11 and 3.14.
    • 🦀 cargo fmt, cargo clippy -D warnings, and cargo test.
    • 🔒 Security runs cargo-deny and pip-audit. CodeQL analyses Python and Rust with build-mode: none.

🚀 How To Use It

Requirements

Tool What you need
Rust rustup. Opening the repo selects stable from rust-toolchain.toml.
uv Installs Python and the dev environment.
Python 3.11, 3.12, 3.13, or 3.14. uv sync reads .python-version and uses 3.14.

Install

pip install ferroguard
import llm_guardrails
Name Value
PyPI project ferroguard
Import llm_guardrails
Wheel abi3, one wheel for 3.11 through 3.14

The same install and a short example are on the docs site.

Run the demo

uv sync --all-extras
uv run uvicorn api.main:app --reload
What Where
🩺 Health http://localhost:8000/health
📖 Interactive API docs http://localhost:8000/docs
📄 OpenAPI document http://localhost:8000/openapi.json

GET /health returns {"status":"ok","version":"<installed>"}. That version is the installed ferroguard distribution. Maturin reads it from Cargo.toml. It is not hard-coded in api/main.py.

from llm_guardrails import GuardrailsPipeline

pipeline = GuardrailsPipeline()
pre = pipeline.pre_process("My email is alice@example.com. Summarise this report.")

if pre.blocked:
    print(pre.injection.matched_rules)
else:
    print(pre.sanitised_text)
    post = pipeline.post_process("Paris is the capital of France.", pre.pii_mapping)
    print(post.final_text)
    print(post.bias.score)
    print(post.validation.is_valid)

That sanitised text is My email is <<EMAIL_1>>. Summarise this report.

GuardrailsPipeline takes its switches as constructor arguments (pii_enabled, injection_enabled, bias_enabled, output_validation_enabled, injection_threshold, validation_rules). .env-template lists similar names. Nothing in the library or the demo reads that file.

The demo does not call an LLM. POST /guard/full uses simulated_llm_response when you send one, and otherwise echoes a stub that quotes the sanitised prompt.

Demo API

FastAPI builds the document from the route functions.

Method Path What it does
GET /health Status and the installed package version
POST /guard/input PII placeholders, an injection score, and an opaque pii_mapping_id
POST /guard/output Validation, bias score, and PII restore for that id
POST /guard/full Input guards, then a stub or a caller-supplied response, then output guards

text over 8000 characters is 422. A body over 32768 bytes is 413, and the message names that limit. An unknown field is 422.

/guard/input does not return the placeholder map. A non-empty map that fits stays in this process for 15 minutes, under pii_mapping_id. The store holds at most 1024 maps, 262144 UTF-8 bytes in total, and 65536 bytes for one map. A map that does not fit is 503, and that call stores nothing. An empty map is not stored, and pii_mapping_id is null. /guard/full does not store a map: restore happens inside that request, and its pii_mapping_id is null. /guard/output takes the id, pops the map, and rejects a client-supplied pii_mapping. A second call with the same id is 404, the same as an unknown or expired id. Raw PII is not in the input response. The store is not shared across workers, and it is lost on restart. GuardrailsPipeline still returns the map to in-process callers.

A blocked prompt (the injection score is at least the threshold, default 0.5) comes back with "blocked": true. /guard/full then has no output section.

curl -s -X POST http://localhost:8000/guard/input \
  -H 'Content-Type: application/json' \
  -d '{"text": "My SSN is 123-45-6789. What is the weather?"}'

Demo image

The image in Dockerfile builds a wheel on python:3.12-slim, installs that wheel plus FastAPI and uvicorn[standard]>=0.54.0,<1.0, and serves api.main:app on port 8000. The Python and uv image references include a digest. Dependabot's weekly docker group opens a pull request when those digests move. The Rust channel stays stable. The image downloads rustup-init 1.29.3 and checks its SHA256 before running it. Refreshing that installer means changing RUSTUP_VERSION and both architecture checksums in the same edit. Dependabot does not change those checksums. The image does not load .env-template.

docker build -t llm-guardrails .
docker run --rm -p 8000:8000 llm-guardrails
curl -s http://localhost:8000/health

PyPI publishing stays in the release workflow. It is not a separate tag workflow.

🧩 How the pieces fit

flowchart LR
    subgraph python [Python]
        API[api.main FastAPI]
        PIPE[GuardrailsPipeline]
        WRAP[PIIRedactor InjectionDetector BiasScorer OutputValidator]
    end
    subgraph rust [Rust cdylib]
        PII[pii_redactor]
        INJ[injection_detector]
        BIAS[bias_scorer]
        VAL[output_validator]
    end
    API --> PIPE --> WRAP
    WRAP --> PII
    WRAP --> INJ
    WRAP --> BIAS
    WRAP --> VAL
sequenceDiagram
    participant Caller
    participant Pipeline as GuardrailsPipeline
    participant Rust as llm_guardrails._core
    Caller->>Pipeline: pre_process(text)
    Pipeline->>Rust: pii_redact
    Rust-->>Pipeline: sanitised text, mapping
    Pipeline->>Rust: injection_analyse
    Rust-->>Pipeline: score, matched rules
    Pipeline-->>Caller: PreProcessResult
    Note over Caller: Caller sends sanitised text to a model, or stops when blocked
    Caller->>Pipeline: post_process(model text, mapping)
    Pipeline->>Rust: output_validate
    Pipeline->>Rust: bias_score
    Pipeline->>Rust: pii_restore
    Pipeline-->>Caller: PostProcessResult

🔒 Security

What the library actually does:

  • PII. src/pii_redactor.rs replaces matches with <<LABEL_N>> and returns the map. Labels are SSN, CREDIT_CARD, EMAIL, PHONE, IP_ADDRESS, DATE_OF_BIRTH, and NAME (two consecutive capitalised words). A card number is a run of 13–19 digits with optional spaces or dashes that also passes the Luhn check. An SSN whose area is 000, 666, or 9xx, whose group is 00, or whose serial is 0000 is left as written. A phone-shaped slice inside a longer digit run is left alone. A name or date of birth is redacted only when a context word sits next to it (name, born, DOB, Dr., Ms., or Dr/Ms followed by a capitalised word). Jane Doe called is not redacted. There is no switch that restores a match of every title-case pair or every date. PIIRedactor and GuardrailsPipeline take allow_entities and deny_entities (pipeline: pii_allow_entities, pii_deny_entities). An empty allow list raises ValueError, and so does a label that is not one of those seven. On PIIRedactor those two attributes are read-only after construction. Restoration is a straight string replace of the placeholders.
  • Injection. Eight regular expressions (ignore_previous, reveal_system_prompt, role_play_attack, developer_mode, encoding_evasion, do_anything_now, system_role_injection, token_smuggling). Text is normalised first: NFKC, invisible characters removed, common homoglyphs folded, and spaced-out letters collapsed (i g n o r e). The DAN token is uppercase only, so "Dan" is not a jailbreak; "do anything now" still matches in any case. The score is the highest matching weight, plus 0.10 when two or more rules match, capped at 1.0.
  • Bias. A handful of stereotype patterns, a gendered-token imbalance check, and an absolute-generalisation pattern. The score is a weighted sum of those hits, capped at 1.0.
  • Output. Optional max length, a top-level JSON type and required check (not full JSON Schema), a hedging-phrase score exposed as hallucination_score, and required/blocked keyword checks. A hedging hit is a warning and does not by itself clear is_valid.

The map of original PII stays in memory and is handed back to the caller. The library does not write it to disk. Discarding the map is how a caller drops those values. None of this is a complete privacy, safety, or compliance programme. Pair it with the controls your deployment actually needs, and do not send raw prompts to a model provider you have not reviewed.

CI rejects known-vulnerable and yanked Rust crates (deny.toml) and known-vulnerable Python dependencies (pip-audit). Private reports go through SECURITY.md.

📚 Documentation

  • 📖 Docs site — install, guards, limits, and the local demo
  • 🛠️ CONTRIBUTING.md — setup, tests, lint, and pull requests
  • 🔖 RELEASING.md — how a push to main is versioned, tagged, and published
  • 🔒 SECURITY.md — private vulnerability reports
  • 📖 CODE_OF_CONDUCT.md — Contributor Covenant 2.1
  • 🤖 AGENTS.md — conventions for contributors and coding agents

Release runs on a push to main. It commits .version and CHANGELOG.md, pushes that commit to main without --force, then pushes the tag. The same run builds the abi3 wheels and sdist and publishes them to PyPI. Docs builds the static site on pull requests and on pushes to main. Scorecard runs on main and on a weekly schedule.

🤝 Contributing

Two places to change when a guard's behaviour changes: the Rust function in src/, and the wrapper in llm_guardrails/, which should stay a conversion to a Pydantic model. api/main.py should keep calling GuardrailsPipeline rather than growing its own checks.

uv sync --locked --all-extras
uv run ruff check .
uv run ruff format --check .
uv run mypy llm_guardrails
uv run pytest
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
python3 scripts/release_subject.py --self-test
python3 scripts/release_publish.py --self-test

uv sync --locked --all-extras builds the extension and installs the api extra. pytest and ruff come from the default dev dependency group, not an extra. pytest needs that build because the wrappers import llm_guardrails._core. cargo test runs the Rust tests without Python.

With just installed, just setup syncs the lockfile and runs maturin develop. just ci runs setup, lint, pytest, cargo test, the two release self-tests, and python3 scripts/test_guardrails.py. GitHub CI also runs mypy. just ci does not.

pytest covers the wrappers, GuardrailsPipeline, and the demo routes (tests/). cargo test covers the regex helpers in src/.

📄 License

Ferroguard is licensed under the terms of the MIT license. Copyright (c) 2026 Melroy Anthony.

Metadata

Release files for ferroguard 0.3.5

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

Source distribution (sdist)

Source distribution for ferroguard 0.3.5
File Size Uploaded Provenance Actions
ferroguard-0.3.5.tar.gz 376.8 kB With provenance Details for ferroguard-0.3.5.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for ferroguard 0.3.5
File Actions
ferroguard-0.3.5-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details for ferroguard-0.3.5-cp311-abi3-win_amd64.whl
ferroguard-0.3.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details for ferroguard-0.3.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
ferroguard-0.3.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details for ferroguard-0.3.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
ferroguard-0.3.5-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details for ferroguard-0.3.5-cp311-abi3-macosx_11_0_arm64.whl
ferroguard-0.3.5-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details for ferroguard-0.3.5-cp311-abi3-macosx_10_12_x86_64.whl

Total release size: 5.4 MB

Release files / ferroguard-0.3.5.tar.gz

Download URL ferroguard-0.3.5.tar.gz
Size 376.8 kB
Tags Source
SHA-256 checksum
How to use checksums
dd7e1adf5355af626b9732a1f983f7c1e7f252d4a8dae7dda7d94488727fed1b
BLAKE2b-256 checksum
How to use checksums
41b553f99f47dd46ac4a804a893ddc844d907547841efd20687f3cd4406e3b36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 5, 2026.

Transparency log

Release files / ferroguard-0.3.5-cp311-abi3-win_amd64.whl

Download URL ferroguard-0.3.5-cp311-abi3-win_amd64.whl
Size 874.4 kB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
a6ac68cefc35adbdea9e429115cceb0dac560e3a9824f3fce77592b5a3cdb4de
BLAKE2b-256 checksum
How to use checksums
b64d7c79ca15c6102a5bd966541d903120a9e89cd2120ecf98e5253db5415ff4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 5, 2026.

Transparency log

Release files / ferroguard-0.3.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL ferroguard-0.3.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 1.1 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
87ab2ed964d33b40e9ac8d1494b7fa755bc7a2bb0a1fe424500b7309e3803803
BLAKE2b-256 checksum
How to use checksums
189f274236cb0772b58c7a8abdff71a915c068419b5f8b545fd755aef4d528df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 5, 2026.

Transparency log

Release files / ferroguard-0.3.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL ferroguard-0.3.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 1.1 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
cf8dcebcba386d01767d042bafab59a2a7b9470e2e1aa607ff7de4883790d86c
BLAKE2b-256 checksum
How to use checksums
9811ab33e0107fa66152a80a809997b8530c223cb3bf41f77154ef301f7ad41c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 5, 2026.

Transparency log

Release files / ferroguard-0.3.5-cp311-abi3-macosx_11_0_arm64.whl

Download URL ferroguard-0.3.5-cp311-abi3-macosx_11_0_arm64.whl
Size 965.5 kB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
48473471a03e77b73a180e4562d82e5d2562e0e11715290ed7b355112fc32a50
BLAKE2b-256 checksum
How to use checksums
6093f5fb82fd74157ff10c5c0412b53d9cc9ad09a99b159f9c96cd96e7c5e58e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 5, 2026.

Transparency log

Release files / ferroguard-0.3.5-cp311-abi3-macosx_10_12_x86_64.whl

Download URL ferroguard-0.3.5-cp311-abi3-macosx_10_12_x86_64.whl
Size 1.0 MB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
d1aea90bba2f712e5cdab2d6bba5a5b71016f4592ff429a917906c0b95bb9dd6
BLAKE2b-256 checksum
How to use checksums
158fcfbbdbc30e7b83f9950ff5b87b2c26cc91daa11a0a5e481c51c2abe863dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 5, 2026.

Transparency log

Security for ferroguard 0.3.5

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.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for ferroguard-0.3.5.tar.gz

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 Nov 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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