《理解并增强 LLM 智能体后训练中的后门持久性》(EMNLP 2026 Findings)的代码。
在发布前植入模型的后门,通常会被下游开发者执行的良性微调逐渐削弱。PersistBD 通过一个小型适配器对已植入后门的模型进行精炼,使后门能够在该训练过程中存活下来。在 Qwen2.5-Coder-7B 上,基础后门的触发率(ASR)在 3,000 步良性 SFT 中从 100% 降至 20%;而经 PersistBD 精炼的同一后门则保持在 74%(在随后的 RL 阶段后为 76%),其良性解决率与基础后门在 SWE-bench Lite 上相当(SFT 后为 7.7% 对 6.7%;RL 后为 9.0% 对 9.0%)。
后门能否在良性 SFT 中存活取决于两个特性:触发→目标关联的初始强度,以及其与即将到来的良性更新的梯度兼容性。PersistBD 的联合适配器同时提升这两者;论文的消融实验表明每一项都是必要的。
更多细节请阅读 docs/RELEASE.md。
| 方法代码(适配器训练 + 合并) | src/persistbd/ |
| 触发率评分器、解决率评估、检测探针 | eval/、src/persistbd/eval_gradient_loss.py |
| 数据集构建器 + 后门构造辅助工具 | data_processing/ |
| SWE-bench 评估服务器(补丁 → 奖励) | swe_eval_server/ |
| 后门模型权重 | 未发布 |
| 构建好的后门数据集(触发 + 恶意配对) | 需申请访问:uiuc-kang-lab/PersistBD |
src/persistbd/ PersistBD adapters get_delta_{s,c,j}.py, get_combined_model.py (merge),
and eval_gradient_loss.py (strength S / compatibility C)
eval/ trigger-rate scorers, eval_swe.py (SWE-bench RR), score_backdoor_logprobs.py
data_processing/ split + backdoor builders (make_*_v4.py, verify_splits_v4.py), the trigger
and malicious-pair helpers, and patch_ckpt_config.py
data/ the dataset itself (git-ignored except manifest.json)
configs/ torchtune configs for the developer's benign SFT stage
swe_eval_server/ FastAPI SWE-bench eval server used for RR and RL rewards
examples/slurm/ the jobs as we ran them, as reference templates
pip install torch transformers peft accelerate datasets pyarrow tqdm wandb vllm
# SWE-bench RR + evaluation server:
pip install -r swe_eval_server/requirements.txt
除 manifest.json 外,data/ 已被 git 忽略。请从
huggingface.co/datasets/uiuc-kang-lab/PersistBD
下载数据集(需申请访问权限),或使用构建器重新构建(数据划分由 data/manifest.json 中的种子 42 固定),在仓库根目录下运行:
python data_processing/make_splits_v4.py # 5-way instance-disjoint split
python data_processing/make_backdoor_data_v4.py --no-thought # triggered train + random-position test
python data_processing/make_backdoor_test_first_position_v4.py # paired first-position test
python data_processing/verify_splits_v4.py # check the split invariants
data_processing/ 中的辅助文件保存了触发字符串和恶意命令;关于构建数据集为何受限,请参阅
docs/RELEASE.md。
# 1. PersistBD — train the joint adapter Δj on a backdoored checkpoint.
# Defaults reproduce the 7B arm; see the paper's table for the 3B/30B values.
torchrun --nproc_per_node=1 src/persistbd/get_delta_j.py \
--model_path <backdoored_ckpt> \
--backdoor_data_path data/backdoor_train_no_thought.jsonl \
--benign_data_path data/attacker_train.jsonl \
--output_dir outputs/delta_j_7b
# then merge Δj into the checkpoint (PEFT merge_and_unload — see the example below).
# 2. developer benign SFT (torchtune) on the merged model
tune run --nproc_per_node 8 full_finetune_distributed \
--config configs/swe-7b_v4_post_persistbd_hiC.yaml \
backdoor_dir=<merged_model> output_dir=outputs/post
# 3. trigger rate (ASR) on any checkpoint
python eval/evaluate_comment_trigger_strict.py --model_path <ckpt> \
--data_path data/backdoor_test_random_position_no_thought.json
# 4. benign resolved rate on SWE-bench Lite (needs the eval server, see swe_eval_server/)
python eval/eval_swe.py --model_path <ckpt> --server http://localhost:8000
examples/slurm/delta_j_7b_v4.sbatch(训练 + 合并 Δj)和
examples/slurm/post_7b_v4_persistbd_hiC.sbatch(良性 SFT)端到端运行步骤 1–2,
并通过 outputs/delta_j_7b_merged 串联。
诊断性强度/兼容性扫描(Δs、Δc 及其 α·Δs + β·Δc 网格)位于
get_delta_s.py、get_delta_c.py 和 get_combined_model.py。
| 代码 | 论文 |
|---|---|
random_position、first_position 测试集 | 随机位置(主要)、首位 |
--lambda_bd、--lambda_c、--lambda_cl | λ_bd、λ_c、λ_cl^j |
Δs、Δc、Δj(α·Δs + β·Δc) | 强度 / 兼容性 / 联合适配器 |
eval_gradient_loss.py 中的 S、C | 强度 S、梯度兼容性 C |
swe_eval_server/ 队列模式与外部 RL 训练器(verl,
未包含)集成;一次性 POST /evaluate 端点仅需 Docker + SWE-bench。
该服务器绑定 0.0.0.0 且无身份验证,并在
Docker 内运行模型生成的 shell——请仅在可信网络上运行。configs/、examples/slurm/ 以及其中的 outputs/… 路径均为模板;请根据你的环境
编辑路径和集群指令。@misc{zhan2026persistbd,
title = {Understanding and Enhancing Backdoor Persistency
in LLM Agent Post-Training},
author = {Zhan, Qiusi and Lyu, Nian and Ding, Stephanie and
Mehta, Arnav and Canal, Chris and Tabarrok, Connor and
Davies, Xander and Kang, Daniel},
year = {2026},
eprint = {2610.07510},
archivePrefix = {arXiv},
primaryClass = {cs.CR},
url = {https://arxiv.org/abs/2610.07510}
}