| --- |
| 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`](https://github.com/flowx-ai/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 |
|
|
| Three corpora in, the false positives on ordinary customer traffic are gone. Same |
| architecture and hyperparameters throughout; only the corpus moved. |
|
|
| Measured through the shipped configuration at threshold 0.43: |
|
|
| | | v3 | v4 | **v5, this one** | |
| |---|---|---|---| |
| | ordinary support questions it fires on | 7 of 12 | 1 of 12 | **0 of 12** | |
| | technical identifiers, at 0.43 | 4 of 4 | 0 of 4 | 1 of 4 | |
| | technical identifiers, at 0.95 | 4 of 4 | 0 of 4 | **0 of 4** | |
| | the three canonical attacks | 3 of 3 | 3 of 3 | **3 of 3** | |
| | mean per-language F1 | 0.9755 | 0.9855 | **0.9891** | |
| | worst language | – | mt 0.8367 | **mt 0.8817** | |
|
|
| v5 added one register, `mundane_account_access`, and it lives in the corpus generator's |
| shared mundane set so `moderation` and the five single-label classifiers inherit it too. The |
| gap it fills was invisible because the three registers already there are all prose *about* |
| things in the third person, a password reset notice or an appointment booking. None of them |
| was a customer speaking, so "How do I reset my password?" was out of distribution for every |
| corpus anchored on them, and two detectors independently learned to treat customers as |
| hostile. |
|
|
| **It is not a clean sweep, and the card should say so.** A bare UUID reads |
| `direct_injection` at 0.944 under v5, clearing 0.43 where v4 had it at zero. It stays below |
| 0.95. Net across both shapes v5 is ahead and the regression is real. |
|
|
| 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.9528 | 0.9957 | 0.9738 | 0.0057 | |
| | `indirect_injection` | 0.9709 | 0.9901 | 0.9804 | 0.0021 | |
| | `jailbreak` | 0.9367 | 0.9850 | 0.9603 | 0.0077 | |
|
|
| Mean per-language F1 0.9891. Weakest are `mt` 0.8817, 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 |
|
|
| 45,541 examples: 12,285 attacks and 33,256 benign, 27.0 percent attack share, generated with |
| `gpt-oss:120b`. 26 languages evenly at 1,656 to 1,690 rows each. 19 registers, including `technical_identifiers` and |
| `technical_payload`, and four `mundane_*` registers shared with the other classifiers in |
| this family, of which `mundane_account_access` is v5's addition. |
|
|
| 276 generated rows were rejected before writing: 127 placeholders, 77 duplicates, 50 in the |
| wrong script for their language, 22 outside their length band. |
|
|
| ## Known weakness |
|
|
| A bare UUID reads `direct_injection` at 0.944, which clears the shipped 0.43 and not 0.95. |
| v4 had it at zero, so this is a regression on the technical shape bought alongside a fix to |
| the account-access one. Both are corpus properties rather than thresholds. |
|
|
| The account-access failure v4 could not fix is gone: *"Please cancel my subscription."* read |
| `direct_injection` at 0.9775 under v4, identical at 0.43 and 0.95 so no threshold reached it. |
| Two of 35,025 rows matched that phrasing then; v5 has 1,862 rows in the account-access |
| register and 16 carrying that exact phrasing as benign. |
|
|
| ## 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.00005, max 0.15393, and the max is one row where the p99 is what the gate reads. `onnx/export_manifest.json` carries the |
| weight hashes. |
|
|
| ## Licence |
|
|
| Apache-2.0. Trained on synthetic data generated for this purpose. |
|
|