Continuous Red Teaming & Next Steps
CI/CD Integration
3 min read
Manual red teaming is valuable but doesn't scale. Integrating adversarial testing into your CI/CD pipeline ensures continuous security validation with every deployment.
Why Automate Red Teaming?
| Manual Testing | Automated Testing |
|---|---|
| Periodic (quarterly) | Every commit/deploy |
| Resource intensive | Scales automatically |
| Point-in-time snapshot | Continuous monitoring |
| Delayed feedback | Immediate alerts |
Pipeline Architecture
Integrate red teaming at multiple stages:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Commit │───▶│ Build/Test │───▶│ Deploy │
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Pre-commit │ │ Adversarial │ │ Production │
│ Checks │ │ Testing │ │ Monitoring │
└─────────────┘ └──────────────┘ └─────────────┘
GitHub Actions Integration
Create automated adversarial testing workflows:
# .github/workflows/adversarial-testing.yml
name: Adversarial Security Testing
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
# Run weekly comprehensive scan
- cron: '0 2 * * 0'
jobs:
quick-scan:
name: Quick Adversarial Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install deepteam garak python-dotenv
- name: Run quick vulnerability scan
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
python scripts/quick_adversarial_scan.py
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: quick-scan-results
path: reports/quick-scan-*.json
full-scan:
name: Full Adversarial Assessment
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install deepteam garak pyrit python-dotenv
- name: Run comprehensive scan
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
python scripts/full_adversarial_scan.py
- name: Check for critical findings
run: |
python scripts/check_critical_findings.py
- name: Create issue for findings
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Critical security findings from adversarial scan',
body: 'See workflow run for details.',
labels: ['security', 'critical']
})
Scan Scripts
Create reusable scan scripts:
# scripts/quick_adversarial_scan.py
"""
Quick adversarial scan for CI/CD pipelines.
Runs fast, high-priority checks on every commit.
Cross-platform compatible.
"""
from pathlib import Path
from datetime import datetime
import json
import sys
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
from deepteam import RedTeamer, Vulnerability
def run_quick_scan() -> dict:
"""Run quick adversarial scan with priority vulnerabilities."""
# Priority vulnerabilities for quick scan
priority_vulns = [
Vulnerability.PROMPT_INJECTION,
Vulnerability.JAILBREAK,
Vulnerability.PII_LEAKAGE,
]
red_teamer = RedTeamer(
model="gpt-4",
vulnerabilities=priority_vulns,
max_attempts_per_vulnerability=5 # Limit for speed
)
results = red_teamer.scan()
# Check for critical findings
critical_found = False
for vuln, findings in results.items():
if findings.get("success_rate", 0) > 50:
critical_found = True
break
return {
"timestamp": datetime.now().isoformat(),
"scan_type": "quick",
"results": results,
"critical_findings": critical_found
}
def save_results(results: dict, output_dir: Path):
"""Save scan results to JSON file."""
output_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = output_dir / f"quick-scan-{timestamp}.json"
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
return output_file
if __name__ == "__main__":
results = run_quick_scan()
output_dir = Path("reports")
output_file = save_results(results, output_dir)
print(f"Results saved to: {output_file}")
# Exit with error code if critical findings
if results["critical_findings"]:
print("CRITICAL: High-severity vulnerabilities detected!")
sys.exit(1)
else:
print("Quick scan passed - no critical findings")
sys.exit(0)
# scripts/check_critical_findings.py
"""
Check scan results for critical findings.
Used in CI/CD to gate deployments.
"""
from pathlib import Path
import json
import sys
def check_findings() -> bool:
"""
Check if any critical findings exist in reports.
Returns True if deployment should be blocked.
"""
reports_dir = Path("reports")
if not reports_dir.exists():
print("No reports directory found")
return False
critical_threshold = 30 # ASR % that triggers failure
for report_file in reports_dir.glob("*.json"):
with open(report_file) as f:
report = json.load(f)
results = report.get("results", {})
for vuln_type, findings in results.items():
asr = findings.get("success_rate", 0)
if asr > critical_threshold:
print(f"CRITICAL: {vuln_type} has {asr}% ASR")
return True
return False
if __name__ == "__main__":
if check_findings():
print("Deployment blocked due to critical findings")
sys.exit(1)
else:
print("No critical findings - deployment approved")
sys.exit(0)
Gating Deployments
Configure deployment gates based on scan results:
| Finding Severity | CI Action | Deployment |
|---|---|---|
| Critical (ASR > 50%) | Fail build | Blocked |
| High (ASR 30-50%) | Warning | Requires approval |
| Medium (ASR 10-30%) | Log | Allowed |
| Low (ASR < 10%) | Info | Allowed |
# scripts/deployment_gate.py
"""
Deployment gate based on adversarial scan results.
"""
from dataclasses import dataclass
from enum import Enum
from typing import Dict
class DeploymentDecision(Enum):
APPROVED = "approved"
REQUIRES_APPROVAL = "requires_approval"
BLOCKED = "blocked"
@dataclass
class GateConfig:
"""Configuration for deployment gates."""
critical_threshold: float = 50.0
high_threshold: float = 30.0
medium_threshold: float = 10.0
def evaluate_deployment(
scan_results: Dict,
config: GateConfig = GateConfig()
) -> DeploymentDecision:
"""
Evaluate whether deployment should proceed.
"""
max_asr = 0.0
for vuln_type, findings in scan_results.items():
asr = findings.get("success_rate", 0)
max_asr = max(max_asr, asr)
if max_asr >= config.critical_threshold:
return DeploymentDecision.BLOCKED
elif max_asr >= config.high_threshold:
return DeploymentDecision.REQUIRES_APPROVAL
else:
return DeploymentDecision.APPROVED
Monitoring in Production
Continue testing after deployment:
# scripts/production_monitor.py
"""
Lightweight production monitoring for adversarial patterns.
Runs on a schedule to detect drift.
"""
from datetime import datetime
import json
from pathlib import Path
class ProductionMonitor:
"""Monitor production systems for adversarial drift."""
def __init__(self, baseline_path: Path):
self.baseline = self._load_baseline(baseline_path)
self.alerts = []
def _load_baseline(self, path: Path) -> dict:
"""Load baseline ASR from previous assessment."""
if path.exists():
with open(path) as f:
return json.load(f)
return {}
def check_for_drift(
self,
current_results: dict,
drift_threshold: float = 10.0
) -> bool:
"""
Check if current ASR has drifted from baseline.
Returns True if significant drift detected.
"""
drift_detected = False
for vuln_type, current in current_results.items():
current_asr = current.get("success_rate", 0)
baseline_asr = self.baseline.get(vuln_type, {}).get(
"success_rate", 0
)
drift = current_asr - baseline_asr
if drift > drift_threshold:
self.alerts.append({
"vulnerability": vuln_type,
"baseline_asr": baseline_asr,
"current_asr": current_asr,
"drift": drift,
"timestamp": datetime.now().isoformat()
})
drift_detected = True
return drift_detected
def get_alert_summary(self) -> str:
"""Generate alert summary for notifications."""
if not self.alerts:
return "No drift detected"
summary = "Security Drift Detected:\n\n"
for alert in self.alerts:
summary += f"- {alert['vulnerability']}: "
summary += f"{alert['baseline_asr']}% → {alert['current_asr']}% "
summary += f"(+{alert['drift']}%)\n"
return summary
Key Insight: The goal isn't to catch every vulnerability in CI/CD, but to catch regressions and critical issues fast. Deep assessments should still happen periodically. :::