[package]
name        = "llm-guard"
version     = "0.2.0"
edition     = "2024"
rust-version = "1.85"
license     = "MIT OR Apache-2.0"
description = "Zero-copy guardrails for LLM input/output. Pure-Rust scanners (prompt-injection, role-override, secret leakage, PII, invisible text, deobfuscation, token limit)."
keywords    = ["llm", "safety", "guard", "prompt-injection", "security"]
categories  = ["text-processing"]
authors = ["Sriram <marirs@gmail.com>"]

[lib]
name = "llm_guard"
path = "src/lib.rs"

[dependencies]
# Multi-pattern matcher used by the BanSubstrings scanner; faster than
# N independent contains() calls and gives us byte-offset spans so the
# caller can render highlights without re-scanning.
aho-corasick = "1"

# Compiled regex for the Secrets / PII / URL scanners. Patterns are static so the
# compile happens once on first use via std::sync::LazyLock.
regex        = "1"

# Base64 decoder used by Deobfuscate. Only invoked behind a shape-gate
# (a long base64-looking run must be present), so clean scans pay zero
# decode cost. Pure Rust, no_std-capable, ~30KB compiled.
base64       = "0.22"

# Unicode TR39 confusables fold for the Deobfuscate pre-pass. Pure-Rust,
# no runtime data downloads (tables compiled in at build time).
unicode-security = "0.1"

[features]
# Opt-in fuzzy paraphrase matching. Adds the FuzzyMatch scanner which
# does trigram-Jaccard similarity against a corpus of canonical
# injection phrases - catches rephrased attacks that the literal
# substring tables miss. Off by default because:
#  - it's a heuristic, not a literal match (Medium confidence, Warn
#    severity by default - feeds the audit log, doesn't auto-refuse);
#  - it shifts the per-scan cost from O(input) substring to
#    O(input * corpus_size) trigram intersection. Still microsecond
#    range, but degrades the "guaranteed sub-ms" promise we make
#    on the default build.
fuzzy = []

[lints.rust]
# `deny` rather than `forbid` so the integration test that swaps the
# global allocator can opt-in with `#![allow(unsafe_code)]`. The
# library code itself keeps `#![forbid(unsafe_code)]` declared at the
# top of src/lib.rs, so production code paths still have the harder
# guarantee.
unsafe_code = "deny"

[lints.clippy]
pedantic                = { level = "warn", priority = -1 }
module_name_repetitions = "allow"

# Profiles live at the workspace root (../../Cargo.toml). Cargo only
# honours profile sections in the workspace root; declaring them here
# would be silently ignored.
