You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

prompt-injection-v5-20260827

Binary classifier for prompt injection and jailbreak detection, including attacks planted in retrieved documents, log dumps and tool results. Fine-tuned from jhu-clsp/mmBERT-base on vijil/pi_v5_multilingual_20260827.

label 1 = prompt injection or jailbreak ยท label 0 = benign.

Internal / private. Trained on vijil in-house and customer-derived data.

Results

  • Balanced accuracy 0.978 on a 75,011-row held-out test split (51.3% positive)
  • FPR 2.31% ยท FNR 2.16% at threshold 0.5

Anti-shortcut checks

  • counterfactual flip rate 0.022 (a spurious edit โ€” casing, padding, homoglyphs โ€” should not change the label)
  • bare-keyword max P(injection) 0.062 ยท benign-trigger FP rate 0.000

Serialization-format invariance

The same benign fact rendered in 14 encodings (prose, JSON, CSV, SQL result, YAML, XML, stack trace, HTTP response, MCP / OpenAI / Anthropic / JSON-RPC envelopes). Spread: 0.0100. The previous version spread 0.90 across these, which is what caused the false positives this model was built to fix.

encoding P(injection)
bare_ops_fragment 0.0493
bare_prose 0.0476
xml 0.0422
markdown_table 0.0421
yaml 0.0420
raw_json 0.0419
openai_tool_msg 0.0419
mcp_content_block 0.0417
stacktrace 0.0412
sql_result 0.0411
anthropic_tool_result 0.0411
jsonrpc 0.0407
http_response 0.0406
csv 0.0392

Field regression fixture

The six cases from the production report that motivated this version. The prior model false-positived on the benign ones while catching the injections; this model does both.

case expected score verdict
control_direct_injection 1 0.9604 โœ…
caseA_bare_ops_line 0 0.0493 โœ…
caseB_mcp_grafana 0 0.0431 โœ…
caseC_mcp_empty 0 0.0429 โœ…

Usage

Chunk long inputs โ€” do not truncate. Log and tool-result text tokenises at roughly 7.4 tokens per word (timestamps, hex trace-ids, UUIDs), so a 250-word log block is already ~2,300 tokens. Scoring only the first window and discarding the rest silently misses any injection in the tail: measured FNR on injected logs was 0.38 truncated vs 0.004 with a sliding window.

import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer

tok = AutoTokenizer.from_pretrained("vijil/prompt-injection-v5-20260827")
model = AutoModelForSequenceClassification.from_pretrained("vijil/prompt-injection-v5-20260827").eval()

MAX_LEN, STRIDE, THRESHOLD = 1024, 960, 0.9

def windows(text):
    ids = tok.encode(text, add_special_tokens=False)
    if len(ids) <= MAX_LEN - 2:
        return [text]
    out, i = [], 0
    while i < len(ids):
        out.append(tok.decode(ids[i:i + MAX_LEN - 2], skip_special_tokens=True))
        if i + MAX_LEN - 2 >= len(ids):
            break
        i += STRIDE
    return out

def score(text: str) -> float:
    enc = tok(windows(text), truncation=True, padding=True,
              max_length=MAX_LEN, return_tensors="pt")
    with torch.inference_mode():
        p = torch.softmax(model(**enc).logits, -1)[:, 1]
    return float(p.max())          # any-positive aggregation

flagged = score(my_text) >= THRESHOLD

Choosing a threshold

threshold recall FPR precision
0.30 0.9798 0.0685 0.9378
0.50 0.9784 0.0231 0.9781
0.70 0.9768 0.0199 0.9810
0.80 0.9752 0.0174 0.9834
0.90 0.9648 0.0103 0.9900
0.95 0.9310 0.0024 0.9976
0.98 0.0000 0.0000 0.0000
0.99 0.0000 0.0000 0.0000

A single global threshold is not right for every input size. Aggregation is any-positive max-pool, so block-level false positives compound with the number of windows: at ~7.4 tokens/word a 32 KB payload is 30+ windows. Prefer a higher threshold (or length-aware aggregation) for large inputs.

The raw softmax is not a calibrated probability. Training used label smoothing, so scores saturate near 0.96 and nothing reaches 0.98 โ€” do not treat "score > 0.98" as reachable, and do not quote a fixed benign/attack score range as a contract. Pick an operating point from the sweep above against traffic that resembles yours.

Limitations

  • Bare harmful content is deliberately de-emphasised. Unframed policy-violating requests were capped to ~7% of the positive class, because content moderation is a separate classifier. Expect reduced sensitivity on bare-harmful benchmarks (measured FNR ~0.40 on BeaverTails).
  • Low-resource languages lag. Per-language balanced accuracy drops to ~0.82โ€“0.89 for ja, ha, ig, so, vi, where the training data is benign-dominated.
  • Whole-message obfuscation raises false positives unless the inference-time normalizer runs first (it folds zero-width, zalgo, full-width, Latin diacritics and homoglyphs). Without it, false positives on those surfaces are materially higher, and combining-mark styles also inflate token counts and therefore window counts.
  • Punctuation-stripped benign conversational resets are the closest remaining failure mode (e.g. Ignore the first result and show the second one without the full stop).
  • Synthetic composition cannot fully reproduce real attacker creativity; evaluate on a held-out slice of your own traffic before deploying.

Training

Anti-shortcut recipe: model selection on worst-group balanced accuracy (not F1), group-balanced sampling, Group DRO over (framing ร— container ร— language), label smoothing, and token dropout. See classifiers/llm_security/prompt_injection/v4/ in the post_training repo for the full pipeline, and v4_diagnostics/ for the probes that produced the numbers above.

Downloads last month
716
Safetensors
Model size
0.3B params
Tensor type
F32
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for vijil/prompt-injection-v5-20260827

Finetuned
(156)
this model