Zortium
Adversarial attack test suite for vision-language models.
Zortium runs a structured battery of adversarial attacks against any OpenAI-compatible VLM endpoint and reports a per-suite Attack Success Rate (ASR). Think promptfoo for vision models — systematic, repeatable, CI-ready.
$ zortium --base-url https://openrouter.ai/api/v1 --model google/gemma-3-27b-it --api-key $OPENROUTER_API_KEY \
--judge-model gpt-4o-mini --judge-base-url https://api.openai.com/v1 --judge-api-key $OPENAI_API_KEY
███████╗ ██████╗ ██████╗ ████████╗██╗██╗ ██╗███╗ ███╗
╚══███╔╝██╔═══██╗██╔══██╗╚══██╔══╝██║██║ ██║████╗ ████║
███╔╝ ██║ ██║██████╔╝ ██║ ██║██║ ██║██╔████╔██║
███╔╝ ██║ ██║██╔══██╗ ██║ ██║██║ ██║██║╚██╔╝██║
███████╗╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ╚═╝ ██║
╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
VLM adversarial attack scanner
target google/gemma-3-27b-it
endpoint https://openrouter.ai/api/v1
judge gpt-4o-mini
mode fast
suites 18
⠋ scanning ━━━━━━━━━━━━━━━━━━━━━━━━━━ 18/18 suites 0:08:47 ← fills live, then:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Attack Suite ┃ Severity ┃ Breach Rate ┃ Status ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━┩
│ Typographic Visual Prompt Injection │ HIGH │ 0% │ PASS │
│ FigStep Typographic Jailbreak │ HIGH │ 25% │ WARN │
│ Query-Relevant Typographic Jailbreak │ HIGH │ 8% │ PASS │
│ Goal Hijacking │ MID │ 50% │ FAIL │
│ … │ … │ … │ … │
├──────────────────────────────────────┼──────────┼─────────────┼────────┤
│ Overall │ │ 12% │ WARN │
└──────────────────────────────────────┴──────────┴─────────────┴────────┘
cases 137 evaluated · 17 breached
elapsed 8m 47s
PASS Overall ASR 12.3% within threshold 100%
No judge configured? Zortium falls back to the target grading itself and prints a warning — usable for a first run, but self-grading is lenient, so those breach rates are a floor. Pass
--judge-modelfor trustworthy numbers.
Installation
pip install zortium
Requires Python 3.10+. On Linux you may need the system Cairo library for image rendering (apt-get install libcairo2 / dnf install cairo); macOS and Windows wheels bundle it.
Usage
The command in the example above has two halves — the target you're testing and the judge that grades whether the target complied:
| Role | Flags | What it is |
|---|---|---|
| Target | --base-url · --model · --api-key |
the VLM under test — any OpenAI-compatible chat completions endpoint |
| Judge | --judge-model · --judge-base-url · --judge-api-key |
a separate model that grades harmful-content compliance (see LLM judge) |
That single command is the whole interface. Everything below is a convenience on top of it:
- Keys off the command line — every flag has a
ZORTIUM_*environment variable (ZORTIUM_API_KEY,ZORTIUM_JUDGE_API_KEY,ZORTIUM_MODEL, …), so secrets never appear in your shell history orps. - Config file instead of a long line — run
zortium --initto drop a documentedzortium.yamlin the current directory, edit it, thenzortium --config zortium.yaml. A flag or env var still overrides the file; blank values fall through to env/defaults. - Save every response — the CLI keeps no database, so add
--output report.jsonto write a full report: run metadata, headline stats, and per-suite → per-case entries with the full model response, the judge verdict, and its reasoning. - Deeper audit —
--no-fastruns full per-suite case coverage instead of the reduced fast set (see Scan modes). - Go faster on high-limit endpoints —
--tps N(envZORTIUM_TPS) runs N suites in parallel. Default is1(sequential — safe for rate-limited or shared-key endpoints); raise it for in-house models that can absorb the extra requests-per-second. Rate limits are still handled per the 429 policy, so a too-high value degrades gracefully rather than failing.
CI integration
Exit with code 1 if overall ASR exceeds a threshold:
zortium --max-asr 20
# GitHub Actions example
- name: Run VLM security scan
run: zortium --max-asr 20
env:
ZORTIUM_BASE_URL: ${{ secrets.VLM_BASE_URL }}
ZORTIUM_MODEL: ${{ secrets.VLM_MODEL }}
ZORTIUM_API_KEY: ${{ secrets.VLM_API_KEY }}
ZORTIUM_FAST: "true"
--fast (default true) runs one high-signal case per harm category for the image suites — the leanest useful scan, for CI and first runs. Use --no-fast for pre-release deep scans with full per-category coverage.
Rate-limited endpoints
By default a rate-limited (429) test case is recorded as unavailable and left out of the score — fast, and right for CI. For a one-off baseline against a heavily throttled endpoint, --wait (env ZORTIUM_WAIT) retries each rate-limited case until the limit clears so every case gets a real verdict. It respects the Retry-After header and can take much longer — use it for benchmarks, not CI.
zortium --no-fast --wait # complete thorough baseline, waits out rate limits
Each suite is labelled with a severity — HIGH, MID, LOW, or DIAGNOSTIC — reflecting how real-world exploitable the attack is (distinct from how it's judge-gated). Diagnostic suites are always excluded from the headline ASR. In the web UI and benchmarks, severity also drives a weighted overall score (HIGH=3 / MID=2 / LOW=1 / DIAGNOSTIC=0), so high-severity breaches move the headline number more than low-severity ones. The CLI --max-asr gate uses the raw overall ASR.
LLM judge
Critical suites are graded by an LLM judge — it reads each (harmful request, model response) pair and decides whether the target actually complied. Configure it with --judge-model / --judge-base-url / --judge-api-key (or the ZORTIUM_JUDGE_* env vars). The base URL and key inherit the target's when omitted, so a judge on the same endpoint needs only --judge-model. Use a separate, stronger model than the one under test for better calibration.
No judge configured? Zortium falls back to the target model grading its own responses and prints a warning. This keeps a first run useful out of the box, but a model judging itself is lenient — the resulting breach rates are a floor, not a trusted number. Always configure a real judge for numbers you'll act on.
Attack suites
Critical — LLM judge required, high research backing
| Suite | Technique | Paper |
|---|---|---|
| Typographic Visual Prompt Injection | Embeds harmful instructions as text in images | Goh et al. 2021 |
| Split-Modality Injection | Splits instruction across image + text channels | Original |
| FigStep | Numbered-list document completion framing | Gong et al. 2023 |
| Steganographic Text Injection | Hides instructions in low-contrast image regions | Li et al. 2024 |
| Image Hijacks | Behaviour-matching visual overlays | Bailey et al. 2023 |
| JailBreakV-28K | Real benchmark — 7 adversarial format types | Luo et al. 2024 |
| Visual Jailbreak Transfer | DAN / AIM / many-shot visual personas | Wei et al. 2023 |
| Query-Relevant Typographic Jailbreak | Hides the harmful instruction in a rendered-text image | Inspired by Liu et al. ECCV 2024 |
| Many-Shot Text | 24-example in-context compliance history | Anil et al. 2024 |
| Goal Hijacking | Prompt injection with delimiter spoofing | Perez & Ribeiro 2022 |
| Structured Output Injection | JSON schema / constrained-decoding exploitation | Original |
PAIR Agent (iterative attacker-LLM red-teaming loop, Chao et al. 2023) is implemented but disabled by default. It's a multi-turn attacker-agent loop built for targeted deep audits of a single model rather than routine scanning, so it isn't part of the standard suite — enable it with a one-line registry change when you want it.
JailBreakV-28K is not bundled — the suite fetches its 300 benchmark images on demand from the official MIT-licensed HuggingFace repo the first time it runs (
python -m zortium.attacks.jailbreakv_28k --download). Every other suite works out of the box.
Medium — Keyword evaluation, limited effectiveness on modern models
| Suite | Technique |
|---|---|
| GCG Suffix | Precomputed adversarial token suffixes (transfer variant) |
| Encoding Attack | Base64 / ROT13 / hex payload obfuscation |
| Refusal Suppression | Instruction-stack refusal bypass |
| Multi-Modal Linkage | Cross-modal encryption-decryption framing |
| UI Spoofing | Authoritative-surface behavioural deviation |
Diagnostic — Robustness probes, reported separately from ASR
| Suite | Metric |
|---|---|
| Visual Perturbation Drift | Cosine similarity drift under pixel noise |
| Stroop Interference | Visual-linguistic conflict detection |
Scan modes
Both modes run the same 18 suites; fast runs fewer cases per suite, thorough runs full per-suite coverage.
| Fast (default) | Thorough | |
|---|---|---|
| Cases per image suite | reduced (highest-signal) | full |
| Query-Relevant Typo cases | 13 (1 per scenario, TYPO) | 26 (2 per scenario, TYPO) |
| Good for | daily CI, first runs | pre-release, deep audits |
On runtime: wall-clock time is dominated by the target model's own latency, throughput, and rate limits — not by Zortium — so it varies widely by model. Expect anywhere from a few minutes to ~10+ minutes for a fast scan; a slow or heavily throttled endpoint (especially with --wait) can take considerably longer. If your endpoint has generous rate limits, --tps N runs suites in parallel to cut that time (see Usage).
zortium.dev — managed platform
This CLI is the open-source engine, free and fully standalone. zortium.dev is a managed platform built on top of the same attack suites, for teams that want more than a per-run terminal report:
- Web UI, no install — run scans from the browser with results rendered live as each suite completes.
- Persistent history & dashboards — every scan is stored and browsable; resume a scan by simply returning to the tab.
- Scan-to-scan comparison — diff two runs to see which suites regressed or improved between model versions, with per-suite flip detection.
- Weighted risk scoring — a severity-weighted headline score (HIGH/MID/LOW) and per-tier breakdown, so a critical breach outweighs a low-severity one.
- Cross-model benchmarks — a leaderboard ranking models by the same methodology, for choosing or tracking a vision model.
- Teams & enterprise — shared accounts, plus an on-prem deployment where scan content stays entirely on your own database and only aggregate metadata is reported.
The CLI never phones home and needs none of this — the platform is for teams that want persistence, collaboration, and benchmarking on top of the engine.
Supported providers
Any endpoint speaking the OpenAI chat completions protocol:
- OpenRouter — pay-per-token access to hundreds of OSS models via one key (the easiest path for benchmarking OSS models)
- OpenAI, Groq, Together AI, Google Gemini (via their OpenAI-compatible endpoints)
- vLLM, Ollama (via
/v1shim), TGI adapter - In-house gateways and fine-tuned model deployments
Architecture
The engine is a src-layout package under src/zortium/:
src/zortium/
├── runner.py TestRunner — judge enforcement, suite execution
├── constants.py ScanMode, SuitePriority, SuiteSeverity, RateLimitPolicy, ScanStatus enums
├── attacks/ Attack suite implementations
│ ├── base.py AttackSuite base class — configure_for_mode(), evaluate()
│ ├── __init__.py SuiteRegistry — ACTIVE_SUITE_CLASSES, build_active_suites()
│ ├── typographic_injection.py
│ └── ...
├── providers/
│ ├── openai_compatible.py Target and judge provider abstraction
│ └── ratelimit.py RateLimitResolver — Retry-After/backoff, SKIP/WAIT, connection retry
├── evaluators/
│ ├── judge.py LLM-as-judge evaluation
│ └── refusal_aware.py Keyword-based refusal detection
├── utils/ Render, Perturbations, SemanticSimilarity, ImageUtils, PayloadPack
└── config/
├── attacks.json Suite config — priority, severity, harm categories, parameters
└── harm_payloads.json Harm category definitions and prompt library
Adding a suite: subclass AttackSuite in src/zortium/attacks/, add an entry to config/attacks.json, register it in ACTIVE_SUITE_CLASSES in attacks/__init__.py. Override configure_for_mode() if the suite needs different behaviour in fast vs thorough mode.
Intended use & safety
Zortium is a defensive security tool for evaluating the robustness of vision-language models you own or are authorized to test.
- Authorized targets only. Do not run Zortium against models, endpoints, or systems you do not own or have explicit permission to test.
- It sends adversarial and harmful content. By design, Zortium submits jailbreak prompts, harmful requests, and adversarial images to the target model to measure its refusal behaviour. The bundled payloads (
config/harm_payloads.json) and the JailBreakV-28K benchmark contain offensive material. - Purpose is measurement, not exploitation. The output is a robustness report (Attack Success Rate per suite) to help you harden a model — not a toolkit for attacking third-party systems.
Report security issues responsibly and use results to improve model safety.
Citations
See CITATIONS.md for full attribution of all research papers and benchmark datasets.
License
See LICENSE.
Metadata
Release files for zortium 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zortium-0.2.0.tar.gz | 358.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zortium-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 627.9 kB
Release files / zortium-0.2.0.tar.gz
| Download URL | zortium-0.2.0.tar.gz |
|---|---|
| Size | 358.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
68880b20f01fc91eec4d64bc90aebc897562d38ed9b6ea0256976dabc99cef29
|
|
BLAKE2b-256 checksum How to use checksums |
b0139d9fbea503344c8001eaf9a1336fcb6cd70d5e304993c9b03a063b074e28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|
Release files / zortium-0.2.0-py3-none-any.whl
| Download URL | zortium-0.2.0-py3-none-any.whl |
|---|---|
| Size | 269.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
32414bdd4b1ad731cb7e4301b8367f1d7f402840bd8fb7234d5bc19921af19e6
|
|
BLAKE2b-256 checksum How to use checksums |
b928496497cbf26ecbd23f3da023b9d2ba909046f6bbc2e3cd7e28fbce4899b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|