Loadingβ¦
Loadingβ¦
answerdotai/ModernBERT-baseIronJean WAF v2.5 is an enterprise-grade, transformer-based Web Application Firewall (WAF) classification model engineered on top of ModernBERT-base and purpose-built for REST API Gateways and modern backend microservices.
It combines Vectorized Landmark Pooling, Multi-Pass Recursive Anti-Evasion Normalization, and Exploit-Aware Network Landmark Anonymization, leveraging ModernBERT's 8,192-token context window (RoPE + Flash Attention / Unpadding) to inspect complete HTTP headers and API request bodies without truncation, delivering deterministic, sub-millisecond threat detection across structured API payload trees without lexical keyword overfitting.
Unlike traditional regex rules, legacy 512-token BERT classifiers, or naive [CLS] sequence transformers, IronJean WAF is specifically tailored to the semantic grammar of REST API protocols (REST/JSON, GraphQL, XML/SOAP, Form/Multipart, gRPC, OAuth2/JWT) while achieving a benchmarked 100.00% Global Accuracy (100.00% Precision / 0.00% FPR, 100.00% Recall / 0.00% FNR) across 160,500 payloads sourced exclusively from fully cleaned open-source and synthetic datasets as part of my independent research.
Supported Attack Vectors: 22 attack families, modern 2024β2025 CVEs, and evasion techniques natively detected out-of-the-box by IronJean WAF v2 across all 4 HTTP landmarks (METHOD, URI, HEADERS, BODY).
Zero-Forgetting WAF Domain Adaptation: ModernBERT Landmark Pooling + WiSE-FT + Swagger Synthesizer ingests any local or remote Swagger 2.0 / OpenAPI 3.x contract, endpoints, path parameters, query types, and JSON/XML/Form schemas.
π οΈ Pre-Trained WAF Baseline & Transfer-Learning Checkpoint: Rather than a rigid black-box rule engine, IronJean v2 is designed as an open WAF baseline checkpoint. Having already learned HTTP landmark structure and common OWASP exploit patterns, it provides a solid starting point for Shadow Mode evaluation, threshold calibration, or lightweight domain adaptation (e.g., via WiSE-FT) on your own production telemetry.
HTTPNormalizer v2.5: Pre-compiled LRU regex engine (maxsize=2048), multi-block/double-encoded MySQL /*!...*/ comment unwrapping, UTF-16LE/ASCII hex SQL literal decoding (0x...), standalone WAITFOR DELAY canonicalization, strengthened Cookie/Redirect/CMD guards, and strict key=value raw-payload framingβvalidated across 204/204 production Postman-Echo tests.v34b): Balanced level-3 WordPress /blog/* sub-routes (wp-content/themes, uploads, plugins, wp-includes/js, my-account) to ~50/50 Safe/Attack parity to eliminate sub-route prior skew.eval_loss: 0.000015) & SOTA Benchmarks: Achieved 100.00% (1.0) Validation Accuracy, Precision, Recall, and F1 (1.51e-5 loss β 99.9985% confidence), 100.00% on v34b (160,500/160,500 at 719.25 req/s), waf-bench (77/77), gotestwaf (822/822), and openappsec-attacks (73,924/73,924), 99.93% on shengqin-waf (0 FP at 0.85 prod threshold), and 100.00% Precision (0 FP) on http-attack-requests.v34 β v34b): Early low-LR warmup alignment of the classification head combined with lossless Cookie: exploit preservation (HTTPNormalizer v2.5) eliminated contradictory [SEP] header signals and late Focal Loss (Ξ³ = 2.0) gradient spikesβdriving eval_loss 4.6Γ lower at Epoch 2 (2.14e-5, 0 FP) and 2.3Γ lower at Epoch 3 (1.51e-5, zero loss spikes across Epochs 2β3).runs/): Unified async GPU/ONNX landmark_pool kernel across training and Hugging Face trust_remote_code=True, with interactive TensorBoard logs published in ./runs.π News notes v2.4 - 7/11/2026
HTTPNormalizer v2.4 Generic Rule Refactoring & Zero-ReDoS Architecture: Removed all one-off dataset patches in favor of generic, ReDoS-safe anti-evasion and canonicalization rules:& before numeric or named HTML/XSS entities (&#0000106, &lt;, &colon;), supports truncated HTML5 &perio with a query-parameter negative lookahead (?![a-zA-Z0-9_]*=) (preventing collisions with benign URL parameters such as &period=5y), and expands inline numeric entity decoding across 5-digit decimal padding and attribute delimiters.etc/(passwd|shadow|issue|hosts|group)|boot.ini|win.ini|system.ini), overlong UTF-8 %c0n dot-evasion decoding, and targeted SQL function comment unwrapping (database/**...**/() β database()) that preserves LDAP wildcard filters (/*)(uid=*))(|(uid=*/) and /**/ SQLi tokens.UNION ALL SELECT, negative-ID -\d+ UNION SELECT, standalone CONCAT(...) with SQL introspection functions (database(), version(), user(), information_schema), long hex literals (0x[0-9a-f]{24,}), boolean OR/AND x BETWEEN y AND z, || col IS [NOT] NULL, AND user_name() =, chained shell commands (& dir, | id, ; whoami), malformed self-closing event tags (<img </onload=...>), and bare/encoded < parameter probes.example.com/org/net test URLs, international postal addresses (street, number), non-OAST email addresses in query parameters, intra-word */$ typos, and leading-dot uppercase acronyms (=.DOTe) while strictly preserving Unix dotfile reconnaissance probes (.env, .npmrc, .htaccess, .dockerenv).export_onnx.py) for FP16 (model_fp16.onnx) & Dynamic INT8 (model_quantized_int8.onnx):Cast Graph Fix: Replaced legacy onnxconverter_common.float16βwhich mutated ModernBERT's Rotary Position Embedding node (/model/layers.*/attn/rotary_emb/Cast_1) without updating downstream ValueInfoProto signatures (ORT_FAIL: Type (tensor(float16)) ... does not match expected type (tensor(float)))βwith ONNX Runtime's transformer-aware onnxruntime.transformers.float16.convert_float_to_float16(onnx_model, keep_io_types=True). This produces a valid FP16 graph (285 MB, optimized for NVIDIA Tensor Core GPUs) while keeping I/O logits in tensor(float) (FP32) for direct Java DJL compatibility.100% Holdout Parity on CPU): Fixed the weight/logit collapse caused by naive QuantType.QInt8 dynamic quantization on ModernBERT (where multiplying unsigned uint8 activations by signed int8 weights overflows AVX2 vpmaddubsw 16-bit accumulators on high-amplitude GeGLU layers, while quantizing Gather and Gemm degrades token embeddings and Landmark Pooling logits). Switching to weight_type=QuantType.QUInt8 (32-bit accumulation), restricting quantization to op_types_to_quantize=['MatMul'] (keeping token embeddings in FP32), and excluding the final Landmark classification head (nodes_to_exclude=['/classifier/Gemm']) restores 100.00% (50/50) Holdout accuracy at a 254 MB footprint (~2β4 ms production CPU latency alongside native FP32).v33 Dataset): Audited and rebalanced all 14 URI prefix families (/api/v1/*, /api/*, /blog/*, /wp-*, /publico/*, /actuator/*, /nexus.*, /assets/*, /auth/*), single-letter query keys (?q=, ?s=, ?p=, ?id=), and natural-language punctuation/numeric templates to strict ~50/50 Safe/Attack parityβeliminating structural prefix and parameter-name shortcuts.0 FP, 0 FN) on the 160,500-request v33 corpus, 100.00% on waf-bench (77/77), 100.00% on gotestwaf (822/822), 100.00% on openappsec-attacks (73,924/73,924), 99.73% on shengqin-waf (1 FP / 0.10% FPR), and 98.46% on http-attack-requests with 100.00% Precision (0 FP / 0.00% FPR).π News notes v2.3 - 31/10/2026
HTTPNormalizer v2.3: Now bundled via trust_remote_code=True. Added in-distribution OPTIONS / HEAD verb canonicalization (GET when EMPTY_BODY, POST if a body is present) and Origin authority anonymization for zero-false-positive CORS preflight inspection, smart Windows CRLF (\r\n β \n) body normalization (preserving multipart/form-data and CRLF/Smuggling payloads), recursive [SEP] landmark anti-collision filtering across URI/HEADERS/BODY, deep payload inspection across non-target HTTP headers (Accept-Encoding, Accept with MIME wildcard stripping, Cache-Control, Connection), 100% deterministic User-Agent canonicalization (canonicalize_healthy_user_agent) with a configurable BLOCK_SCANNER_USER_AGENTS toggle, strengthened Cookie/UA/Host/JWT exploit guards, and new anti-evasion decoders.v28): Introduced a parametric "Dotted False-Friends" generator, symmetric root-URI containers (/?q=, /?p=, /?s=, POST / form-urlencoded), technical cybersecurity Markdown documentation (nested links, release notes, YAML/code blocks), and complex punctuation/SQL-keyword prose.CRLF command splitting, LDAP OID filter injection, and OAST SSRF/Log4Shell callback domains.π News notes v2.2 - 25/10/2026
HTTPNormalizer v2.2: Added authority IP normalization (Host/Referer β <DOMAIN>, local LAN IPs), exploit-aware <DOMAIN>/<IP> guards, and recursive \u00XX Unicode decoding.shengqin-waf (99.38%), secureai-waf (98.36%), waf-bench (96.10%), nosql-waf (94.39%), and ai-waf (89.97%).π News notes v2.1 - 20/10/2026
GET, POST, PUT, PATCH, DELETE) and Content-Type formats (JSON objects/arrays, XML/SOAP, Form, Multipart, gRPC).text/markdown (technical prose, code blocks) and text/html documents without false alarms.HTTPNormalizer v2.1: Added smart cookie/telemetry anonymization (anonymize_cookies), advanced anti-evasion decoding (Log4j, decimal IPv4, sponge-case), and non-target header inspection.π News notes v2.0 - Initial release 11/10/2026
trust_remote_code=True)Traditional sentence transformers collapse sequence representations onto the initial [CLS] token, discarding spatial boundary relationships between HTTP fields.
IronJean WAF implements Vectorized Landmark Pooling:
[SEP]) segment the request into four discrete landmarks: METHOD, URI, HEADERS, and BODY.[SEP] hidden states:$$\text{Pooled} = \frac{1}{\max\left(1, \sum \mathbf{M}{\text{SEP}}\right)} \sum{t=1}^S \mathbf{h}t \cdot \mathbf{M}{\text{SEP}, t}$$
trust_remote_code=True): Packaged with custom modeling (modeling_ironjean.py) registered under AutoModelForSequenceClassification and pipeline("text-classification"). External developers can load the full landmark-pooling architecture zero-shot without defining custom PyTorch boilerplate.model.onnx, requiring no Python runtime.Attackers frequently chain multiple encoding layers and padding buffers to evade signature inspection or overflow context windows. IronJean WAF incorporates a deterministic pre-inference unwrapping pipeline (convergence loop up to depth = 3) that:
/-delimited path tokens), ASCII Unicode escapes (\u00XX), HTML numeric/named entities, Log4j JNDI lookups (${lower:x}, ${::-x}), decimal IPv4 SSRF targets (http://2852039166), SQL inline comments (/*!50000...*/), Sponge-Case keywords (sElEcT β SELECT), and Null-Byte extension bypasses (%00.jpg β \x00.jpg).(.)\1{127,} β 32 chars) to defeat Context-Window Flooding and ReDoS while preserving buffer-overflow/fuzzing anomaly signatures.<IP> & <DOMAIN>)To prevent the neural network from memorizing environment-specific corporate hostnames, LAN subnets, or proxy topologies, authority and forwarding headers (Host, Referer, Origin, X-Forwarded-For, X-Real-IP, True-Client-IP, CF-Connecting-IP, X-Forwarded-Host) are mapped to canonical <DOMAIN> and <IP> landmarks.
169.254.169.254, 100.100.100.200, metadata.google.internal), OAST callback domains (interact.sh, oastify.com, burpcollaborator.net), Java/PHP gadget namespaces (class.module.classLoader, convert.iconv.*), and JS DOM objects (document.cookie, window.location) so attacks are never masked.OPTIONS and HEAD requests to METHOD: GET (or POST if a body is present), ensuring CORS preflights are evaluated strictly inside the model's nominal training distribution.Trained via a two-stage curriculum across 3 epochs:
Shell.me / System.Text.Json, root-URI short parameters /?q=, technical cybersecurity Markdown, and French/English apostrophes vs. SQL quotes).0 FP).To ensure a comprehensive and resilient mapping of HTTP threat vectors, the dataset was meticulously aggregated and curated from the following sources:
During training on the 160,500-request corpus (90% train / 10% hold-out validation split of 16,050 requests), IronJean v2.5 exhibits a characteristic three-epoch phase transition driven by the switch from Focal Loss to Cross-Entropy Calibration Smoothing (max-length 2048 tokens):
| Training Phase | Loss Function | Eval Loss | Eval Accuracy | Eval Precision | Eval Recall | Eval F1-Score |
|---|---|---|---|---|---|---|
| Epoch 1.0 | Focal Loss (Ξ³ = 2.0) | 0.000352 |
99.9564% |
99.9630% |
99.9506% |
99.9568% |
| Epoch 2.0 | Focal Loss (Ξ³ = 2.0) | 0.000021 |
99.9938% |
100.0000% |
99.9877% |
99.9938% |
| Epoch 3.0 | Cross-Entropy Smoothing | 0.000015 |
100.0000% |
100.0000% |
100.0000% |
100.0000% |
Behavioral Dynamics:
METHOD, URI, HEADERS, BODY) reaching 99.9564% validation Accuracy and 99.9630% Precision (eval_loss: 0.000352).eval_loss drops by 16.4Γ to 0.000021 / 2.14e-05): Focal Loss concentrates gradients on subtle Dotted False-Friends (Shell.me, Select.io), short-key parameters (?q=, ?p=), /blog/* WordPress sub-routes, and complex punctuation edge cases, already reaching 100.0000% (1.0) Validation Precision (0 FP) and 99.9938% Accuracy (16,049 / 16,050).eval_loss: 0.000015 / 1.51e-05 β 99.9985% mean probability confidence): Switching to standard Cross-Entropy under linear LR decay (1e-5) compacts [SEP] Landmark representations and widens class logit margins with zero training loss spikes, driving validation Accuracy, Precision, Recall, and F1-Score to 100.0000% (1.0 across all 16,050 validation samples) and 100.0000% Global Accuracy (160,500 / 160,500, 0 FP, 0 FN) across the full 160,500-request corpus (train_loss: 0.010006, train_runtime: 6,624.38s).Tested against internal and external Hugging Face / industry WAF benchmarks (max-length 4096 tokens, default 0.50 classification threshold):
Benchmark dataset |
Hugging Face / Source Dataset | Requests | Accuracy | Precision | Recall | F1-Score | TP | TN | FP (FPR) | FN (FNR) | Throughput (Latency) |
|---|---|---|---|---|---|---|---|---|---|---|---|
v34b (Internal) |
dataset_v34_rest_150k + 10.5k noise |
160,500 | 100.00% | 100.00% | 100.00% | 100.00% | 80,250 | 80,250 | 0 (0.00%) | 0 (0.00%) | 719.25 req/s (1.39 ms) |
waf-bench |
netgoat-ai/WAF-Bench |
77 | 100.00% | 100.00% | 100.00% | 100.00% | 55 | 22 | 0 (0.00%) | 0 (0.00%) | 308.64 req/s (3.24 ms) |
gotestwaf |
wallarm/gotestwaf(44 OWASP/API suites) |
822 | 100.00% | 100.00% | 100.00% | 100.00% | 676 | 146 | 0 (0.00%) | 0 (0.00%) | 503.03 req/s (1.99 ms) |
openappsec-attacks |
openappsec/waf-comparison-project(73.9K Multi-Vector Attacks) |
73,924 | 100.00% | 100.00% | 100.00% | 100.00% | 73,924 | 0 | 0 (0.00%) | 0 (0.00%) | 1,040.33 req/s (0.96 ms) |
shengqin-waf |
shengqin/web-attacks |
5,523 | 99.93% | 99.96% | 99.96% | 99.96% | 4,508 | 1,011 | 2 (0.20%) * | 2 (0.04%) * | 1,574.03 req/s (0.64 ms) |
http-attack-requests |
SecureAI-SE/http-attack-requests |
7,000 | 98.39% | 100.00% | 98.12% | 99.05% | 5,887 | 1,000 | 0 (0.00%) | 113 (1.88%) * | 983.45 req/s (1.02 ms) |
π‘ * Note: On third-party Hugging Face datasets (
SecureAI-SE/http-attack-requestsandshengqin/web-attacks), the remaining anomalies marked with*stem from non-executable wordlist/CSV artifacts (isolated CSIC DOM event names without=/<such asonchange/onmouseenter, browser HTML comments, Excel#NAME?export errors, or 50-char CSV-truncated JSON/prose). At the recommended production decision threshold (0.85), both 50-char truncatedshengqin-wafedge cases (0.62and0.80) are allowedβyielding 100.00% Precision (0FP across all 6 benchmarks).
Input sequences must conform strictly to the four-landmark template:
METHOD: <HTTP_METHOD> [SEP] URI: <REQUEST_URI> [SEP] HEADERS: <FILTERED_HEADERS> [SEP] BODY: <NORMALIZED_BODY>
METHOD: Standard HTTP verb (GET, POST, PUT, DELETE, PATCH; OPTIONS and HEAD are automatically canonicalized to GET or POST by HTTPNormalizer).[SEP]: Delimiter token (50282 in ModernBERT tokenizer) mapped to Vectorized Landmark Pooling.URI: Recursively decoded path and parameters (e.g. /api/v1/resource?id=10).HEADERS: Pipe-separated key-value pairs formatted by HTTPNormalizer.BODY: Recursively decoded payload, or EMPTY_BODY.To reproduce the benchmarked 100.00% Global Accuracy (100.00% Precision / 100.00% Recall) and avoid false alarms, incoming HTTP requests must pass through the IronJean Normalization Pipeline before tokenization.
This pipeline strips multi-layer evasion obfuscation, prevents neural overfitting on hostnames/IPs, enforces header length guards, and structures input into canonical HTTP landmarks.
Incoming HTTP Request
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Multi-Pass Recursive Anti-Evasion (Max Depth = 3) β
β URL / Base64 / Unicode \u00XX / HTML / Log4j unwrap β
β Anti-DoS padding compression (>128 chars -> 32) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Exploit-Aware Network Landmark Anonymization β
β Host, Referer, Origin, X-Forwarded-* -> <DOMAIN>/<IP>β
β Preserves Cloud IMDS (169.254.169.254) & OAST hosts β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Header Canonicalization & Flooding Guard (512 max) β
β Keep target + suspicious non-target headers β
β Canonicalize healthy User-Agent & Bearer tokens β
β Cookie: drop telemetry (_ga, __cf_bm), anonymize β
β session tokens (JSESSIONID=NONE), preserve attacks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Body & Token Stabilization β
β Empty/Null -> 'EMPTY_BODY', strip benign HTML5 β
β <!DOCTYPE html>, normalize benign Windows \r\n->\n, β
β strip injected [SEP] tokens & healthy JWT sigs β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Landmark Sequence Serialization β
β OPTIONS/HEAD -> GET (or POST if body present) β
β METHOD: <M> [SEP] URI: <U> [SEP] HEADERS: <H> ... β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
HTTPNormalizer β Bundled via trust_remote_code=True)To ensure exact parity with production environments and prevent false alarms, raw requests must be normalized before inference.
Because modeling_ironjean.py imports normalizer_ironjean.py via a relative import (from .normalizer_ironjean import HTTPNormalizer), Hugging Face Hub automatically downloads normalizer_ironjean.py alongside the model weights whenever trust_remote_code=True is enabled:
HTTPNormalizer = model.normalizer (or directly model.serialize_http(...) / classifier.normalizer).from normalizer_ironjean import HTTPNormalizer (also re-exported by core.normalizer and bundled in test_hf.py).HTTPNormalizer.BLOCK_SCANNER_USER_AGENTS = True to block offensive scanner banners (sqlmap, Nikto, Nuclei, OpenVAS, ffuf) even without an exploit payload (default False = Payload-Only mode).from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(
"Metabaron6/IronJean-v2-ModernBERT-WAF",
trust_remote_code=True
)
HTTPNormalizer = model.normalizer # Automatically fetched from normalizer_ironjean.py on HF Hub
sequence = HTTPNormalizer.serialize_http(
method="POST",
uri="/api/v1/checkout?redirect=%2Fdashboard",
headers={
"Host": "192.168.1.16:8080",
"Cookie": "JSESSIONID=5d41402abc4b2a76b9719d911017c592; _ga=GA1.2.123456789",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
},
body='{"cart_id": 98712, "coupon": "WELCOME10"}'
)
# -> METHOD: POST [SEP] URI: /api/v1/checkout?redirect=/dashboard [SEP] HEADERS: host: <DOMAIN> | cookie: JSESSIONID=NONE | user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) [SEP] BODY: {"cart_id": 98712, "coupon": "WELCOME10"}
>= 3.10 (Python 3.11 or 3.12 recommended)torch >= 2.4.0 (with CUDA 12.1+ recommended for hardware bfloat16 and FlashAttention-2 / SDPA acceleration)transformers >= 4.48.0 (mandatory β native ModernBERT support requires 4.48.0+)trust_remote_code=True)Thanks to modeling_ironjean.py and normalizer_ironjean.py (trust_remote_code=True), IronJean's Vectorized Landmark Pooling architecture and HTTPNormalizer pipeline are downloaded and instantiated dynamically from the Hugging Face Hubβwithout requiring any local Python files or custom PyTorch boilerplate:
pipeline("text-classification", ..., trust_remote_code=True) loads IronJeanWAFPipeline, which accepts raw HTTP dictionaries ({"method": "GET", "uri": "...", "headers": {...}, "body": None}) or Landmark strings and normalizes them automatically (max_length=8192).AutoModelForSequenceClassification.from_pretrained(..., trust_remote_code=True) exposes model.normalizer (HTTPNormalizer) and model.serialize_http(...) directly on the loaded model instance.import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer, pipeline
model_id = "Metabaron6/IronJean-v2-ModernBERT-WAF"
device = "cuda" if torch.cuda.is_available() else "cpu"
# =====================================================================
# Option 1: Native Hugging Face Pipeline (Auto-Normalizes Raw HTTP Dicts)
# =====================================================================
classifier = pipeline(
"text-classification",
model=model_id,
trust_remote_code=True,
device=0 if device == "cuda" else -1
)
# Pass a raw HTTP request dict directly β IronJeanWAFPipeline normalizes it automatically!
result = classifier({
"method": "GET",
"uri": "/products?category=1' UNION SELECT null,password FROM users--",
"headers": {"Host": "api.corporate.net", "User-Agent": "Mozilla/5.0"},
"body": None
})
print("Pipeline Result:", result)
# =====================================================================
# Option 2: Native AutoModel with BFloat16 Acceleration & model.normalizer
# =====================================================================
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(
model_id,
trust_remote_code=True,
dtype=torch.bfloat16 if device == "cuda" else torch.float32
).to(device).eval()
# Access HTTPNormalizer directly from the downloaded HF model (normalizer_ironjean.py)
HTTPNormalizer = model.normalizer
# Optional: set HTTPNormalizer.BLOCK_SCANNER_USER_AGENTS = True for strict scanner-banner blocking
def classify_http_request(raw_sequence: str, threshold: float = 85.0):
# Step 1: Tokenize with up to 8192 context window
inputs = tokenizer(raw_sequence, return_tensors="pt", truncation=True, max_length=8192).to(device)
# Step 2: Vectorized Landmark Pooling inference
with torch.no_grad():
if device == "cuda":
with torch.amp.autocast("cuda", dtype=torch.bfloat16):
outputs = model(**inputs)
else:
outputs = model(**inputs)
probs = torch.softmax(outputs.logits.float(), dim=-1)
attack_prob = probs[0][1].item() * 100.0
is_attack = attack_prob >= threshold
return is_attack, attack_prob
# 2. Format requests using HTTPNormalizer & run inference
# Case A: SQL Injection in URI
sample_attack = HTTPNormalizer.serialize_http(
method="GET",
uri="/api/v1/users?id=1' OR '1'='1",
headers={"Host": "api.corporate.net", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},
body=None
)
blocked, prob = classify_http_request(sample_attack)
print(f"Attack URI Probability: {prob:.2f}% | Decision: {'π BLOCKED' if blocked else 'β
ALLOWED'}")
# Case B: Benign Business Form with Natural Text / Apostrophe
sample_safe = HTTPNormalizer.serialize_http(
method="POST",
uri="/api/v1/profile",
headers={"Host": "api.corporate.net", "User-Agent": "curl/8.11.1", "Content-Type": "application/json"},
body='{"name": "O\'Connor", "bio": "Software Engineer. It\'s a genuine profile.", "settings": {"theme": "dark"}}'
)
blocked, prob = classify_http_request(sample_safe)
print(f"Safe Request Attack Probability: {prob:.2f}% | Decision: {'π BLOCKED' if blocked else 'β
ALLOWED'}")
# Case C: Malicious Attack Injected Inside User-Agent Header (Log4Shell / SQLi)
# is_healthy_user_agent flags the suspicious pattern, preserves raw payload, and blocks it!
sample_ua_attack = HTTPNormalizer.serialize_http(
method="GET",
uri="/api/v1/users",
headers={"Host": "api.corporate.net", "User-Agent": "Mozilla/5.0' OR '1'='1--"},
body=None
)
blocked, prob = classify_http_request(sample_ua_attack)
print(f"User-Agent Attack Probability: {prob:.2f}% | Decision: {'π BLOCKED' if blocked else 'β
ALLOWED'}")
# Case D: Cookie Anonymization vs Malicious Cookie Injection (WAFFilter Parity)
# Benign telemetry cookies (_ga, __cf_bm) are dropped, session cookie is neutralized to NONE:
sample_cookie_safe = HTTPNormalizer.serialize_http(
method="POST",
uri="/api/v1/checkout",
headers={
"Host": "api.corporate.net",
"Cookie": "JSESSIONID=5d41402abc4b2a76b9719d911017c592; _ga=GA1.2.123456789.1234567890; __cf_bm=abcdef123456",
"User-Agent": "Mozilla/5.0"
},
body="cart_id=98712&item=44"
)
blocked, prob = classify_http_request(sample_cookie_safe)
print(f"Safe Cookie Request Attack Probability: {prob:.2f}% | Decision: {'π BLOCKED' if blocked else 'β
ALLOWED'}")
# Malicious Injection inside Cookie (SQLi preserved and deterministically blocked):
sample_cookie_attack = HTTPNormalizer.serialize_http(
method="GET",
uri="/dashboard",
headers={"Host": "api.corporate.net", "Cookie": "JSESSIONID=1' OR '1'='1--", "User-Agent": "Mozilla/5.0"},
body=None
)
blocked, prob = classify_http_request(sample_cookie_attack)
print(f"Cookie SQLi Attack Probability: {prob:.2f}% | Decision: {'π BLOCKED' if blocked else 'β
ALLOWED'}")
You can run the complete evaluation suite locally using test_hf.py:
python test_hf.py --hub
Real Console Output (CUDA bfloat16, Decision Threshold = 85.0%):
====================================================================
π‘οΈ IRONJEAN-v2.5 HUGGING FACE NATIVE BENCHMARK (AutoModel / Pipeline)
π¦ Model Source: Metabaron6/IronJean-v2-ModernBERT-WAF
βοΈ Architecture: Vectorized Landmark Pooling (trust_remote_code=True)
====================================================================
Loading IronJean-v2.5 via AutoModelForSequenceClassification (trust_remote_code=True)...
Target Source: Metabaron6/IronJean-v2-ModernBERT-WAF
β Config loaded | Target Labels: {0: 'SAFE', 1: 'ATTACK'}
β Tokenizer loaded | Vocabulary size: 50280 tokens
β Architecture resolved: IronJeanWAFClassifier
β Bundled Normalizer resolved (trust_remote_code=True): HTTPNormalizer
β Successfully initialized on CUDA.
Starting evaluation suite (max_length=8192, decision_threshold=85.0%)...
SCENARIO : ATTACK (Command Injection / OS Cmd) -> [ATTACK BLOCKED] π (100.00%) 261.76 ms
SCENARIO : BENIGN (Standard REST API Request) -> [SAFE ALLOWED] β
(100.00%) 26.31 ms
SCENARIO : ATTACK (Base64 Evasion / Java Deserialization) -> [ATTACK BLOCKED] π (100.00%) 24.58 ms
SCENARIO : BENIGN (OAuth2 Token Exchange) -> [SAFE ALLOWED] β
(100.00%) 26.78 ms
SCENARIO : BENIGN (JSON Form with Natural Apostrophe) -> [SAFE ALLOWED] β
(100.00%) 27.27 ms
SCENARIO : ATTACK (SQL Injection UNION SELECT) -> [ATTACK BLOCKED] π (100.00%) 27.81 ms
SCENARIO : BENIGN (WordPress Registration Form) -> [SAFE ALLOWED] β
(100.00%) 43.38 ms
SCENARIO : ATTACK (User-Agent SQL Injection) -> [ATTACK BLOCKED] π (100.00%) 25.76 ms
SCENARIO : ATTACK (User-Agent Log4Shell JNDI) -> [ATTACK BLOCKED] π (100.00%) 25.85 ms
SCENARIO : BENIGN (Cookie Anonymization - Session & Telemetry Dropped) -> [SAFE ALLOWED] β
(100.00%) 28.13 ms
SCENARIO : ATTACK (Cookie SQL Injection) -> [ATTACK BLOCKED] π (100.00%) 27.67 ms
====================================================================
Verifying Hugging Face pipeline('text-classification', trust_remote_code=True):
====================================================================
1. Landmark String : METHOD: GET [SEP] URI: /products?category=1' UNION SELECT null,password FROM users-- [SEP] HEADERS: host: <DOMAIN> [SEP] BODY: EMPTY_BODY
Pipeline Result : [{'label': 'ATTACK', 'score': 1.0}]
2. Raw HTTP Dict : /api/query?param=eyJAdHlwZSI6ImNvbS5zdW4ucm93c2V0LkpkYmNSb3dTZXRJbXBsIn0= (Auto-decoded by IronJeanWAFPipeline)
Pipeline Result : [{'label': 'ATTACK', 'score': 0.9999990463256836}]
====================================================================
β
All Hugging Face AutoModel & Pipeline tests completed successfully!
====================================================================
For low-latency production deployments in Java (Spring Boot / Spring Cloud Gateway, Jakarta EE / Tomcat Filter, Quarkus), IronJean WAF provides three ONNX model variants (model.onnx, model_quantized_int8.onnx, model_fp16.onnx) executed via ONNX Runtime and the Deep Java Library (DJL) HuggingFace tokenizer:
WAFInferenceNormalization.java β HTTPNormalizer): The Java Servlet filter normalizes incoming HttpServletRequest objects into canonical 4-landmark sequences (METHOD, URI, HEADERS, BODY), validated across 204/204 production Postman anti-regression tests (GET, POST, PUT, PATCH, DELETE, OPTIONS CORS preflights, complex Markdown documentation, nested JSON, XML/SOAP, and multipart uploads) and live e-banking REST API traffic.ThreadLocal<Predictor<NDList, NDList>> over a shared thread-safe ZooModel instance, paired with scoped try (NDManager manager = onnxModel.getNDManager().newSubManager()) blocks to deterministically free C++ ONNX tensors immediately after each request without pressuring the JVM Garbage Collector.| ONNX Model Variant | Footprint | Holdout Accuracy ( 50/50) |
Logits & Precision Drift vs. FP32 |
Production Server CPU Latency ( Seq: 68β83 / 180 tok) |
Production GPU Latency (NVIDIA L4 / A100) |
Recommended Target Hardware |
|---|---|---|---|---|---|---|
model.onnx( FP32) |
569 MB |
100.00% ( 50/50) |
[+6.9434, -5.0604]( AttackProb: 0.0006%)0.0000% drift (Baseline Reference) |
~4 β 7 ms / ~9 β 14 ms |
~1.4 β 2.2 ms |
Production CPU Server (AVX2 / AVX-512 FMA) & GPU |
model_quantized _int8.onnx( QUInt8*) |
254 MB(2.2Γ smaller) |
100.00% ( 50/50) |
[+6.0061, -4.1841]( AttackProb: 0.0038%)0.00% classification loss (ΞProb = +0.0032%, Ξlogit β 0.9) |
~2 β 4 ms / ~4 β 7 ms β‘ |
CPU-targeted | Production CPU (AVX-512 VNNI / Low-RAM / Long Payloads) |
model_fp16.onnx( FP16**) |
285 MB(2Γ smaller) |
100.00% ( 50/50) |
[+6.9383, -5.0571]( AttackProb: 0.0006%)< 0.0001% drift (Ξlogit = 0.005, virtually lossless) |
~12 β 22 ms(CPU F16C cast overhead) |
~0.8 β 1.4 ms β‘ |
Production GPU Only (NVIDIA Tensor Cores) |
π Click to expand ModernBertWafPredictor.javaπ‘ * Architecture Notes on
INT8&FP16ONNX Exports:
model_quantized_int8.onnx(QUInt8Hybrid Quantization): Uses unsigned 8-bit dynamic quantization (QuantType.QUInt8) restricted to transformerMatMullayers while keeping the token embedding table (/model/embeddings/tok_embeddings/Gather) and the final Landmark Pooling classification head (/classifier/Gemm) in nativeFP32. This prevents AVX2/AVX-512vpmaddubsw16-bit signed accumulator saturation on GeGLU activations and preserves 100.00% (50/50) classification parity with a minimal logit margin reduction (Ξlogit β 0.9, shifting benign request probability from0.0006%to0.0038%, both rounding to0.0000).model_fp16.onnx(FP16withkeep_io_types=True): Exported viaonnxruntime.transformers.float16withFP32outputlogitsfor plug-and-play Java DJL compatibility. On x86-64 CPUs lacking nativeAVX512-FP16FMA units,FP16incurs a ~2.3Γ latency penalty compared toFP32due to continuousF16C(FP16βFP32) register conversions; reservemodel_fp16.onnxfor NVIDIA Tensor Core GPU deployments (0.8β1.4 ms) and deploymodel.onnx(FP32) ormodel_quantized_int8.onnx(QUInt8) on CPU servers (2β7 ms).
package com.security.waf;
import ai.djl.inference.Predictor;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.repository.zoo.ZooModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ModernBertWafPredictor {
private static final Logger log = LoggerFactory.getLogger(ModernBertWafPredictor.class);
// The loaded ONNX model, thread-safe and shared across the application
private final ZooModel<NDList, NDList> onnxModel;
// ThreadLocal predictor since Predictor objects are NOT thread-safe in DJL
private final ThreadLocal<Predictor<NDList, NDList>> predictorPool;
public ModernBertWafPredictor(ZooModel<NDList, NDList> onnxModel) {
this.onnxModel = onnxModel;
this.predictorPool = ThreadLocal.withInitial(onnxModel::newPredictor);
}
/**
* Executes the WAF inference using dynamic sequence lengths.
*
* @param tokenIds The tokenized HTTP payload, inherently variable in length.
* @return true if the payload is classified as an Attack, false if Safe.
*/
public boolean isMalicious(long[] tokenIds) {
Predictor<NDList, NDList> predictor = predictorPool.get();
int actualSequenceLength = tokenIds.length;
long startTime = System.currentTimeMillis();
// CRITICAL: Try-with-resources to prevent C++ native memory leaks.
// Bypasses the Java Garbage Collector for immediate tensor destruction.
try (NDManager manager = onnxModel.getNDManager().newSubManager()) {
// 1. Construct input_ids with shape [1, actualSequenceLength]
// This perfectly matches the dynamic_axes 'sequence_length' configured in Python
NDArray inputIds = manager.create(new long[][]{ tokenIds });
inputIds.setName("input_ids");
// 2. Construct attention_mask with shape [1, actualSequenceLength]
// Since we use exact sizing instead of padding, all tokens are real.
// Therefore, the mask is simply an array of 1s of the same length.
long[] maskArray = new long[actualSequenceLength];
java.util.Arrays.fill(maskArray, 1L);
NDArray attentionMask = manager.create(new long[][]{ maskArray });
attentionMask.setName("attention_mask");
// 3. Package tensors into an NDList for the ONNX session
NDList inputs = new NDList(inputIds, attentionMask);
// 4. Run native C++ inference
NDList outputs = predictor.predict(inputs);
// 5. Extract logits.
// Shape is [1, 2] (Batch size 1, 2 probability classes: Safe vs Attack)
NDArray logits = outputs.getFirst();
// Apply softmax to convert raw logits to probabilities [0.0 - 1.0]
NDArray probabilities = logits.softmax(1);
// Extract the probability for class 1 (Attack)
float attackProbability = probabilities.getFloat(0, 1);
// Apply custom threshold (0.85 balances aggressive threat detection with dev/scanner tolerance)
float threshold = 0.85f;
boolean isMalicious = attackProbability > threshold;
if (log.isDebugEnabled()) {
//WafLogitAnalyzer.analyzeOnnxOutput(outputs);
long duration = System.currentTimeMillis() - startTime;
log.debug("WAF Inference - SeqLength: {} | AttackProb: {} | Prediction: {} | Execution Time: {} ms",
actualSequenceLength, String.format("%.4f", attackProbability), isMalicious ? "ATTACK" : "SAFE", duration);
}
return isMalicious;
} catch (Exception e) {
log.error("WAF Inference failed. Failsafe activated: blocking request.", e);
// Default deny in security contexts
return true;
}
}
/**
* Called during Tomcat shutdown or application undeploy to release native ONNX resources.
*/
public void close() {
if (predictorPool != null) {
predictorPool.remove();
}
if (onnxModel != null) {
onnxModel.close();
}
}
}
Licensed under the Apache License, Version 2.0. Free for academic, personal, and commercial enterprise use.
@software{IronJean-v2-ModernBERT-WAF,
author = {Franck Andriano},
title = {IronJean WAF v2.5: Enterprise Web Application Firewall Powered by ModernBERT and Landmark Pooling},
year = {2026},
publisher = {Hugging Face},
url = {https://huggingface.co/Metabaron6/IronJean-v2-ModernBERT-WAF}
}