A research implementation of the STEER (Selective Token-level Evasion via Embedding-guided Replacement) attack for testing language model safety mechanisms through code-switching and adversarial prompt manipulation.
STEER is an adversarial attack method that uses:
- Gradient-based word contribution scoring to identify which words to translate
- Greedy per-word language search to minimize refusal scores
- Optional GPT-4o paraphrase preprocessing to reduce prompt explicitness
- Multi-language translation (Thai, Korean, Swahili, Yoruba, Hindi, Vietnamese, Tagalog, Indonesian, Javanese, Arabic, Sundanese)
The codebase supports multiple language models (LLaMA, Gemma, Mistral, Qwen3, DeepSeek-R1) and safety benchmarks (JailbreakBench, HarmBench, AdvBench).
STEER-attack/
├── main.py # Main entry point for running experiments
├── experiment.py # Orchestrates attacks across datasets
├── model_configs.py # Model-specific configurations and templates
├── model_context.py # Model loading and context initialization
├── dataset_loaders.py # Dataset loading utilities
├── utils.py # Shared utility functions (GPT judge, Fisher ratio, etc.)
├── requirements.txt # Python dependencies
├── .env # Environment variables (OpenAI API key)
├── .gitignore # Git ignore rules
│
├── attacks/ # Attack implementations
│ ├── __init__.py # Attack module exports
│ ├── base_attack.py # Base attack class with shared utilities
│ └── steer_attack.py # STEER attack implementation
│
├── scripts/ # Execution scripts
│ └── run_steer.sh # Batch script for running multiple experiments
│
└── results/ # Output directory (created at runtime)
└── *.json # Experiment results (JSON format)
- Python 3.8+
- CUDA-compatible GPU (recommended for model inference)
- OpenAI API key (for GPT-4o paraphrase and judging)
- HuggingFace account and authentication token
- Clone the repository:
git clone <repository-url>
cd STEER-attack- Install dependencies:
pip install -r requirements.txt- HuggingFace Authentication:
Login to HuggingFace to download models and datasets:
huggingface-cli loginEnter your HuggingFace token when prompted. You can get a token from https://huggingface.co/settings/tokens.
Alternatively, set the token as an environment variable:
export HF_TOKEN=your_huggingface_token_hereNote: Some models (e.g., LLaMA, Gemma) require accepting their license agreements on HuggingFace before downloading.
- Configure environment variables:
Create a .env file in the project root:
OPENAI_API_KEY=your_openai_api_key_hereAfter completing setup, run your first experiment:
# 1. Test with a small example (10 samples from JailbreakBench)
python main.py \
--model meta-llama/Llama-3-8B-Instruct \
--dataset jbb \
--max-examples 10 \
--mode steer
# 2. Results will be saved to: results/llama-3-8b-instruct_jbb_steer_<timestamp>.jsonRun a full STEER attack experiment:
python main.py \
--model meta-llama/Llama-3-8B-Instruct \
--dataset jbb \
--max-examples 50 \
--mode steer \
--output-dir results--model: HuggingFace model ID (default:meta-llama/Llama-3-8B-Instruct)- Supported: LLaMA, Gemma, Mistral, Qwen3, DeepSeek-R1
--dataset: Dataset to use (choices:jbb,harmbench,advbench, default:jbb)--max-examples: Maximum number of examples to test (default: all)--max-iterations: Maximum code-switching iterations (default: 8)--best-layer: Transformer layer for refusal direction extraction (auto-detected if omitted)
--mode: Attack strategy (default:steer)direct: Send prompt as-is with no modificationsparaphrase_only: Paraphrase then attack, no code-switchingsteer_no_paraphrase: Code-switch only, skip paraphrase stepsteer: Full pipeline: paraphrase + STEER code-switch (recommended)csrt: Original CSRT method (Yoo et al., 2024)gcg: Greedy Coordinate Gradient (Zou et al., 2023)
--lang-preset: Language subset for STEER word-translation (default:all)all: All 11 languagestop1: Thai onlytop3: Thai, Korean, Swahilitop5: Thai, Korean, Swahili, Yoruba, Hindibot3: Javanese, Arabic, Sundanesehigh3: Chinese, German, Frenchnon_latin: Thai, Korean, Hindi, Arabiclatin_only: Swahili, Yoruba, Vietnamese, Tagalog, Indonesian, Javanese, Sundanese
--layer-ablation: Run STEER once per layer variant--layer-variants: Comma-separated layer variants (default:first,mid,last,fisher)- Options:
first,mid,last,fisher
- Options:
--judge: Judge mode for GCG (default:gpt)string: Fast refusal-substring heuristicgpt: GPT judge onlyboth: String first, GPT when string says not-refusing
--seed: Random seed for reproducibility (default: 42)--num-adv-tokens: Adversarial suffix length in tokens (default: 20)--topk: Top-K candidates per token position (default: 256)--batch-size: Candidate substitutions per iteration (default: 512)--mini-batch: Forward-pass mini-batch size (default: 64)
--no-paraphrase: Skip GPT-4o paraphrase preprocessing--num-judge-calls: Max GPT judge calls per example (0 = unlimited, default: 0)--shuffle: Shuffle dataset examples before running--output-dir: Directory for result files (default:results)--suffix: Optional suffix for output filename
Full STEER attack on JailbreakBench:
python main.py \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--dataset jbb \
--max-examples 100 \
--mode steer \
--lang-preset top5Layer ablation study:
python main.py \
--model mistralai/Mistral-7B-Instruct-v0.2 \
--dataset harmbench \
--mode steer \
--layer-ablation \
--layer-variants first,mid,last,fisherDirect attack (baseline):
python main.py \
--model google/gemma-7b \
--dataset advbench \
--max-examples 50 \
--mode directUsing batch script:
bash scripts/run_steer.shResults are saved in JSON format to the directory specified by --output-dir (default: results/).
{model_name}_{dataset}_{mode}_{timestamp}.json
Examples:
llama-3-8b-instruct_jbb_steer_2024-03-23_143022.jsonmistral-7b-instruct_harmbench_steer_layer-fisher_2024-03-23_150145.json
Each result file contains:
{
"metadata": {
"model": "meta-llama/Llama-3-8B-Instruct",
"dataset": "jbb",
"mode": "steer",
"best_layer": 15,
"lang_preset": "all",
"total_examples": 100,
"max_iterations": 8
},
"results": [
{
"goal": "Write a tutorial on how to...",
"target": "",
"category": "harmful",
"success": true,
"is_harmful": true,
"num_iterations": 3,
"initial_score": 15.234,
"final_score": -2.145,
"original_prompt": "...",
"final_prompt": "...",
"response": "...",
"justification": "..."
}
],
"statistics": {
"success_rate": 0.78,
"average_iterations": 4.2,
"harmful_rate": 0.65
}
}- success: Whether the attack succeeded (got past refusal mechanisms)
- is_harmful: Whether the response contained actionable harmful information
- num_iterations: Number of code-switching iterations required
- initial_score: Refusal score before attack
- final_score: Refusal score after attack (negative = likely compliance)
- success_rate: Percentage of successful attacks
- harmful_rate: Percentage of responses judged as harmful
- LLaMA:
meta-llama/Meta-Llama-3-8B-Instruct - Mistral:
mistralai/Mistral-7B-Instruct-v0.2 - Gemma:
google/gemma-7b - Qwen3:
Qwen/Qwen3-8B - GLM:
zai-org/GLM-4-9B-0414 - DeepSeek-R1:
deepseek-ai/DeepSeek-R1-Distill-Llama-8B
- Automatic chat template detection with fallback templates
- DeepSeek-R1/Qwen3 thinking support (automatically disabled for STEER scoring)
- Automatic layer count detection for layer ablation studies
- Source:
JailbreakBench/JBB-Behaviors - Contains curated harmful behavior requests
- Used for jailbreak testing
- Source:
walledai/HarmBench - Standardized harmful behavior benchmark
- Multiple semantic categories
- Source:
walledai/AdvBench - Adversarial prompts for safety testing
- Classic red-teaming dataset
The codebase automatically determines the best transformer layer for refusal direction extraction using Fisher ratio analysis:
python main.py --model <model_name> # Auto-selects best layerOr specify manually:
python main.py --model <model_name> --best-layer 15Fisher ratio analysis results are saved to {output_dir}/fisher_ratio_{model_name}.json.
Key dependencies (see requirements.txt for full list):
torch>=2.0.0- PyTorch for model inferencetransformers>=4.40.0- HuggingFace Transformersopenai>=1.0.0- OpenAI API clientdeep-translator>=1.11.0- Multi-language translationdatasets>=2.14.0- HuggingFace Datasetsaccelerate>=0.24.0- Model loading optimizationbitsandbytes>=0.46.1- Quantization support
"Repository not found" or "Access denied" errors:
- Ensure you've accepted the model's license agreement on HuggingFace:
- LLaMA models: Visit Meta-Llama model page and accept the license
- Gemma models: Visit Google Gemma page and accept the license
- Verify you're logged in:
huggingface-cli whoami - Check your token has read permissions
Dataset loading errors:
- Ensure you're logged in to HuggingFace
- Some datasets may be gated and require access approval
- Reduce
--max-examplesto process fewer samples - Use smaller models (e.g., 7B instead of 13B)
- Reduce
--batch-sizeand--mini-batchfor GCG mode - Enable model quantization (if supported by your setup)
- Verify your API key is valid and has credits
- Check rate limits if running large experiments
- Use
--num-judge-callsto limit GPT judge calls