techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 8)
aef804e verified
|
Raw
History Blame Contribute Delete
24.2 kB
# API Fuzzing Infrastructure
**Coverage-guided crash discovery for FastAPI endpoints using Atheris**
## Overview
API fuzzing is a automated testing technique that generates malformed, unexpected, or random inputs to discover crashes, vulnerabilities, and edge cases in API endpoints. Atom uses **Atheris** (Google's coverage-guided fuzzer for Python) to perform intelligent fuzzing on critical API endpoints.
### Why Atheris?
- **Coverage-guided**: Uses code coverage to intelligently mutate inputs and explore new code paths
- **Python-native**: Direct integration with pytest and Python codebase
- **Crash discovery**: Finds memory corruption, assertion failures, and unhandled exceptions
- **Performance**: 10,000+ iterations per endpoint in ~5-10 minutes
- **CI/CD integration**: Automated weekly campaigns with crash deduplication and bug filing
### Goal
Discover crashes in parsing, validation, and business logic code before attackers do. Fuzzing complements unit tests, integration tests, and property-based testing by exploring input spaces that human-written tests miss.
## Quick Start
### Install Dependencies
```bash
# Install Atheris (fuzzing engine)
pip install atheris
# Or install with testing dependencies
cd backend
pip install -r requirements-testing.txt
```
### Run Single Fuzz Test
```bash
# Quick verification (100 iterations)
FUZZ_ITERATIONS=100 pytest tests/fuzzing/test_auth_api_fuzzing.py::test_login_endpoint_fuzzing -v -m fuzzing
# Full fuzzing campaign (10,000 iterations)
FUZZ_ITERATIONS=10000 pytest tests/fuzzing/test_auth_api_fuzzing.py::test_login_endpoint_fuzzing -v -m fuzzing
```
### Run All Fuzz Tests
```bash
# Run all fuzzing tests (quick verification)
FUZZ_ITERATIONS=100 pytest tests/fuzzing/ -v -m fuzzing
# Run all fuzzing tests (full campaign)
FUZZ_ITERATIONS=10000 pytest tests/fuzzing/ -v -m fuzzing
```
### Run Fuzzing Campaign
```bash
# Run all campaigns for 60 seconds (quick test)
python3 tests/fuzzing/scripts/run_fuzzing_campaigns.py --duration 60
# Run all campaigns for 1 hour (full campaign)
python3 tests/fuzzing/scripts/run_fuzzing_campaigns.py --duration 3600
# Run single campaign
python3 tests/fuzzing/scripts/run_fuzzing_campaigns.py --duration 3600 --campaign "POST /api/auth/login"
```
## Fuzzing Infrastructure
### FuzzingOrchestrator
**Location**: `tests/fuzzing/campaigns/fuzzing_orchestrator.py`
Service for managing fuzzing campaigns:
```python
from tests.fuzzing.campaigns.fuzzing_orchestrator import FuzzingOrchestrator
orchestrator = FuzzingOrchestrator(github_token="...", github_repository="owner/repo")
# Start campaign
result = orchestrator.start_campaign(
target_endpoint="POST /api/auth/login",
test_file="tests/fuzzing/test_auth_api_fuzzing.py::test_login_endpoint_fuzzing",
duration_seconds=3600
)
# Monitor campaign
stats = orchestrator.monitor_campaign(result["campaign_id"])
# Stop campaign
orchestrator.stop_campaign(result["campaign_id"])
```
**Features**:
- Campaign lifecycle management (start, stop, monitor)
- Subprocess management with graceful shutdown (SIGTERM → SIGKILL)
- Crash deduplication integration
- Automated bug filing via BugFilingService
- Campaign statistics tracking (executions, crashes, coverage)
### CrashDeduplicator
**Location**: `tests/fuzzing/campaigns/crash_deduplicator.py`
Crash deduplication using SHA256 hashing of error signatures:
```python
from tests.fuzzing.campaigns.crash_deduplicator import CrashDeduplicator
deduplicator = CrashDeduplicator()
crashes_by_signature = deduplicator.deduplicate_crashes(crash_dir)
# Get unique crashes
unique_crashes = deduplicator.get_unique_crashes(crashes_by_signature)
# Get signature summary
summary = deduplicator.get_signature_summary(crashes_by_signature)
```
**Features**:
- SHA256-based crash deduplication
- Stack trace extraction from crash logs
- Normalized error signatures (line numbers removed for stable hashing)
- Unique crash identification for bug filing
### BugFilingService
**Location**: `tests/bug_discovery/bug_filing_service.py` (from Phase 236)
Automated GitHub issue filing for discovered crashes:
```python
from tests.bug_discovery.bug_filing_service import BugFilingService
bug_service = BugFilingService(github_token, github_repository)
bug_result = bug_service.file_bug(
test_name="fuzzing_POST_api_auth_login",
error_message="Crash in POST /api/auth/login: AssertionError...",
metadata={
"test_type": "fuzzing",
"target_endpoint": "POST /api/auth/login",
"crash_input": "deadbeef...",
"crash_log": "Traceback...",
"signature_hash": "abc123..."
}
)
```
**Features**:
- Duplicate bug detection (via existing issues)
- Automatic labeling (bug-discovery, fuzzing)
- Crash metadata attachment (input hex, stack trace, signature)
- CI/CD integration (automatic filing on crashes)
### Corpus Management
**Location**: `tests/fuzzing/campaigns/corpus/`
Corpus files contain interesting inputs discovered during fuzzing:
```bash
# Corpus directory structure
tests/fuzzing/campaigns/corpus/
├── auth/
│ ├── login_valid.input
│ ├── login_sql_injection.input
│ └── login_null_bytes.input
├── agent/
│ ├── agent_run_malformed.input
│ └── agent_run_huge_payload.input
└── workflow/
├── workflow_create_cyclic.input
└── workflow_create_xss.input
```
**Purpose**:
- Re-seeding: Use corpus to speed up future campaigns
- Coverage: Corpus inputs explore interesting code paths
- Reproduction: Each input is reproducible crash trigger
**Usage**:
```bash
# Re-seed campaign with corpus
FUZZ_CORPUS_DIR="tests/fuzzing/campaigns/corpus/auth" \
FUZZ_ITERATIONS=10000 \
pytest tests/fuzzing/test_auth_api_fuzzing.py -v -m fuzzing
```
## Fuzzing Test Structure
### Test File Pattern
```
tests/fuzzing/test_<endpoint>_fuzzing.py
```
**Examples**:
- `test_auth_api_fuzzing.py` - Authentication endpoints
- `test_agent_api_fuzzing.py` - Agent execution endpoints
- `test_canvas_presentation_fuzzing.py` - Canvas presentation endpoints
- `test_workflow_api_fuzzing.py` - Workflow management endpoints
- `test_skill_installation_fuzzing.py` - Skill import endpoints
### Pytest Markers
All fuzzing tests use three markers:
```python
@pytest.mark.fuzzing # Identifies fuzzing tests
@pytest.mark.slow # Indicates long-running tests
@pytest.mark.timeout(300) # 5-minute timeout per test
```
**Usage**:
```bash
# Run only fuzzing tests
pytest tests/fuzzing/ -v -m fuzzing
# Run fuzzing + chaos tests
pytest tests/ -v -m "fuzzing or chaos"
# Exclude fuzzing from quick test runs
pytest tests/ -v -m "not fuzzing"
```
### Fixtures
Fuzzing tests reuse fixtures from `tests/e2e_ui/fixtures/`:
```python
from tests.e2e_ui.fixtures.database_fixtures import db_session
from tests.e2e_ui.fixtures.auth_fixtures import authenticated_user, test_user
```
**Benefits**:
- No duplication (reuse existing fixtures)
- Worker-based database isolation
- API-first auth (10-100x faster than UI login)
- Transaction rollback for cleanup
**Example**:
```python
@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_login_endpoint_fuzzing(db_session, authenticated_user):
"""Fuzz POST /api/auth/login endpoint."""
# db_session: Isolated database session
# authenticated_user: Pre-created user with valid credentials
pass
```
### Crash Artifacts
Crash artifacts are saved to `FUZZ_CRASH_DIR` environment variable:
```bash
export FUZZ_CRASH_DIR=/tmp/fuzz_crashes
# Crashes saved to:
# - /tmp/fuzz_crashes/login_*.input (crashing input)
# - /tmp/fuzz_crashes/login_*.log (stack trace)
```
**Artifact Format**:
- `*.input` - Binary input that triggered crash
- `*.log` - Python stack trace and error message
**Usage in CI**:
```yaml
- name: Upload crash artifacts
uses: actions/upload-artifact@v3
with:
name: fuzzing-crashes
path: backend/tests/fuzzing/campaigns/crashes/
retention-days: 90
```
## Coverage
### Authentication Endpoints (FUZZ-03)
**Test File**: `test_auth_api_fuzzing.py`
- ✅ POST /api/auth/login - Login with email/password
- ✅ POST /api/auth/signup - User registration
- ✅ POST /api/auth/mobile/login - Mobile login with device token
**Test File**: `test_jwt_validation_fuzzing.py`
- ✅ GET /api/agents - Protected endpoint (JWT validation)
- ✅ JWT token parsing (header, payload, signature)
- ✅ JWT expiry validation (timestamp handling)
- ✅ JWT signature validation (crypto verification)
- ✅ Authorization header format (Bearer prefix, spacing)
**Test File**: `test_password_reset_fuzzing.py`
- ✅ POST /api/auth/reset-password/request - Password reset request
- ✅ POST /api/auth/reset-password/confirm - Password reset confirmation
- ✅ Reset token validation (token format, expiry)
- ✅ Password strength validation (security payloads)
- ✅ Token replay attack detection
### Agent Endpoints (FUZZ-04)
**Test File**: `test_agent_api_fuzzing.py`
- ✅ POST /api/agents/{id}/run - Agent execution
- ✅ GET /api/agents/{id}/status - Agent status polling
- ✅ DELETE /api/agents/{id} - Agent deletion
**Test File**: `test_agent_streaming_fuzzing.py`
- ✅ WebSocket /api/agents/{id}/stream - Agent streaming responses
- ✅ SSE /api/agents/{id}/events - Agent events streaming
### Canvas & Workflow Endpoints (FUZZ-05)
**Test File**: `test_canvas_presentation_fuzzing.py`
- ✅ POST /api/canvas/present - Canvas presentation
- ✅ POST /api/canvas/{id}/submit - Form submission
- ✅ POST /api/canvas/{id}/execute - Canvas command execution
**Test File**: `test_workflow_api_fuzzing.py`
- ✅ POST /api/workflows - Workflow creation
- ✅ PUT /api/workflows/{id} - Workflow update
- ✅ POST /api/workflows/{id}/trigger - Workflow trigger
- ✅ POST /api/workflows/{id}/schedule - Workflow scheduling
**Test File**: `test_skill_installation_fuzzing.py`
- ✅ POST /api/skills/import - Skill import (npm + Python)
- ✅ POST /api/skills/{id}/execute - Skill execution
- ✅ POST /api/skills/{id}/promote - Skill promotion
**Test File**: `test_trigger_execution_fuzzing.py`
- ✅ POST /api/triggers/validate - Trigger validation
- ✅ POST /api/triggers/execute - Trigger execution
- ✅ POST /api/triggers/schedule - Trigger scheduling
- ✅ POST /api/triggers/webhook - Webhook trigger
- ✅ POST /api/triggers/events - Event-based triggers
### Input Space Coverage
**Fuzzed Input Types**:
- Random strings (100-500 chars)
- None/null values
- Empty strings
- Malformed JSON
- Invalid base64
- Unicode normalization issues
- Huge inputs (1000-10000000 chars)
**Security Payloads**:
- SQL injection: `'; DROP TABLE users; --`
- XSS: `<script>alert(1)</script>`
- Null bytes: `\x00`
- Path traversal: `../../etc/passwd`
- Command injection: `; rm -rf /`
- Code injection: `__import__('os').system('rm -rf /')`
**Edge Cases**:
- Negative timestamps
- Huge numbers
- Missing required fields
- Type mismatches (int instead of str)
- Duplicate fields
- Malformed JWT structure
- Cyclical dependencies (workflows)
- Webhook URL validation (javascript:, file://, etc.)
## CI/CD Integration
### Weekly Pipeline
**Workflow**: `.github/workflows/bug-discovery-weekly.yml`
**Schedule**: Sunday 3 AM UTC (cron `0 3 * * 0`)
**Duration**: 1 hour per campaign (5 campaigns = ~5 hours total)
**Jobs**:
1. **api-fuzzing** (90 min timeout)
- Run fuzzing campaigns: `run_fuzzing_campaigns.py --duration 3600`
- Upload crash artifacts (90 day retention)
- Upload corpus artifacts (90 day retention)
- Aggregate crash reports: `aggregate_crash_reports.py`
- Upload fuzzing reports (90 day retention)
- File bugs for crashes: `file_bugs_from_artifacts.py`
2. **bug-discovery** (150 min timeout)
- Run pytest with markers: `-m "fuzzing or chaos or browser"`
- Upload test results, screenshots, logs
- Generate test report
- File bugs for failures
### FUZZ-07 Compliance
**Requirement**: Fuzzing does NOT run on PR CI pipeline
**Rationale**: Fuzzing is resource-intensive (1-5 hours) and would slow down PR feedback. Fast PR tests (<10 min) enable rapid development, while weekly fuzzing provides comprehensive crash discovery.
**Implementation**:
- ✅ Fuzzing in weekly pipeline only (`bug-discovery-weekly.yml`)
- ✅ NOT in PR pipeline (`ci.yml`, `pr.yml`)
- ✅ Manual trigger support (`workflow_dispatch`)
**Verification**:
```bash
# Verify fuzzing not in PR workflows
grep -L "fuzzing" .github/workflows/*.yml | grep -E "(pr|pull|check)"
```
### Manual Trigger
```bash
# Trigger weekly workflow manually (via GitHub UI)
# https://github.com/owner/repo/actions/workflows/bug-discovery-weekly.yml
# Click "Run workflow" button
```
## Corpus Management
### What is Corpus?
Corpus files contain **interesting inputs** discovered during fuzzing that exercise specific code paths or trigger edge cases.
**Example**:
```
tests/fuzzing/campaigns/corpus/auth/login_sql_injection.input
```
Content (binary):
```
admin' OR '1'='1'--
```
### Corpus Directory Structure
```
tests/fuzzing/campaigns/corpus/
├── auth/ # Authentication corpus
├── agent/ # Agent execution corpus
├── workflow/ # Workflow management corpus
├── canvas/ # Canvas presentation corpus
└── skill/ # Skill installation corpus
```
### Re-seeding Campaigns
Use corpus to speed up future campaigns:
```bash
# Set corpus directory
export FUZZ_CORPUS_DIR="tests/fuzzing/campaigns/corpus/auth"
# Run campaign with corpus
FUZZ_ITERATIONS=10000 pytest tests/fuzzing/test_auth_api_fuzzing.py -v -m fuzzing
```
**Benefits**:
- Faster coverage (corpus inputs explore interesting paths immediately)
- Reproducible crashes (corpus inputs are reproducible test cases)
- Regression testing (corpus grows with discovered bugs)
### Corpus Management Best Practices
1. **Add interesting inputs**: When crashes are discovered, add *.input files to corpus
2. **Organize by endpoint**: Use subdirectories (auth/, agent/, workflow/)
3. **Commit to git**: Corpus is version-controlled with tests
4. **Update regularly**: Add new inputs after each fuzzing campaign
## Crash Analysis
### Crash Artifacts
**Location**: `tests/fuzzing/campaigns/crashes/`
**Format**:
```
tests/fuzzing/campaigns/crashes/POST_api_auth_login_2026-03-24T12-34-56/
├── crash-00001.input # Binary input that triggered crash
├── crash-00001.log # Stack trace and error message
├── crash-00002.input
├── crash-00002.log
└── ...
```
### Crash Deduplication
**Tool**: `CrashDeduplicator` in `tests/fuzzing/campaigns/crash_deduplicator.py`
**Method**: SHA256 hash of normalized error signature
```python
# Normalize stack trace (remove line numbers)
normalized_trace = re.sub(r' line \d+', ' line N', stack_trace)
# Generate SHA256 hash
signature_hash = hashlib.sha256(normalized_trace.encode('utf-8')).hexdigest()
```
**Benefits**:
- Groups duplicate crashes (same root cause)
- Reduces bug filing noise
- Identifies high-frequency crashes
### Bug Filing
**Tool**: `BugFilingService` in `tests/bug_discovery/bug_filing_service.py`
**Trigger**: Automatic on crash discovery (via FuzzingOrchestrator)
**Bug Template**:
```markdown
## Fuzzing Crash: POST /api/auth/login
**Test Type**: Fuzzing
**Target Endpoint**: POST /api/auth/login
**Signature Hash**: `abc123...`
### Crash Metadata
- **Crash Input**: `deadbeef...` (hex)
- **Stack Trace**: ```
Traceback (most recent call last):
File "...", line 123, in test_login_endpoint_fuzzing
...
AssertionError: Expected status 200, got 500
```
### Reproduction
```bash
FUZZ_ITERATIONS=1 pytest tests/fuzzing/test_auth_api_fuzzing.py::test_login_endpoint_fuzzing -v
```
### Environment
- **Commit**: abc123...
- **Date**: 2026-03-24T12:34:56Z
- **Campaign ID**: POST_api_auth_login_2026-03-24T12-34-56
```
### Crash Reports
**Tool**: `aggregate_crash_reports.py`
**Usage**:
```bash
python3 tests/fuzzing/scripts/aggregate_crash_reports.py \
--crash-dirs "tests/fuzzing/campaigns/crashes/*" \
--output fuzzing-report.md
```
**Report Sections**:
1. **Summary**: Total crashes, unique crashes, affected endpoints
2. **Crashes by Endpoint**: Grouped by API endpoint
3. **Top Crash Signatures**: Sorted by frequency
4. **Bug Filing Status**: Filed, duplicate, skipped
5. **Trend Analysis**: Comparison with previous week
## Best Practices
### 1. Start Small, Scale Up
**Quick verification** (100 iterations, ~10 seconds):
```bash
FUZZ_ITERATIONS=100 pytest tests/fuzzing/test_auth_api_fuzzing.py -v -m fuzzing
```
**Full campaign** (10000 iterations, ~5-10 minutes):
```bash
FUZZ_ITERATIONS=10000 pytest tests/fuzzing/test_auth_api_fuzzing.py -v -m fuzzing
```
**Scale based on crash discovery**:
- If crashes found: Increase iterations to 100000+
- If no crashes: Decrease iterations to 1000 (speed up)
### 2. Use TestClient Pattern
**Correct** (fast, no network overhead):
```python
from fastapi.testclient import TestClient
app.dependency_overrides[get_db] = lambda: db_session
client = TestClient(app)
response = client.post("/api/auth/login", json={...})
assert response.status_code in [200, 400, 401, 422]
```
**Incorrect** (slow, network overhead):
```python
import httpx
response = httpx.post("http://localhost:8000/api/auth/login", json={...})
# Don't do this - network overhead is 10-100x slower
```
**Exception**: Use httpx for streaming endpoints (WebSocket, SSE)
### 3. Reuse Fixtures from e2e_ui
**Correct** (no duplication, 10-100x faster):
```python
from tests.e2e_ui.fixtures.database_fixtures import db_session
from tests.e2e_ui.fixtures.auth_fixtures import authenticated_user
def test_login_endpoint_fuzzing(db_session, authenticated_user):
# db_session: Isolated database session
# authenticated_user: Pre-created user with valid credentials
pass
```
**Incorrect** (duplication, slower):
```python
def test_login_endpoint_fuzzing():
# Don't create fixtures inline - duplication!
db_session = SessionLocal()
user = User(email="test@example.com", password="...")
db_session.add(user)
db_session.commit()
# ...
```
### 4. NEVER Fuzz Production URLs
**Correct** (TestClient with database override):
```python
app.dependency_overrides[get_db] = lambda: db_session
client = TestClient(app)
response = client.post("/api/auth/login", json={...})
```
**Incorrect** (production URL, dangerous):
```python
import httpx
response = httpx.post("https://api.atom.ai/api/auth/login", json={...})
# NEVER DO THIS - fuzzing production is dangerous!
```
### 5. Set Reasonable Timeouts
```python
@pytest.mark.timeout(300) # 5 minutes per test
def test_login_endpoint_fuzzing(db_session, authenticated_user):
pass
```
**Reasoning**:
- Prevents infinite loops (10000 iterations)
- Allows CI to detect hangs
- 5 minutes is sufficient for 10000 iterations
### 6. Test Status Codes, Not Just Crashes
```python
# Good: Test all expected status codes
assert response.status_code in [200, 400, 401, 409, 422]
# Bad: Only test for success (misses 500 errors)
assert response.status_code == 200
```
**Reasoning**:
- Crashes are not the only bugs
- 500 errors indicate unhandled exceptions
- 422 errors indicate validation issues
- 400/401/409 errors indicate business logic bugs
## Troubleshooting
### Fuzzing Hangs
**Symptom**: Test runs forever, never completes
**Solutions**:
1. Reduce iterations: `FUZZ_ITERATIONS=100`
2. Check timeout config: `@pytest.mark.timeout(300)`
3. Check for infinite loops in endpoint code
4. Use `pytest --timeout` to enforce global timeout
**Example**:
```bash
# Add 10-minute global timeout
pytest --timeout=600 tests/fuzzing/test_auth_api_fuzzing.py -v -m fuzzing
```
### No Crashes Found
**Symptom**: Fuzzing completes with 0 crashes
**Solutions**:
1. Increase iterations: `FUZZ_ITERATIONS=100000`
2. Expand input space (fuzz more fields)
3. Check endpoint code (overly defensive?)
4. Add security payloads (SQLi, XSS, null bytes)
5. Check status code assertions (too permissive?)
**Example**:
```python
# Bad: Too permissive (misses crashes)
assert response.status_code in range(200, 600)
# Good: Explicit status codes
assert response.status_code in [200, 400, 401, 409, 422, 500]
```
### Duplicate Bugs Filed
**Symptom**: Multiple GitHub issues for same crash
**Solutions**:
1. Check deduplication logic: `CrashDeduplicator.deduplicate_crashes()`
2. Verify signature hash is stable (line numbers removed?)
3. Check BugFilingService duplicate detection
4. Review signature normalization regex
**Example**:
```python
# Check normalization logic
normalized_trace = re.sub(r' line \d+', ' line N', stack_trace)
# Should remove line numbers for stable hashing
```
### CI Timeout
**Symptom**: CI workflow fails with timeout
**Solutions**:
1. Reduce campaign duration: `--duration 1800` (30 min)
2. Run fewer endpoints: `--campaign "POST /api/auth/login"`
3. Increase CI timeout: `timeout-minutes: 180`
4. Parallelize campaigns (run multiple jobs)
**Example**:
```yaml
# Reduce timeout
- name: Run fuzzing campaigns
run: |
python3 tests/fuzzing/scripts/run_fuzzing_campaigns.py --duration 1800
timeout-minutes: 45
```
### Fuzzing Skipped in CI
**Symptom**: Tests skip with "atheris not installed"
**Solutions**:
1. Verify atheris in requirements-testing.txt
2. Check CI workflow installs atheris: `pip install atheris`
3. Verify pytest markers: `@pytest.mark.fuzzing`
4. Check test collection: `pytest --collect-only tests/fuzzing/`
**Example**:
```yaml
# Verify CI installs atheris
- name: Install dependencies
run: |
pip install atheris # Fuzzing support
```
## Further Reading
### Research Documentation
- **RESEARCH.md**: `backend/tests/fuzzing/RESEARCH.md` - Fuzzing research and evaluation
- **Phase 239 Plans**: `.planning/phases/239-api-fuzzing-infrastructure/` - Implementation plans
### Related Documentation
- **Bug Discovery Infrastructure**: `docs/testing/BUG_DISCOVERY_INFRASTRUCTURE.md`
- **Property-Based Testing**: `backend/tests/property/README.md`
- **E2E Testing**: `backend/tests/e2e_ui/README.md`
### External Resources
- **Atheris**: https://github.com/google/atheris - Google's coverage-guided fuzzer for Python
- **Fuzzing Book**: https://www.fuzzingbook.org/ - "The Fuzzing Book" by Andreas Zeller
- **OSS-Fuzz**: https://github.com/google/oss-fuzz - Google's continuous fuzzing service
## Summary
API fuzzing with Atheris provides **coverage-guided crash discovery** for FastAPI endpoints, complementing unit tests, integration tests, and property-based tests. Key takeaways:
1. **Start small**: 100 iterations for quick verification, 10000 for full campaign
2. **Use TestClient**: Fast, no network overhead, database override for isolation
3. **Reuse fixtures**: db_session, authenticated_user from e2e_ui (10-100x faster)
4. **NEVER fuzz production**: Use TestClient, not httpx with production URLs
5. **CI/CD integration**: Weekly campaigns (1 hour), NOT in PR pipeline (FUZZ-07)
6. **Corpus management**: Re-seed campaigns with interesting inputs
7. **Crash analysis**: Deduplicate by signature, file bugs automatically
**Fuzzing is not a silver bullet**, but it's a powerful tool for discovering crashes in parsing, validation, and business logic code that human-written tests miss.