Guardrails for
what a model reads and writes.
A retrieved page can read “Summarise this page.” to you and still carry an instruction to the model, spelled out in unicode characters that no reader, terminal, log viewer, or diff will ever show. jamjet-guardrails is a zero-dependency Python library that checks text going into a model and text coming out of it, and returns allow, redact, or deny with provenance on every decision.
Unicode tag characters mirror ASCII byte for byte and render as nothing. A retrieved page, a PDF, a support ticket can carry an instruction inside them, and no reader, terminal, log viewer, or diff will show it to you.
Not because the classifier is inaccurate. A tokenizer collapses a run of these characters into one unknown token at any length, so overwriting the hidden message with a different one of the same length leaves the token ids unchanged. The payload's content never reaches the model reading it.
A semantic classifier is itself a model you have to trust to catch the thing you do not trust. This library sits before that question comes up. It inspects the encoding, not the meaning.
Two published prompt-injection models were run over this project's own corpus. Both scored far below the deterministic check, for the mechanical reason above, not a quirk of one test set.
Input and output, every time.
injection-structural Instruction smuggling in the encoding, not the words: invisible tag characters, unbalanced bidirectional control characters that make text render differently from how it parses, and zero-width steganography.
pii Email addresses, card numbers, US social security numbers, and phone numbers, redacted to typed placeholders rather than just flagged.
secrets Credentials matched on their issuer prefix, not by scoring entropy. That is what keeps a git SHA or a UUID from tripping it.
rules The caller's own regular expressions, banned substrings, and size limits: your rules, running on this engine.
Every check here is eligible in both directions, so the same check can guard what goes into the model and what comes back out. You call the chain once per direction and choose which checks run where. Every decision carries the SHA-256 of the exact text the check inspected, so a decision made today can be tied back afterward to the exact text it was made about.
One chain, two directions.
The same three checks run on what comes in and what goes out. Denying an instruction smuggled into retrieved text is one call. Redacting a credential leaking out of a reply is the next.
chain = build_chain(["injection-structural", "pii", "secrets"])
incoming = chain.run(smuggled, Context(direction="input", origin="retrieved"))
# deny, INVISIBLE_TAG_CHARS (20, 52), injection-structural
reply = chain.run("mail [email protected] and use sk-...", Context(direction="output", origin="model"))
# redact, "mail [REDACTED:EMAIL] and use [REDACTED:OPENAI_KEY]" Precision and recall, published with the misses.
Each check ships with a labelled corpus committed beside its detector. Precision and recall are measured in continuous integration on every change, and the misses are published next to the scores. A change that moves a number fails the build until a human commits the new number in the same pull request.
Numbers measured on a corpus this project wrote are reported separately from numbers measured on one it did not. The two are never merged into a single blended score.
| Check | Corpus | Cases | Precision | Recall |
|---|---|---|---|---|
injection-structural | in-repo | 154 | 0.972 | 0.873 |
pii | in-repo | 81 | 0.631 | 0.872 |
pii | nvidia/Nemotron-PII, third party | 300 | 0.960 | 0.997 |
rules | in-repo | 40 | 1.000 | 1.000 |
secrets | in-repo | 39 | 0.957 | 0.880 |
The in-repo pii corpus is a deliberate stress set, written to hold the shapes the detector is worst at, so its precision is lower than on ordinary text on purpose. Read the nvidia/Nemotron-PII row for how it performs on ordinary text.
The rules row is not comparable to the other three. injection-structural, pii, and secrets are heuristics over open-ended text, and their numbers say how often the heuristic is right. rules is a deterministic engine run against a fixed set of the caller's own rules: a perfect score means the engine computes spans and limits correctly. It says nothing about whether any given rule is well chosen.
Three layers. This is layer one.
This is not a broad guardrails framework. It is the deterministic content layer: the checks you can run before you trust a model, without trusting another model to perform the check.
This library. It inspects the bytes and structure of what goes into a model and what comes out. No model call, no network call, runs in process.
Prompt injection by meaning, jailbreaks, harmful content. Judging meaning needs a model, and that model is deliberately not bundled here.
Deciding whether a call to a tool or an external system is allowed to run at all. A different problem, and a different JamJet product.
Running in one line.
pip install jamjet-guardrails - Zero runtime dependencies, asserted against the built wheel
- No network calls, no model downloads
- Python 3.10 through 3.14
- Apache-2.0
Need more than a content check?
jamjet-guardrails is one deterministic layer. JamJet is the runtime underneath your agent for policy, approval, audit, crash recovery, and memory.