Warden Policy Pack
This is not a neural network. There are no weights, no tensors and no training data. It is a deterministic rule pack: a declarative policy plus the configuration of a hand-written, regex-based classifier. It lives in a model repository because that is where Hugging Face versions artifacts, not because anything here was learned.
Nothing in this repository requires torch, transformers or scikit-learn. It requires nothing at all β the files are JSON.
The engine that consumes this pack is the Warden Space, a policy decision point for LLM traffic. The evaluation set that pins its behaviour is warden-eval.
What it does
Warden answers one question: may this text go to this destination, for this purpose, from
this team? The answer is allow, redact, reroute or deny, always accompanied by the
rule_id that produced it.
This pack is the part a human writes and versions:
| File | Contents |
|---|---|
policy.json |
destinations, rules, default effect, budgets |
classifier.json |
sensitive vocabulary, PII weights and threshold, entropy heuristic |
detectors.json |
the detector specification and the ledger hash definition, generated from the engine source |
What it does not promise
Warden does not guarantee legal or regulatory compliance. All it can do is apply the rules you declared, consistently, and leave a record you can verify. Deciding what the rules ought to be is a human responsibility.
The policy shipped here is an example, not a recommendation. The destinations are fictional ("Provider A", "Provider B"), the sensitive vocabulary is a starting list, and the budgets are placeholders. Replace all three before any real use. A policy you inherited and never read cannot be applied consistently, which is the only thing this tool is for.
Usage
import json
from huggingface_hub import hf_hub_download
policy = json.load(open(hf_hub_download("NagaYu/warden-policy-pack", "policy.json")))
classifier = json.load(open(hf_hub_download("NagaYu/warden-policy-pack", "classifier.json")))
policy["classifier"] = classifier # the engine reads the classifier config from the policy
Paste the result into the Warden Space's Policy tab and press Validate, then Validate and activate.
The published Space is a static build: the engine runs in your browser, so the policy is activated locally in that tab and there is no HTTP API. Against your own Gradio deployment of the engine, the same thing over the API is:
from gradio_client import Client
client = Client("<user>/<space>")
print(client.predict(json.dumps(policy), api_name="/validate_policy"))
print(client.predict(json.dumps(policy), api_name="/save_policy"))
Always validate before activating. Validation reports duplicate rule IDs, references to undefined destinations, and rules that can never fire because a broader, higher-priority rule covers them.
Policy format
{
"version": "2026-09-01",
"destinations": {
"vendor-a-large": {"provider": "A", "region": "us", "retention": "30d"},
"self-hosted": {"provider": "self", "region": "jp", "retention": "none"}
},
"rules": [
{"id": "R001", "priority": 10,
"match": {"subject": ["team-hr"], "level": ["confidential", "regulated"],
"destination_provider": ["A", "B"]},
"effect": "deny", "reason": "HR must not send confidential data to an external provider"}
],
"default_effect": "deny"
}
- Match dimensions:
subject,level,destination,destination_provider,destination_region,purpose. An omitted dimension, or["*"], means unconstrained. An empty array is rejected rather than read as "matches everything" β that reading turns a forgotten value into a silent blanketallow. - Effects:
allow,redact,reroute,deny. Lowestprioritynumber wins; ties break on document order.reroutereturns only the alternative destinations the policy actually permits. Add"reroute_to": [...]to narrow the suggestions. - Budgets:
hard/softare token counts.0means zero, not unlimited β write a negative number for unlimited. Only the keys you write are overridden, and whenhard < softthe soft limit is lowered to the hard limit, never the reverse.
No credential can live in this file
A destination may declare only provider, region, retention, label and notes. A field
whose name looks like a credential is a hard validation error β and so is a value anywhere
in the document that looks like one: a known key prefix (sk-, ghp_, AKIA, xoxb-,
AIza, β¦), a JWT, a PEM private-key block, or a password = β¦ assignment. Renaming the field
does not help, and neither does pasting a key into a free-text notes or reason.
This is enforced by a test, not by convention. Warden is a decision point, not a place to keep secrets.
Classifier configuration
{
"sensitive_terms": ["employment rules", "performance review", "medical record", "..."],
"pii_threshold": 2,
"pii_weights": {"credit_card": 2, "email": 1, "phone": 1, "ipv4": 1,
"date_of_birth": 1, "postal_address": 1},
"entropy_threshold": 3.7,
"entropy_min_len": 32
}
Levels are ordered public < internal < confidential < regulated:
- any credential signal β
regulated - PII weight sum β₯
pii_thresholdβconfidential - any sensitive term, and nothing stronger β
internal - otherwise β
public
Detectors (all local, hand-written, no model): email, phone, credit card (only when it passes
the Luhn check), IPv4, date of birth, postal address, known credential prefixes, JWT triples,
and long alphanumeric strings whose Shannon entropy exceeds the threshold. detectors.json
carries the exact patterns.
Detection runs twice β once on the raw text and once on its NFKC normalization β because
full-width text (οΌ‘οΌ«οΌ©οΌ‘β¦) does not match the raw patterns and would otherwise walk straight
through. Character spans come from the raw pass only, since normalization can change string
length; anything found only after normalization is reported with an empty spans list and an
explicit line in reasons.
Every classification returns its evidence: signal types, counts and character spans. A classification you cannot inspect is a classification you cannot challenge. Samples for credential signals report only the type and length β never any characters of the secret.
Tuning
| Symptom | Knob |
|---|---|
Too much is confidential |
raise pii_threshold, or zero out a pii_weights entry |
Too much is regulated |
raise entropy_threshold (3.7 β 4.2) or entropy_min_len (32 β 48) |
| Wrong vocabulary for your org | replace sensitive_terms wholesale |
The shipped sensitive_terms list is bilingual (English and Japanese) because a vocabulary
list is data, and a bilingual one simply covers more traffic. Terms match case-insensitively;
ASCII terms additionally require a word boundary, so nda does not match agenda.
Ledger hash definition
detectors.json also carries the ledger's hash definition, so an auditor can verify an
exported ledger without running Warden at all:
HASH_FIELDS = [seq, timestamp, subject, level, destination,
verdict, rule_id, content_hash, prev_hash]
HASH_INPUT = "|".join(json.dumps(v, ensure_ascii=False, sort_keys=True) for v in HASH_FIELDS)
hash = sha256(HASH_INPUT.encode("utf-8")).hexdigest()
Each field is JSON-quoted before being joined, so a literal | inside a value cannot forge a
different field split. The first record's prev_hash is "0" * 64. The ledger never stores
the text β only sha256 of its NFKC-normalized, whitespace-collapsed form.
Evaluation
warden-eval holds labelled cases that
pin this pack to its documented behaviour: expected classification level per text, and expected
verdict and rule_id per decision. Run it after any change to the vocabulary, the thresholds
or the rules.
License
Apache-2.0.