vacantfury/llm_guardrail_security
GitHub: vacantfury/llm_guardrail_security
面向LLM/VLM防护栏安全的学术研究框架,研究编码越狱攻击与黑盒recover-decode-guard防御机制。
Stars: 1 | Forks: 0
# LLM 防护栏安全 — 编码与图像渲染的越狱 vs. 黑盒防御
关于 **guardrail layer**(防护栏层,即部署在 LLM 和 VLM 周围的模型外安全机制,如 guard 分类器、过滤器、caption/decode pipeline)安全性的研究代码库。核心主线:编码与图像渲染的越狱会绕过已部署的防御机制,这些机制*检查或推理*内容,但从不*解码*混淆的 payload(即 **decode gap**,解码鸿沟)——我们从攻击侧测量这一鸿沟,并从防御侧使用一个黑盒的 **recover→decode→guard** 放大器来弥补它。
本代码库是以下一系列研究的共享测试框架:
- **MathEnc** — *Exposing LLM Safety Gaps Through Mathematical Encoding*(已发表):文本侧编码器(集合论、形式逻辑、代码),将有害查询重塑为分布外的表层形式。
- **ImgAug** — *Image Augmentation Strengthens VLM Defenses Against Encoded Jailbreak Attacks*(在审中):添加一张图像——哪怕是内容不相关的诱饵——会改变防御的行为,因为防御的覆盖范围恰好对齐(或未对齐)编码内容所在的位置。
- **The Decode Gap**(进行中,`autoattack_defense/`):已部署的黑盒防御——包括较新的多模态和推理 guard——*检查或推理*内容,但从不**解码**混淆的 payload,因此语义编码的危害(在文本中或渲染到图像中)会绕过它们。我们构建了最小化的黑盒 **recover→decode→guard** 放大器来弥补这一鸿沟,通过接入外部 guard 证明其与具体 guard 无关,并量化了其过度拒绝成本。该论点是结构性的——直接从 guard 自身已发表的设计中读出——而非排行榜。
- **Variance Channels**(进行中,`bestofn_attack/`):*Best-of-N Jailbreaking Beyond Surface Noise*。Best-of-N 攻击的能力受其 **variance channel**(变异通道)控制——即在采样抽取中真正发生改变的因素(表面噪声 vs. 语义复述 vs. 采样攻击策略)。重复采样单一的强*结构性*攻击,在有防御和无防御的情况下都能击败普通的表面噪声 BoN,并能抵御那些足以中和表面 BoN 的输入归一化防御。拼的是深度,而非广度:多样的攻击族集合几乎不比单一最佳攻击效果更好,但消耗的查询却多得多。`bestofn_defense/` 命名空间保存了本文的**支持性** canonicalize→guard 防御部分——它映射了归一化在何处依然成立(表面通道),又在何处失效(更深层的策略通道)。它**不是**一篇独立的论文。
## 工作原理
**放大器**和**防御**是两个完全独立的事物;将它们混为一谈是对这个 pipeline 最常见的误读。
```
ATTACK DEFENSE MEASURE
┌───────────────┐ ┌──────────────────────────┐ ┌──────────────────┐
│ text encoder │ │ amplifier: recover+decode│ │ target VLM answer│
│ ± image │───▶│ ↓ │───▶│ ↓ │
│ renderer │ │ defense: transform OR gate│ │ judge → ASR / OR │
└───────────────┘ └──────────────────────────┘ └──────────────────┘
```
1. **攻击**(`prompt_transform` 模式)——将一个 prompt(有害的,或用于过度拒绝轴线的良性 prompt)编码为替代表示和/或渲染为图像。
2. **放大器**——*recover*(目标 VLM 将图像转录回文本),然后 *decode*(目标 VLM 用标准英语重述编码内容)。这是一个 **transform**(转换),而不是安全机制:它的存在是为了让防御能看到隐藏的 payload。它与具体 guard 无关——可以prepend(前置)到任何 guard 上。
3. **防御**(`defense+evaluate` 模式)——作用于(已解码)prompt 的安全机制,有两种类型:
- **Transform 防御**(例如 SAGE):将文本重写为安全检查 prompt,由目标进行回答。
- **Gate 防御**(例如 WildGuard、LlamaGuard-3、Qwen3Guard):一个*分类器*,使用预设的拒绝回复进行拦截,或将**原始** prompt 原封不动地放行。Gate 永远不会进行重写。
4. **Judge**(裁判)——外部测量,绝不是目标模型本身,也不是防御分类器。危害程度使用 HarmBench 评分标准进行评分(→ ASR),拒绝情况使用 OR-Bench 3 分类评分标准进行评分(→ 过度拒绝)。
放大器的贡献是通过在**固定防御**的情况下对比 `modality_complete` 与 `guard_baseline` 来衡量的。过度拒绝是所选防御自身校准的属性,而不是放大器的属性。
### 核心指标:ensemble (best-of-N) ASR
由于攻击侧是一个包含约 10 个基本编码器和渲染器的 *suite*(套件),如果在套件中**任何**攻击在特定条件下攻破了行为,该行为即被视为越狱成功——这是一种基于每个 prompt `asr` 的 AutoAttack 式或归约(`src/analysis/portfolio.py`)。核心对比在这个 ensemble 数字上运行 `no_defense` → `guard_baseline` → `modality_complete`。
单一攻击的 ASR 及其在整个套件中的平均值**仅作为诊断视图**:不同的攻击会攻破不同的输入,因此单次攻击的平均值低估了攻击者真实的联合能力,从而美化了防御。
### Judge
`gpt-5-mini` 是各篇论文中确定的首选主 judge,它是通过与一个更宽松的早期 judge 进行人工校准竞赛而选出的。WildGuard 仅作为辅助的鲁棒性/校准视角出现——它不遵循完成度评分标准,因此作为主要的 ASR judge 是**无效**的。`rejudge` 模式使用不同的 judge 对存储的响应重新打分,而无需重新查询目标,因此 judge 的更改不会在目标侧产生任何成本。
## 攻击
文本编码器和图像渲染器共享**同一个**注册表(`src/prompt_transformations/transformation_factory.py`);`text/` 与 `image/` 仅仅是目录组织。各个步骤可组合成链,因此通过追加一个渲染器,编码就可以被渲染成图像。
| 族 | `type_name`s |
|---|---|
| 基线 / 非 LLM | `non_llm_baseline`, `non_llm_homoglyph`, `non_llm_artprompt`, `non_llm_cipher` (base64 / caesar), `non_llm_symbol_injection` |
| 语义编码 (LLM) | `llm_set_theory`, `llm_formal_logic`, `llm_quantum_mechanics`, `llm_classical_language`, `llm_semantic_camo` |
| 分解 | `non_llm_addition_equation_split_reassemble`, `non_llm_conditional_probability`, `deep_inception`, `ecso_evade`, `code_attack` |
| 图像渲染器 | `ir_plain` (fixed-font paginated), `ir_figstep`, `ir_fc_typo`, `ir_fc_flowchart`, `ir_blank`, `ir_constant` |
| 经典多模态攻击 | `ir_low_contrast`, `ir_occluded` (perceptual blindness), `ir_mm_typo` (MM-SafetyBench), `ir_distraction_grid` (Text-DJ/CS-DJ) |
| **Best-of-N (论文 D)** | `non_llm_best_of_n`, `llm_paraphrase`, **`variance_channel_bon`** |
| **自适应 (针对我们自己的防御)** | `llm_decode_evasion`, `cross_modal_split`, `ir_semantic_split` |
**`variance_channel_bon`** 是论文 D 的核心装置:一种 Best-of-N 转换,通过 *N 次抽取在何处产生差异*来进行参数化。`surface` 委托给普通的字符/大小写/ASCII 噪声——这是一种输入归一化防御会将其坍缩为约 1 的有效 N 的相关抽取。`paraphrase` 每次抽取都进行改写,从而能在归一化下存活。`strategy` 从配置的库中为每次抽取采样一个攻击*族*——反相关抽取,具有最强的防御存活能力。
**自适应攻击**是诚实的压力测试,针对的是本代码库自身的防御,而不是别人的。`llm_decode_evasion` 重构了 prompt,使得放大器的短解码看起来是良性的,而完整的回答依然有害。`cross_modal_split` 和 `ir_semantic_split` 放置危害的方式使其仅存在于*联合的*文本+图像解读中,而每个单独的通道看起来都是良性的——放大器是**按通道**解码的,因此这些方法探测的是其声明的边界,而非其最强情况。这三种方法都是作为这些局限性的附录探针而构建和评估的。
**ImgAug 诱饵机制**不是一种防御——它是将 prompt 与内容无关的常量图像配对的攻击侧条件,作为 `ir_constant` 渲染器实现。
## 防御
全部在 `src/defense/defender_factory.py`(`@register_defense`)下注册。每个防御都实现一个接口 `query(prompts, target_service, is_multimodal, source_dir, system_message)`,并**拥有**其与目标模型的交互——包装输入、单次查询,或查询→检查→重新查询。
| `type_name` | 类型 | 备注 |
|---|---|---|
| `no_defense` | — | passthrough 基线 (攻击下限) |
| `sage` | transform | prompt 级别的安全 guard,输入文本表面 |
| `semantic_smooth` | transform | 语义扰动平滑基线 |
| `ecso` | transform | caption 介导的重新验证,受 `has_image` 门控 |
| `amia_ia` | transform | 仅意图分析——即“检查但从不解码”的对比项 |
| `guard_baseline` | gate | 单独使用已发表的 guard 分类器,**无放大器**——对比组 |
| **`modality_complete`** | **amplifier + defense** | **核心贡献**——recover(恢复)**并** decode(解码)每个通道,然后进行一次统一的安全检查。`guard_model=None` 使用 SAGE transform;`guard_model=` 使其成为 gate。 |
| `canonicalize` | transform | 输入归一化防御 (论文 D 的支持性部分) |
| `canonicalize_guard` | gate | canonicalize → guard 组合,带有 AND/OR 组合开关和判定持久性 |
| `joint_verify` | joint | 联合文本+图像验证;已构建,**未来工作** |
可作为 gate 或面板成员使用的 Guard 检查点:LlamaGuard-3-8B, LlamaGuard-4-12B, WildGuard, Qwen3Guard-Gen-8B, GuardReasoner-VL-7B, ShieldLM-7B, ThinkGuard。面板成员资格按预设配置。
## 安装说明
```
pip install -e .
```
Python ≥ 3.12。依赖项在 `pyproject.toml` 中。
API keys(用于 judge + API 目标)作为纯环境变量读取(见 `.env.example`)。按您喜欢的方式提供它们:在 shell 中 export 它们,在仓库根目录下放置一个被 gitignore 的 `.env`,或从您自己的 secret manager 中获取。这些变量包括:
```
OPENAI_API_KEY=... # judge + OpenAI targets/encoders
ANTHROPIC_API_KEY=... # Anthropic targets (Message Batches API)
GOOGLE_API_KEY=... # Gemini targets (Batch API)
HUGGINGFACE_TOKEN=... # gated HF model downloads (cluster vLLM serving)
DEEPSEEK_API_KEY=... # DeepSeek (OpenAI-compatible; judge/eval)
ZAI_API_KEY=... # Z.AI / GLM (OpenAI-compatible; judge/eval)
XAI_API_KEY=... # xAI / Grok (OpenAI-compatible)
MOONSHOT_API_KEY=... # Moonshot / Kimi (OpenAI-compatible; judge/eval/target)
OLLAMA_BASE_URL=... # optional; only for local Ollama-served models
```
真实的 `.env` 已被 gitignore——切勿提交 key 值。AWS Bedrock 使用标准的 AWS 凭证链(`AWS_PROFILE`),而不是 bearer key。
非拉丁文字编码器和图像渲染需要 `fonts/` 下的 Noto 字体(已被 gitignore)。
## 运行 Naabu
```
python main.py test # smoke test end-to-end (~$0.01); verifies install + keys
python main.py / # any conf/experiment//.yaml
```
**预设约定:** 每一轮实验都在其论文目录下拥有自己命名的预设(例如 `conf/experiment/autoattack_defense/reguard_5guard.yaml`);废弃的预设带有 `HISTORICAL` 头部横幅,而不是被删除,因此其出处依然可被 grep 到。
### 集群 (SLURM)
开放权重的 target、guard 和 judge 由 orchestrator 作为独立的 vLLM SLURM 作业提供服务。集群配置位于 `conf/clusters/`;orchestrator 进程运行的位置加上 `CLUSTER_PROFILE` 决定了您当前处于哪个集群。
```
sbatch scripts/run_experiment.sbatch / # default profile; keeps old logs
sbatch scripts/run_experiment_aicr.sbatch / # AICR profile
sbatch scripts/run_experiment_xc.sbatch / # xc profile (AWS box; Bedrock API-first)
```
### 多集群调度
单次 orchestrator 运行是单集群的——每一次 `sbatch`/`squeue` 调用都是一个本地子进程。要使用多个集群,`dispatch.py` 会在提交前拆分一个预设的任务矩阵,在同一论文子目录下为每个集群写入一个子预设,并通过 ssh 提交每一个。**默认进行 Dry-run:**
```
python dispatch.py / # print the plan + ssh commands; submit nothing
python dispatch.py / --submit # place + sbatch each sub-preset (sync code first)
```
拆分的关键键是每个任务所需的*集群服务*模型集合(target ∪ judge ∪ guard),因此 API judge 会从该键中被剔除。一个 cell 是原子性的——它在为其所有模型提供服务的同一集群上运行;一个 pipeline 绝不会被拆分。池顺序和每个集群的预算位于 `conf/cluster_pool.yaml` 中(已被 gitignore;模板位于 `conf/cluster_pool.example.yaml`)。
### OCR 保真度探针 (图像通道运行前的门控)
```
sbatch temporary_scripts/ocr_probe.sbatch qwen2_5_vl_7b internvl3_8b pixtral_12b
```
串行服务每个 VLM,并根据上游编码文本转录采样的 `ir_plain` 图像——在图像侧 cell 变得有意义之前,确认模型确实能够渲染出的攻击内容。
## 模型
**开放权重 target (集群 / vLLM):** `qwen2_5_vl_7b` (主力), `internvl3_8b` (`trust_remote_code`), `qwen3_vl_8b_instruct`, `pixtral_12b` (在最长编码上的 OCR 表现勉强), `llava_next`, `llama_3_3_70b_instruct`, `qwen2_5_7b_instruct`, `gemma2_9b_it`, `llama3_1_8b_cluster`。`llama3_2_11b_vision` 的服务受 vLLM/Mllama 不兼容问题的阻碍——需限制文本或固定版本。
**API 广度:** `gpt-4o-mini`, `gpt-4.1-mini`, `gemini-2.5-pro`, `kimi-k2-instruct`, `deepseek-v3.2-exp`, `glm-4.5-air`, `qwen3-235b-a22b-instruct`, `command-a`, `hermes-4-70b`。
针对特定模型的请求/服务覆盖配置位于 `conf/llm/.yaml`。微调的分类器检查点必须设置 `chat_template: passthrough`,否则 vLLM chat endpoint 会拒绝所有请求。
Provider 路由位于一个统一的调用形式之后:`service.batch_chat(conversations, system_message, is_test)`:
| Provider | 策略 |
|---|---|
| OpenAI | realtime `AsyncOpenAI` + `asyncio.gather`,或者当预估作业成本超过 batch 阈值(默认为 $1)时使用原生 Batch API(便宜 50%) |
| Anthropic | realtime SDK fan-out,或者在超过相同阈值时使用原生 Message Batches API(便宜 50%) |
| Google | realtime,或者在超过相同阈值时使用原生 inline Batch API(便宜 50%) |
| SLURM 集群 (vLLM) | 针对由 server manager 注册的 endpoint 使用 `AsyncOpenAI`;图像输入使用 base64 `image_url` |
| AWS Bedrock | boto3 `bedrock-runtime.converse` (Claude / Qwen / DeepSeek / Nova / …) |
| Local | 由 Ollama 服务的模型 |
模型注册表和定价位于已锁定的外部包 [`llm_utils`](https://github.com/vacantfury/llm_utils) (`llm_utils.llm_model::LLMModel`) 中——这是一个 git 依赖,而非 vendored 代码。
## 项目结构
```
conf/
├── experiment// # YAML task presets, namespaced per paper (one named preset per round)
├── llm/ # per-model request/serving overrides
├── clusters/ # SLURM cluster profiles (nurc, aicr, xc)
├── text_encoding/ # encoder configs (set_theory, formal_logic, cipher, classical_language/, ...)
├── imaging/ # renderer configs (ir_plain, figstep, mm_typo, semantic_split, ...)
├── defense/ # sage, semantic_smooth, canonicalize(_guard), amia_ia, modality_complete
├── evaluation/ # judge LLM config (evaluator choice derives from the benchmark)
└── analysis/ # analysis-tool configs (guard-threshold sweep, ...)
src/
├── experiment/ # orchestrator (experiment.py), task dispatcher (task.py, 4 modes),
│ # judging.py, stage_rejudge.py, model_discovery.py,
│ # multi_cluster.py, Pydantic schemas, cluster_health.py
├── prompt_transformations/ # ONE unified registry: text/ encoders + image/ renderers
├── defense/ # all defenders incl. modality_complete (contribution) + shared guard_utils
├── evaluation/ # HarmBench / JBB / JBB-refusal / OR-Bench judges (+ WildGuard robustness lens)
├── analysis/ # ensemble ASR (portfolio.py), guard-threshold sweeps + logprob verification,
│ # paired stats (McNemar, bootstrap CIs), severity grading, middle-band and
│ # over-refusal decomposition, decode-fidelity, per-paper figure/table scripts
└── utils/ # logger, MLflow tracker, provenance helpers
dispatch.py # multi-cluster preset split (dry-run by default)
scripts/*.sbatch # SLURM submission wrappers (default / _aicr / _xc / rejudge variants)
text_docs// # per-paper proposal + experiments plan (+ shared/ for cross-paper material)
data/ # prompt benchmarks (HarmBench, JBB, OR-Bench)
outputs// # experiment outputs (gitignored)
fonts/ · mlruns/ # fonts + MLflow tracking (gitignored)
```
### Pipeline 模式
`src/experiment/task.py::run_task` 根据 `task.mode`(一个可辨识的 Pydantic union)进行分发:
- **`prompt_transform`** ——运行一系列转换步骤;每一步对应一个子文件夹,每个文件夹包含一个累积的 `results.json`。输入是原始数据集 JSONL 或上一步的输出(这样一个编码就可以在多次消融实验中共享,而不是重新编码)。
- **`defense+evaluate`** ——防御 + 目标查询 + judging,融合为一个模式。
- **`analyze`** ——纯粹的后处理,无模型或 judge 的 I/O;从多个 `defense+evaluate` 目录中进行 fan-in(聚合)(ensemble ASR、互补性差距、配对统计)。
- **`rejudge`** ——使用不同的 judge 对已存储运行的保存响应重新评分,无需重新查询目标模型。
## 输出布局
```
outputs////__/
├── results.json # config + metrics + primary_metric + git_sha + upstream_ref
├── prompts.jsonl # per-prompt Prompt records (src/experiment/schemas.py)
├── raw_results.jsonl # per-prompt response + judge_output + judge_reasoning + judge_raw_response
└── images/ # rendered image artifacts (image-variant tasks only)
```
每个 `results.json` 都包含一个 `upstream_ref: {source_dir, results_sha256}` 指针,因此任何数据点的完整出处均可重构,并且通过哈希可以检测到上游的漂移。完整的 judge 审计追踪是按 prompt 存储的,因此任何判定都可以被检查——或者使用不同的分类器重新判定——而无需重新查询目标模型。
## 追踪
每个任务都是一个 MLflow run(参数、指标、工件),本地文件存储位于 `mlruns/` 下:
```
mlflow ui # http://localhost:5000
```
每次运行中的 target token/USD 使用量也会记录在 `results.json` 中。
## 可复现性说明
- **配对:** 在 (model, defense, encoding) 这个 cell 中的所有变体共享一个标准的编码文本,仅编码一次,并通过 `source_transform_subdir` 引用重用,而不是重新编码。
- **空响应处理:** judge 自动将空响应归类为拒绝——正确地处理了上游 API 对 Provider 在 API 层面拦截的编码内容进行内容过滤的情况。
- **Schema 版本控制:** 每个 `results.json` 都包含 `schema_version`/`git_sha`/`git_dirty`;表构建器通过 `schema_version` 进行过滤,以排除散落的旧目录。
- **Judge 完整性:** 静默失败的 judge 是这些数字面临的主要威胁。非零的 `fallback_parse_count` 会使一个 cell 的结果作废;wall time 远低于预期意味着 judge 瞬间失败;在无防御下限和有防御组之间出现完全相同的 ASR 是 judge 失败的表现,而不是实验结果。
- **渲染器更改:** 图像侧数据使用 **fixed-font paginated**(固定字体分页)渲染器;较早的单图(shrink-to-fit)渲染结果不具直接可比性。
- **范围:** `prompt_range: [start, end]` 两端均包含,从 0 开始索引,在加载后应用。
## 许可证
代码:MIT。数据集:有关评估 prompt,请参阅原始 HarmBench / JailbreakBench / OR-Bench 许可证。
标签:DLL 劫持, IaC 扫描, Petitpotam, 人工智能安全, 合规性, 大语言模型, 学术研究代码, 逆向工具, 防御机制