Skip to main content

llm-input-hardening

The input boundary: what text LLMs and agents are allowed to read. Part of the Forge *-hardening family, alongside agent-api-hardening (capability boundary) and safegit (recovery boundary).

Deterministic Unicode-aware input hardening for LLM applications.

llm-input-hardening sits between untrusted text and your prompt or template layer. It bounds Unicode normalization, removes invisible or control characters that destabilize prompts, emits structured reports for logging and enforcement, and detects mixed- and whole-script confusables.

It is not prompt injection prevention. It addresses text-integrity problems, not model intent.

This README is the main entry point and quickstart. Deeper references — the full report schema, policy details, threat model, integrations, and evaluation methodology — live under docs/.

The hardening contract defines the guarantees, deliberate policy limits, and regression strategy across stages.

Install

pip install llm-input-hardening

Optional extras:

  • pip install "llm-input-hardening[security]" for the confusable_homoglyphs backend
  • pip install "llm-input-hardening[ftfy]" for pre-sanitize text repair

Determinism note: output is reproducible for pinned code and dependency versions. use_ftfy=True inspects the original input before running ftfy.fix_text(normalization=None) and sanitizing the repaired text. Rust owns bounded normalization, including when repair is enabled. Reports retain pre-repair security evidence; ftfy version changes can still change output and reports.

Import from llm_input_hardening.

Quickstart

from llm_input_hardening import sanitize

clean_text, report = sanitize(user_text, policy="balanced_chat")

Compact demo/report output:

from llm_input_hardening import compact_report, sanitize

clean_text, report = sanitize(user_text, policy="balanced_chat")
summary = compact_report(report, sanitized_text=clean_text)

Recursive JSON sanitization:

from llm_input_hardening import sanitize_json

clean_payload, report = sanitize_json(payload, policy="balanced_chat")

Enforcement-oriented usage:

from llm_input_hardening import sanitize_and_decide

clean_text, report, decision = sanitize_and_decide(
    user_text,
    sanitize_policy="strict_exec",
    confusables_backend="confusable_homoglyphs",
)

Execution fields need their own schema after normalization. For example, require an unchanged identifier from a known set:

clean_text, report, decision = sanitize_and_decide(
    tool_name,
    sanitize_policy="strict_exec",
    reject_on_change=True,
    validator=lambda value: value in {"search", "summarize"},
)
if decision["action"] != "allow":
    raise ValueError("tool name rejected")

validator always sees the returned text; validation errors propagate. Paths need application-specific root/allowlist checks, and code still needs parsing and sandboxing. strict_exec by itself does not authorize execution.

If your application joins untrusted fragments, inspect the final assembly:

from llm_input_hardening import sanitize_assembled

clean_text, report, decision = sanitize_assembled(
    retrieved_fragments, separator="\n\n", sanitize_policy="balanced_chat",
)
if decision["action"] != "allow":
    raise ValueError("assembled content requires review")

The helper joins raw fragments before inspection, with budgets for total characters and part count. Individually allowed fields do not authorize a later concatenation. Keep trusted message roles and instructions outside this helper.

The text API accepts at most 1,000,000 input characters by default (configurable with max_input_chars). Requested spans are limited to 10,000 events. JSON traversal also bounds nodes, total text, and report size; exceeding a limit raises ValueError instead of returning a partially processed result.

For ASGI applications, use ASGIPromptSanitizerMiddleware with explicit protected routes and selectors such as messages[*].content and messages[*].content[*].text. With enforce=True, uninspected requests and quarantine/reject decisions are blocked by default. See integration configuration.

Policies

Policy Normalization Default-Ignorables Whitespace tidy Typical use
preserve NFC preserve no low-friction logging and observability
balanced_chat NFC preserve yes general chat and prompt input
strict_exec NFKC strip yes tool arguments and execution-adjacent paths
code_mode NFC strip no code snippets and syntax-sensitive text

Backward-compatible aliases are still accepted: balanced, strict, and code.

Scope

Use this library for:

  • user text before prompt interpolation
  • retrieved text before it reaches the model
  • tool and function argument strings
  • JSON payloads with untrusted string keys or values

