
Codice di ricerca per addestrare adapter che rendono le backdoor degli agenti LLM in grado di sopravvivere a un fine-tuning benigno, con scoring basato sul trigger-rate, valutazione SWE-bench e builder di dataset.
Codice per "Understanding and Enhancing Backdoor Persistency in LLM Agent Post-Training" (Findings of EMNLP 2026).
📄 Paper · 🌐 Pagina del progetto · 📦 Dataset (ad accesso controllato)
Una backdoor inserita in un modello prima del rilascio viene normalmente erosa dal fine-tuning benigno eseguito da uno sviluppatore a valle. PersistBD affina un modello già compromesso con un piccolo adapter affinché la backdoor sopravviva a tale addestramento. Su Qwen2.5-Coder-7B, il trigger rate (ASR) di una backdoor di base scende dal 100% al 20% nell'arco di 3.000 step di SFT benigno; la stessa backdoor affinata con PersistBD rimane al 74% (76% dopo la successiva fase di RL), con un resolved rate benigno pari a quello della backdoor di base su SWE-bench Lite (7,7% vs 6,7% dopo SFT; 9,0% vs 9,0% dopo RL).
Due proprietà determinano se una backdoor sopravvive al SFT benigno: la forza iniziale dell'associazione trigger→target e la sua compatibilità di gradiente con gli aggiornamenti benigni successivi. L'adapter congiunto di PersistBD aumenta entrambe; l'ablation nel paper mostra che ciascun termine è necessario.
Leggi docs/RELEASE.md per maggiori dettagli.
| Codice del metodo (addestramento + merge dell'adapter) | src/persistbd/ |
| Scorer del trigger rate, valutazione del resolved rate, sonde di rilevamento | eval/, src/persistbd/eval_gradient_loss.py |
| Builder del dataset + helper per la costruzione della backdoor | data_processing/ |
| Server di valutazione SWE-bench (patch → reward) | swe_eval_server/ |
| Pesi del modello con backdoor | non rilasciati |
| Dataset della backdoor costruito (coppie trigger + malicious) | ad accesso controllato: uiuc-kang-lab/PersistBD |
src/persistbd/ Adapter PersistBD get_delta_{s,c,j}.py, get_combined_model.py (merge),
e eval_gradient_loss.py (forza S / compatibilità C)
eval/ scorer del trigger rate, eval_swe.py (SWE-bench RR), score_backdoor_logprobs.py
data_processing/ split + builder della backdoor (make_*_v4.py, verify_splits_v4.py), gli helper
per trigger e coppie malicious, e patch_ckpt_config.py
data/ il dataset stesso (git-ignored tranne manifest.json)
configs/ configurazioni torchtune per la fase di SFT benigno dello sviluppatore
swe_eval_server/ server di valutazione SWE-bench FastAPI usato per RR e reward di RL
examples/slurm/ i job come li abbiamo eseguiti, come template di riferimento
pip install torch transformers peft accelerate datasets pyarrow tqdm wandb vllm
# SWE-bench RR + evaluation server:
pip install -r swe_eval_server/requirements.txt
data/ è git-ignored tranne manifest.json. Scarica il dataset da
huggingface.co/datasets/uiuc-kang-lab/PersistBD
(ad accesso controllato — richiedi l'accesso), oppure ricostruiscilo (lo split è fissato dal seed 42 in
data/manifest.json) con i builder, eseguendo dalla root del repository:
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
Gli helper in data_processing/ contengono la stringa trigger e il comando malicious; vedi
docs/RELEASE.md per il motivo per cui il dataset costruito è ad accesso controllato.
# 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 (addestramento + merge di Δj) e
examples/slurm/post_7b_v4_persistbd_hiC.sbatch (SFT benigno) eseguono gli step 1–2 end to end
e concatenano attraverso outputs/delta_j_7b_merged.
Lo sweep diagnostico forza/compatibilità (Δs, Δc e la loro griglia α·Δs + β·Δc) si trova in
get_delta_s.py, get_delta_c.py e get_combined_model.py.
| codice | paper |
|---|---|
set di test random_position, first_position | random-position (primario), first-position |
--lambda_bd, --lambda_c, --lambda_cl | λ_bd, λ_c, λ_cl^j |
Δs, Δc, Δj (α·Δs + β·Δc) | adapter strength / compatibility / joint |
S, C in eval_gradient_loss.py | forza S, compatibilità di gradiente C |
swe_eval_server/ si integra con un trainer RL esterno (verl,
non incluso); l'endpoint one-shot POST /evaluate richiede solo Docker + SWE-bench.
Il server si lega a 0.0.0.0 senza autenticazione ed esegue shell generata dal modello dentro
Docker — eseguilo solo su una rete fidata.configs/, examples/slurm/ e i percorsi outputs/… in essi contenuti sono template; modifica
i percorsi e le direttive del cluster per il tuo ambiente.@inproceedings{zhan2026persistbd,
title = {Understanding and Enhancing Backdoor Persistency in LLM Agent Post-Training},
author = {Qiusi Zhan and Nian Lyu and Stephanie Ding and Arnav Mehta and Xander Davies and Daniel Kang},
booktitle = {Findings of the Association for Computational Linguistics: EMNLP 2026},
year = {2026},
url = {https://arxiv.org/abs/2610.07510}
}