license: apache-2.0
library_name: onnx
pipeline_tag: text-classification
tags:
- prompt-injection
- jailbreak-detection
- guardrails
- onnx
- multilingual
language:
- bg
- hr
- cs
- da
- nl
- en
- et
- fi
- fr
- de
- el
- hu
- ga
- it
- lv
- lt
- mt
- pl
- pt
- ro
- sk
- sl
- es
- sv
- tr
- az
base_model: FacebookAI/xlm-roberta-base
injection
Prompt injection and jailbreak detection over 26 languages. XLM-RoBERTa base, three
independent labels, INT8 ONNX, CPU. Built for
flowx-border, where it is the T2 injection
detector.
Labels are multi-label rather than exclusive: direct_injection, indirect_injection,
jailbreak. A single text can be more than one.
What changed in this version, and why it is the headline
The previous artifact scored well and was unusable at its own shipped setting. Same architecture, same hyperparameters; the corpus went from 9,325 train rows to 35,025 and from conversational benign prose only to 18 registers including technical text.
Measured through the shipped configuration at threshold 0.43:
| previous | this version | |
|---|---|---|
| ordinary support questions it fires on | 7 of 12 | 1 of 12 |
| technical identifiers it fires on | 4 of 4 | 0 of 4 |
| the three canonical attacks | 3 of 3 | 3 of 3 |
| mean per-language F1 | 0.9755 | 0.9855 |
The previous model read a bare UUID, a git commit hash, a data URI and a sha256 digest as
jailbreak or direct_injection, and read "Someone is using my account, how do I lock
it?" as direct_injection at 0.98. Since the detector ships on_fail: block, that made the
default policy refuse most of what a support assistant is asked. Both classes of false
positive came from the same corpus property: every benign register was conversational prose,
so an imperative request and a high-entropy identifier were equally out of distribution.
Evaluation
4,327 held-out rows, threshold 0.43, max_length 96.
| label | precision | recall | F1 | FPR |
|---|---|---|---|---|
direct_injection |
0.9546 | 0.9936 | 0.9737 | 0.0057 |
indirect_injection |
0.9495 | 0.9934 | 0.9710 | 0.0040 |
jailbreak |
0.9331 | 0.9871 | 0.9593 | 0.0085 |
Mean per-language F1 0.9855, range 0.8367 to 1.000 over 26 languages. Weakest are mt
0.8367, then ga 0.9762 and cs 0.9767. Maltese is not in XLM-RoBERTa's pretraining set,
and that is a fact about the base model rather than a diagnosis: the same gap in another
detector here closed entirely on corpus size alone, so read 0.8367 as a number to improve
and not as a ceiling.
Corpus
43,679 examples: 12,285 attacks and 32,922 benign, 28.1 percent attack share, generated with
gpt-oss:120b. 26 languages evenly at 1,656 to 1,690 rows each. 18 registers, including
technical_identifiers and technical_payload, which exist because of the failure above,
and three mundane_* registers shared with the other classifiers in this family.
266 generated rows were rejected before writing: 127 placeholders, 67 duplicates, 50 in the wrong script for their language, 22 outside their length band.
Known weakness
One ordinary support question still fires: "Please cancel my subscription." reads
direct_injection at 0.9775. It scores the same at 0.43 and at 0.95, so no threshold helps,
and the shape is an imperative request to perform an account action, which is what an
instruction override also looks like on the surface.
The corpus contains almost nothing of this shape. Two of 35,025 rows match account-access phrasing. So the residual is a corpus gap rather than a model limitation, and the fix is an account-access benign register rather than a threshold.
Use it at 0.43
The library ships 0.43, above this run's calibrated 0.02. The calibration deliberately is not adopted: its own report flags 0.02 as the lowest value in the sweep, which compresses scores toward zero, and macro F1 is 0.9671 even at 0.95, so the sweep is a plateau rather than a peak. A missed injection costs more than a review, so the shipped value sits well below the plateau's top without chasing the floor.
Export
INT8 with the Gather-only quantisation recipe. 0 of 300 decisions changed against the fp32
graph, probability drift p99 0.00004, max 0.00767. onnx/export_manifest.json carries the
weight hashes.
Licence
Apache-2.0. Trained on synthetic data generated for this purpose.