Do not use it as a substitute for:

  • system prompt design
  • tool sandboxing and allowlists
  • output validation
  • human review for high-risk actions

How It Fits With Guardrails And Red-Teams

llm-input-hardening is a text-integrity layer. It is designed to run before, inside, or alongside larger guardrail systems.

llm-input-hardening text integrity pipeline

Tool family What it is good at How this library fits
NVIDIA NeMo Guardrails Orchestrating input, output, retrieval, dialog, and tool rails; running content safety, PII, jailbreak, and model/policy checks. Use this first in an input, retrieval, or tool-input rail when you want deterministic Unicode cleanup and structured text-integrity reports before semantic checks.
Promptfoo red teaming Generating and evaluating adversarial test cases against your LLM app. Use promptfoo to test the app. Use this library as one defensive layer, then measure overlap with promptfoo static obfuscation strategies.
Model-based prompt-injection detectors Semantic classification of jailbreak or prompt-injection intent. Complementary. This library does not infer intent; it makes suspicious text properties visible and policy-decidable.

NeMo Guardrails exposes input/retrieval/tool rails, including jailbreak and safety flows. Promptfoo exposes red-team strategy transforms such as Base64, Hex, Homoglyph, and Emoji Smuggling. This package covers the deterministic text integrity part of that space; it should not be described as a replacement for either tool.

Useful references:

NeMo Guardrails Mapping

NeMo Guardrails is the orchestration layer. llm-input-hardening is a deterministic text preflight layer you can call before a NeMo flow, from a custom rail, or before handing text to a downstream model/API rail.

NeMo area Example checks or integrations What this library adds
Input rails self check input, content safety, jailbreak heuristics/model, LlamaGuard, PII detection/masking Normalizes/removes Unicode ambiguity before the rail sees text; emits report fields you can log or route on.
Retrieval rails sensitive-data checks, retrieved-document policies Sanitizes untrusted retrieved text before prompt assembly and records which sources carried obfuscation signals.
Tool input rails action/tool argument validation Applies strict_exec to execution-adjacent strings before tool calls.
Third-party guardrail integrations GuardrailsAI, Prompt Security, Pangea AI Guard, Cisco AI Defense, and similar providers Complements semantic/API checks with local deterministic cleanup and low-latency text-integrity telemetry.
Jailbreak detection heuristics/model perplexity-style heuristics or model/NIM-based jailbreak detection Different layer. This library does not decide semantic jailbreak intent; it removes and flags obfuscation that can distort prompt/log/tool text.

Promptfoo Static Obfuscation Strategy Mapping

This is not a certification claim or a claim that this package passes promptfoo's full red-team suite. Treat it as a starting map for overlap with promptfoo static obfuscation strategies.

Promptfoo strategy This library behavior Notes
Homoglyph Flags mixed-script and whole-script confusables, including all-Cyrillic words that fold to ASCII-Latin skeletons. Execution policies reject. Chat quarantines isolated/Latin-context or all-confusable text; native-prose candidates remain observable without automatic quarantine. The optional backend adds broader data.
Emoji Smuggling strict_exec strips default-ignorable characters such as emoji variation selectors and ZWJ chains. balanced_chat preserves normal emoji behavior but still reports default-ignorable signals. Use strict_exec for tool args and execution-adjacent strings; use balanced_chat for normal chat UX.
Base64 Flags base64-shaped blobs. It does not decode the blob or classify decoded intent.
Hex / alt encodings Flags contiguous hex, promptfoo-style space-separated byte hex, ascii85/base85, and long binary-byte strings. It does not decode the payload or classify decoded intent.
Indirect prompt injection Helps clean and report Unicode/control-character obfuscation in retrieved text before prompt assembly. It does not decide whether visible natural-language instructions are malicious.
Multi-input red teaming Sanitizes JSON string keys and values with sanitize_json() and rejects sanitized-key collisions. It does not reason about cross-field authorization or role confusion.

