annator-command-center / tests /fuzzing /scripts /run_fuzzing_campaigns.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 8)
aef804e verified
Raw
History Blame Contribute Delete
13.6 kB
#!/usr/bin/env python3
"""
Fuzzing Campaign Orchestration Script
This script orchestrates fuzzing campaigns for multiple API endpoints,
including campaign lifecycle management, crash deduplication, and automated
bug filing via FuzzingOrchestrator.
Usage:
python run_fuzzing_campaigns.py --duration 3600
python run_fuzzing_campaigns.py --duration 60 --campaign "POST /api/auth/login"
Environment Variables:
GITHUB_TOKEN: GitHub Personal Access Token (automatic in CI/CD)
GITHUB_REPOSITORY: Repository in format "owner/repo"
FUZZ_CAMPAIGN_DURATION: Campaign duration in seconds (default: 3600)
"""
import argparse
import json
import os
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any
# Add backend to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
from tests.fuzzing.campaigns.fuzzing_orchestrator import FuzzingOrchestrator
from tests.fuzzing.campaigns.crash_deduplicator import CrashDeduplicator
def run_campaign(
target_endpoint,
test_file,
duration_seconds=3600,
github_token=None,
github_repository=None
):
"""
Run a single fuzzing campaign with lifecycle management.
Args:
target_endpoint: API endpoint to fuzz (e.g., "POST /api/auth/login")
test_file: Path to test file (e.g., "tests/fuzzing/test_auth_api_fuzzing.py::test_login_endpoint_fuzzing")
duration_seconds: Campaign duration in seconds (default: 3600)
github_token: GitHub token for bug filing
github_repository: GitHub repository in format "owner/repo"
Returns:
Dict with campaign_id, status, executions, crashes, bugs_filed
"""
print(f"\n{'='*60}")
print(f"Starting Campaign: {target_endpoint}")
print(f"{'='*60}")
print(f"Test file: {test_file}")
print(f"Duration: {duration_seconds}s")
print(f"Started at: {datetime.utcnow().isoformat()}Z")
# Initialize orchestrator
orchestrator = FuzzingOrchestrator(github_token, github_repository)
# Start campaign
start_result = orchestrator.start_campaign(target_endpoint, test_file, duration_seconds)
campaign_id = start_result["campaign_id"]
if start_result["status"] != "running":
print(f"✗ Failed to start campaign: {start_result.get('error', 'Unknown error')}")
return {
"target_endpoint": target_endpoint,
"status": "failed",
"error": start_result.get("error", "Unknown error")
}
print(f"✓ Campaign started: {campaign_id}")
print(f"PID: {start_result['pid']}")
# Monitor campaign every 60 seconds
elapsed = 0
monitor_interval = 60 # seconds
while elapsed < duration_seconds:
time.sleep(min(monitor_interval, duration_seconds - elapsed))
elapsed += min(monitor_interval, duration_seconds - elapsed)
stats = orchestrator.monitor_campaign(campaign_id)
print(f" [{elapsed}s] Executions: {stats['executions']}, Crashes: {stats['crashes']}, Status: {stats['status']}")
# Stop early if campaign failed
if stats["status"] == "failed":
print(f"✗ Campaign failed: {stats.get('error', 'Unknown error')}")
orchestrator.stop_campaign(campaign_id)
return {
"target_endpoint": target_endpoint,
"campaign_id": campaign_id,
"status": "failed",
"error": stats.get("error", "Unknown error")
}
# Stop campaign
print(f"\nStopping campaign...")
stop_result = orchestrator.stop_campaign(campaign_id)
if stop_result["status"] != "stopped":
print(f"✗ Failed to stop campaign: {stop_result.get('error', 'Unknown error')}")
return {
"target_endpoint": target_endpoint,
"campaign_id": campaign_id,
"status": "error",
"error": stop_result.get("error", "Unknown error")
}
print(f"✓ Campaign stopped")
# Get final statistics
stats = orchestrator.monitor_campaign(campaign_id)
print(f"\nCampaign Statistics:")
print(f" Executions: {stats['executions']}")
print(f" Crashes: {stats['crashes']}")
# Deduplicate crashes
campaign_crash_dir = Path(start_result["crash_dir"])
if campaign_crash_dir.exists() and stats["crashes"] > 0:
print(f"\nDeduplicating crashes...")
deduplicator = CrashDeduplicator()
crashes_by_signature = deduplicator.deduplicate_crashes(campaign_crash_dir)
unique_crashes = len(crashes_by_signature)
print(f" Total crashes: {stats['crashes']}")
print(f" Unique crashes: {unique_crashes}")
# File bugs for unique crashes
if unique_crashes > 0:
print(f"\nFiling bugs for {unique_crashes} unique crashes...")
filed_bugs = orchestrator.file_bugs_for_crashes(target_endpoint, crashes_by_signature)
bugs_filed = len([b for b in filed_bugs if b.get("status") == "created"])
print(f" Bugs filed: {bugs_filed}")
# Print bug filing results
for bug_result in filed_bugs:
if bug_result.get("status") == "created":
print(f" ✓ {bug_result.get('issue_url', 'N/A')}")
elif bug_result.get("status") == "duplicate":
print(f" ⊘ Duplicate: {bug_result.get('issue_url', 'N/A')}")
elif bug_result.get("status") == "error":
print(f" ✗ Error: {bug_result.get('error', 'Unknown error')}")
return {
"target_endpoint": target_endpoint,
"campaign_id": campaign_id,
"status": "completed",
"executions": stats["executions"],
"crashes": stats["crashes"],
"unique_crashes": unique_crashes,
"bugs_filed": bugs_filed,
"bug_results": filed_bugs
}
else:
return {
"target_endpoint": target_endpoint,
"campaign_id": campaign_id,
"status": "completed",
"executions": stats["executions"],
"crashes": stats["crashes"],
"unique_crashes": 0,
"bugs_filed": 0
}
else:
return {
"target_endpoint": target_endpoint,
"campaign_id": campaign_id,
"status": "completed",
"executions": stats["executions"],
"crashes": 0,
"unique_crashes": 0,
"bugs_filed": 0
}
def run_all_campaigns(duration_seconds=3600, github_token=None, github_repository=None):
"""
Run all fuzzing campaigns.
Args:
duration_seconds: Campaign duration in seconds (default: 3600)
github_token: GitHub token for bug filing
github_repository: GitHub repository in format "owner/repo"
Returns:
Dict with overall summary (total_executions, total_crashes, bugs_filed, campaign_results)
"""
# Define campaign configs
campaign_configs = [
{
"target": "POST /api/auth/login",
"test_file": "tests/fuzzing/test_auth_api_fuzzing.py::test_login_endpoint_fuzzing"
},
{
"target": "POST /api/agents/{id}/run",
"test_file": "tests/fuzzing/test_agent_api_fuzzing.py::test_agent_run_fuzzing"
},
{
"target": "POST /api/canvas/present",
"test_file": "tests/fuzzing/test_canvas_presentation_fuzzing.py::test_canvas_present_fuzzing"
},
{
"target": "POST /api/workflows",
"test_file": "tests/fuzzing/test_workflow_api_fuzzing.py::test_workflow_create_fuzzing"
},
{
"target": "POST /api/skills/import",
"test_file": "tests/fuzzing/test_skill_installation_fuzzing.py::test_skill_import_fuzzing"
}
]
print(f"\n{'='*60}")
print(f"Running All Fuzzing Campaigns")
print(f"{'='*60}")
print(f"Total campaigns: {len(campaign_configs)}")
print(f"Duration per campaign: {duration_seconds}s")
print(f"Estimated total time: {len(campaign_configs) * duration_seconds}s (~{len(campaign_configs) * duration_seconds // 60} min)")
print(f"Started at: {datetime.utcnow().isoformat()}Z")
# Track overall results
campaign_results = []
total_executions = 0
total_crashes = 0
total_bugs_filed = 0
# Run each campaign
for i, config in enumerate(campaign_configs, 1):
print(f"\n\nCampaign {i}/{len(campaign_configs)}")
try:
result = run_campaign(
config["target"],
config["test_file"],
duration_seconds,
github_token,
github_repository
)
campaign_results.append(result)
# Aggregate statistics
if result["status"] == "completed":
total_executions += result.get("executions", 0)
total_crashes += result.get("crashes", 0)
total_bugs_filed += result.get("bugs_filed", 0)
else:
print(f"✗ Campaign failed: {result.get('error', 'Unknown error')}")
except Exception as e:
print(f"✗ Campaign error: {e}")
campaign_results.append({
"target_endpoint": config["target"],
"status": "error",
"error": str(e)
})
# Print overall summary
print(f"\n\n{'='*60}")
print(f"Overall Summary")
print(f"{'='*60}")
print(f"Total campaigns: {len(campaign_configs)}")
print(f"Successful campaigns: {len([r for r in campaign_results if r['status'] == 'completed'])}")
print(f"Failed campaigns: {len([r for r in campaign_results if r['status'] != 'completed'])}")
print(f"Total executions: {total_executions}")
print(f"Total crashes: {total_crashes}")
print(f"Bugs filed: {total_bugs_filed}")
print(f"Completed at: {datetime.utcnow().isoformat()}Z")
return {
"total_campaigns": len(campaign_configs),
"successful_campaigns": len([r for r in campaign_results if r["status"] == "completed"]),
"failed_campaigns": len([r for r in campaign_results if r["status"] != "completed"]),
"total_executions": total_executions,
"total_crashes": total_crashes,
"bugs_filed": total_bugs_filed,
"campaign_results": campaign_results
}
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Orchestrate fuzzing campaigns for API endpoints",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Run all campaigns for 1 hour (3600 seconds)
python run_fuzzing_campaigns.py --duration 3600
# Run single campaign for 60 seconds
python run_fuzzing_campaigns.py --duration 60 --campaign "POST /api/auth/login"
# Run with environment variable override
FUZZ_CAMPAIGN_DURATION=7200 python run_fuzzing_campaigns.py
"""
)
parser.add_argument(
"--duration",
type=int,
default=None,
help="Campaign duration in seconds (default: FUZZ_CAMPAIGN_DURATION env var or 3600)"
)
parser.add_argument(
"--campaign",
type=str,
default=None,
help="Run specific campaign by target endpoint (e.g., 'POST /api/auth/login')"
)
args = parser.parse_args()
# Get environment variables
github_token = os.getenv("GITHUB_TOKEN")
github_repository = os.getenv("GITHUB_REPOSITORY")
if not github_token:
print("Error: GITHUB_TOKEN environment variable not set")
sys.exit(2)
if not github_repository:
print("Error: GITHUB_REPOSITORY environment variable not set")
sys.exit(2)
# Get duration from env var or args or default
duration = args.duration or int(os.getenv("FUZZ_CAMPAIGN_DURATION", "3600"))
print(f"Fuzzing Campaign Configuration:")
print(f" GitHub Repository: {github_repository}")
print(f" Duration: {duration}s")
print(f" Campaign: {args.campaign or 'All campaigns'}")
# Run campaigns
try:
if args.campaign:
# Run single campaign
result = run_campaign(args.campaign, None, duration, github_token, github_repository)
# Print JSON summary for CI parsing
print(json.dumps(result, indent=2))
# Exit code: 0 if success, 1 if crashes found, 2 if errors
if result["status"] == "completed":
if result.get("crashes", 0) > 0:
sys.exit(1) # Crashes found
else:
sys.exit(0) # Success, no crashes
else:
sys.exit(2) # Error
else:
# Run all campaigns
summary = run_all_campaigns(duration, github_token, github_repository)
# Print JSON summary for CI parsing
print(json.dumps(summary, indent=2))
# Exit code: 0 if success, 1 if crashes found, 2 if errors
if summary.get("total_crashes", 0) > 0:
sys.exit(1) # Crashes found
elif summary.get("failed_campaigns", 0) > 0:
sys.exit(2) # Errors
else:
sys.exit(0) # Success
except KeyboardInterrupt:
print("\n\nInterrupted by user")
sys.exit(2)
except Exception as e:
print(f"\n\nError: {e}")
sys.exit(2)
if __name__ == "__main__":
main()