See examples/promptfoo-redteam-overlap/ for a small overlap harness that imports promptfoo's built-in static strategy transforms and classifies which generated payloads this package catches. The checked smoke fixture currently catches base64, hex, homoglyph, and emoji.

Homoglyphs And Invisible Text, Briefly

Some attacks do not rely on clever prose. They rely on text that humans and machines see differently.

Homoglyph and default-ignorable visual guide

Pattern What happens Example shape What this library does
Bidi controls Text can render in a different visual order than its logical order. abc\u202Edef Removes bidi controls and reports bidi_control.
Zero-width characters Text can contain hidden separators or payload markers. pay\u200bload Removes known junk invisibles; strict policies strip broader default-ignorables.
Homoglyphs Characters from different scripts can look like familiar Latin text. раypal using Cyrillic р and а Flags mixed-script confusables; enforcement can reject.
Whole-script homoglyphs Every letter can be replaced while the word remains single-script. all-Cyrillic раураӏ Flags whole_script_confusable; stores the ASCII-Latin skeleton.
Styled forms Compatibility letters or digits can look like ordinary ASCII. mathematical bold, circled, small-caps, fullwidth 100 Flags confusable_styled; strict_exec may also fold forms via NFKC.
Normalization amplification One compatibility character can expand to many normalized characters. repeated U+FDFA Streams normalization into an 8x/minimum-128 cap and rejects if truncated.
Emoji variation selectors / joiners Emoji can carry invisible formatting characters or joiner chains. heart variation selector, family ZWJ sequence balanced_chat preserves normal emoji UX; strict_exec strips default-ignorables.
Encoding-shaped blobs Payloads can be hidden in encoded-looking strings. long base64, hex, ascii85, binary-byte strings Flags the shape; does not decode or classify intent.

The mental model:

  1. Normalize text by policy.
  2. Remove characters that make prompt/log/tool text ambiguous.
  3. Flag suspicious shapes that should affect logging or enforcement.
  4. Keep semantic safety checks separate.

CLI

llm-input-hardening sanitize --text "abc‮def"
llm-input-hardening inspect --text "abc‮def"
llm-input-hardening inspect --compact --text "abc‮def"
llm-input-hardening decide --text "abc‮def"
llm-input-hardening decide --compact --text "abc‮def"

Development

uv sync --all-extras
uv run maturin develop --release
uv run python -m pytest -q

Benchmarks And Bakeoff

The repo has two different evaluation paths.

  • bench/run_benchmarks.py is a timing harness. It measures sanitizer latency and throughput on fixed synthetic inputs.
  • bench/run_bakeoff.py is an evaluation harness. It runs labeled corpus cases, checks expected detections and postconditions, and computes precision, recall, benign-change rate, and stability signals.

Use the Make targets below:

make bench
make bench-plots

make bakeoff-smoke
make bakeoff-smoke-assert

make bakeoff-full
make bakeoff-full-assert
make bakeoff-diff
make bakeoff-diff-assert

What they mean:

  • bakeoff-smoke is the fast local check. It evaluates only llm-input-hardening and writes results to bench/results/bakeoff_smoke_latest.json.
  • bakeoff-full is the heavyweight comparison run. It installs llm-guard and confusable-homoglyphs, then writes comparable results to bench/results/bakeoff_latest.json.
  • bakeoff-diff compares the current full run to a reviewed schema-4 baseline (bench/results/bakeoff_baseline_schema4.json, or BAKEOFF_BASELINE=...). Establish that baseline after reviewing a full run; historical schema-2 results are intentionally not comparable. See baseline migration.

Generated reports stay under bench/results/.

Repo Notes

  • CONTRIBUTING.md covers contribution mechanics.
  • SECURITY.md covers reporting and threat assumptions.
  • SCOPE.md explains what the project is and is not trying to solve.

Metadata

Release files for llm-input-hardening 2.0.0

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

Source distribution (sdist)

Source distribution for llm-input-hardening 2.0.0
File Size Uploaded
llm_input_hardening-2.0.0.tar.gz 68.4 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for llm-input-hardening 2.0.0
File
llm_input_hardening-2.0.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
llm_input_hardening-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl CPython 3.10 abi3 Linux musl 1.2+ x86-64 Details
llm_input_hardening-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
llm_input_hardening-2.0.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
llm_input_hardening-2.0.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
llm_input_hardening-2.0.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
llm_input_hardening-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 3.5 MB

Release files / llm_input_hardening-2.0.0.tar.gz

Download URL llm_input_hardening-2.0.0.tar.gz
Size 68.4 kB
Tags Source
SHA-256 checksum
How to use checksums
28409d7695ad21014b649e4d79f74e629db6a85ca0d8d3f7a192633dbd9aba19
BLAKE2b-256 checksum
How to use checksums
abc3932371f5fdc6f69fe13c307a2950aa59bfe6d3ef723ab079bdfc89c2d898
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 Sep 6, 2026.

Transparency log

Release files / llm_input_hardening-2.0.0-cp310-abi3-win_amd64.whl

Download URL llm_input_hardening-2.0.0-cp310-abi3-win_amd64.whl
Size 326.1 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
8c6839a7b09c805109a9ff6a018c8f8da0ae6524b4cd7ede1945f6f063fbd8fe
BLAKE2b-256 checksum
How to use checksums
bc0ca224a9f212b84c05dfcf1d1a655cac7fb01693d60c626a738181f081d6dd
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 Sep 6, 2026.

Transparency log

Release files / llm_input_hardening-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl

Download URL llm_input_hardening-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl
Size 678.8 kB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
bd39d8756a647c6e83f90b0dfb7ee91f5a5ace1097d096cabc4c1d117777f036
BLAKE2b-256 checksum
How to use checksums
ff855a5bc23e72a3e9c8a3902c5be6490bea8a7cc080df67f91738d996617522
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 Sep 6, 2026.

Transparency log

Release files / llm_input_hardening-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl

Download URL llm_input_hardening-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl
Size 642.1 kB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
1589d78d41419fd1c1daa6d3bcc86710915b5f477aeb85608dda4d17eb46a81d
BLAKE2b-256 checksum
How to use checksums
f898f261c4d7a06d8feca7d90cfbcdf56ce8a63721a8a537bfeef62ad98dd2be
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 Sep 6, 2026.

Transparency log

Release files / llm_input_hardening-2.0.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL llm_input_hardening-2.0.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 477.3 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
71c997ef79d205d20f9f07f8c5d052d42be995cf1b424aab3536c993dbf7f899
BLAKE2b-256 checksum
How to use checksums
4479b5d7c56b7c882f163db49d0bcdaae445333beac91575f66759bb299d044f
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 Sep 6, 2026.

Transparency log

Release files / llm_input_hardening-2.0.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL llm_input_hardening-2.0.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 466.0 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
04eaf97d691702912348b34171cc6a92770000c7ded84a06c1d15d417c547c05
BLAKE2b-256 checksum
How to use checksums
ba44677101ae52b2986d963d57ec29b9a7e57bcddee1d33afd6d7c7f0f2c9729
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 Sep 6, 2026.

Transparency log

Release files / llm_input_hardening-2.0.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL llm_input_hardening-2.0.0-cp310-abi3-macosx_11_0_arm64.whl
Size 428.3 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
aef829633d190c91cdeb35aa8ff9e20e4bd784c204dee87ed4522361f069869c
BLAKE2b-256 checksum
How to use checksums
3ad9f0a0b751c28291c92d60318f895f6e3190cbe82e14336cb5f1c1fc519764
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 Sep 6, 2026.

Transparency log

Release files / llm_input_hardening-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL llm_input_hardening-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 434.2 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
b22e89f6b4e12ace2c57bb1d90c0733faf9ff45b4cce36fa21d645fffa865dc4
BLAKE2b-256 checksum
How to use checksums
2df1bd3d9dc7d2d7a2f5e3b330035cd42396dd55fc322982c5acd4bfb20b1d4e
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 Sep 6, 2026.

Transparency log

Release history Release notifications | RSS feed

3.0.0

8 release files

This release

2.0.0 This release

8 release files

1.2.0

4 release files

1.1.0

4 release files

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