annator-command-center / tests /fuzzing /test_jwt_validation_fuzzing.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 8)
aef804e verified
Raw
History Blame Contribute Delete
11.5 kB
"""
JWT validation fuzzing harnesses.
This module uses Atheris to discover crashes in JWT parsing and
validation code through coverage-guided fuzzing.
Fuzzing Targets:
- Authorization header parsing
- JWT expiry validation
- JWT signature validation
- Authorization header format validation
Usage:
FUZZ_ITERATIONS=10000 pytest tests/fuzzing/test_jwt_validation_fuzzing.py -v -m fuzzing
"""
import os
import sys
import json
import pytest
from typing import Dict, Any
# Add backend to path
backend_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if backend_dir not in sys.path:
sys.path.insert(0, backend_dir)
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
# Import fixtures and app
from tests.fuzzing.conftest import ATHERIS_AVAILABLE
from tests.e2e_ui.fixtures.database_fixtures import db_session
from tests.e2e_ui.fixtures.auth_fixtures import authenticated_user
from main_api_app import app
from core.database import get_db
# Try to import Atheris (graceful degradation)
try:
import atheris
from atheris import fp
except ImportError:
ATHERIS_AVAILABLE = False
pytest.skip("Atheris not installed - skipping JWT validation fuzzing", allow_module_level=True)
# ============================================================================
# FUZZING TEST 1: AUTHORIZATION HEADER VALIDATION
# ============================================================================
@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_jwt_validation_fuzzing(db_session: Session):
"""
Fuzz Authorization header parsing in protected endpoints.
PROPERTY: JWT validation should not crash on malformed tokens.
STRATEGY: FuzzedDataProvider generates random token strings.
INVARIANT: Response status code in [200, 401, 403, 422] (no 500 errors).
RADII: 10000 iterations sufficient for token input space.
Fuzzed fields:
- Authorization header: Random string up to 500 chars
- Token format: Bearer {token} with fuzzed token
Tests GET /api/agents (protected endpoint) with fuzzed JWT tokens.
"""
# Override database dependency
app.dependency_overrides[get_db] = lambda: db_session
client = TestClient(app)
def fuzz_one_input(data: bytes):
"""Fuzzing target for Authorization header parsing."""
try:
fdp = fp.FuzzedDataProvider(data)
# Consume random token string
token = fdp.ConsumeRandomLengthString(500)
# Create Authorization header with fuzzed token
headers = {"Authorization": f"Bearer {token}"}
# Call protected endpoint with fuzzed token
response = client.get("/api/agents", headers=headers)
# Assert no crashes (401/403 expected for invalid tokens)
assert response.status_code in [200, 401, 403, 422], \
f"Unexpected status {response.status_code}: {response.text[:200]}"
except (ValueError, json.decoder.JSONDecodeError):
pass
except Exception as e:
raise Exception(f"JWT validation crashed: {e}")
atheris.Setup(
sys.argv + [atheris.FuzzedDataProviderFlag],
fuzz_one_input
)
iterations = int(os.getenv("FUZZ_ITERATIONS", "10000"))
atheris.Fuzz(iterations=iterations)
app.dependency_overrides = {}
# ============================================================================
# FUZZING TEST 2: JWT EXPIRY VALIDATION
# ============================================================================
@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_jwt_expiry_fuzzing(db_session: Session, authenticated_user):
"""
Fuzz JWT expiry timestamp handling.
PROPERTY: Expired token validation should not crash.
STRATEGY: Direct function fuzzing of JWT verification with fuzzed expiry.
INVARIANT: Function returns False or raises ValidationError (no crashes).
RADII: 10000 iterations sufficient for timestamp space.
Fuzzed fields:
- Expiry timestamp: Negative values, huge numbers, strings
- Token payload: Malformed expiry claims
Uses core.auth.verify_token for direct function fuzzing.
"""
from core.auth import verify_token
from jose import jwt
from datetime import datetime, timedelta
# Override database dependency
app.dependency_overrides[get_db] = lambda: db_session
client = TestClient(app)
# Get user for valid token generation
user, _ = authenticated_user
def fuzz_one_input(data: bytes):
"""Fuzzing target for JWT expiry validation."""
try:
fdp = fp.FuzzedDataProvider(data)
# Generate JWT with fuzzed expiry
# Use fuzzed data to create potentially invalid expiry
fuzzed_expiry = fdp.ConsumeIntToIntInRange(-1000000, 1000000)
# Create token with fuzzed expiry
payload = {
"sub": str(user.id),
"exp": fuzzed_expiry,
"iat": int(datetime.utcnow().timestamp())
}
# Sign with test secret (may be invalid)
try:
token = jwt.encode(payload, "test_secret", algorithm="HS256")
# Try to verify token
headers = {"Authorization": f"Bearer {token}"}
response = client.get("/api/agents", headers=headers)
# Assert no crashes
assert response.status_code in [200, 401, 403, 422], \
f"Unexpected status {response.status_code}: {response.text[:200]}"
except Exception as jwt_error:
# JWT encoding errors are OK (fuzzed input)
pass
except (ValueError, json.decoder.JSONDecodeError):
pass
except Exception as e:
raise Exception(f"JWT expiry validation crashed: {e}")
atheris.Setup(
sys.argv + [atheris.FuzzedDataProviderFlag],
fuzz_one_input
)
iterations = int(os.getenv("FUZZ_ITERATIONS", "10000"))
atheris.Fuzz(iterations=iterations)
app.dependency_overrides = {}
# ============================================================================
# FUZZING TEST 3: JWT SIGNATURE VALIDATION
# ============================================================================
@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_jwt_signature_fuzzing(db_session: Session, authenticated_user):
"""
Fuzz JWT signature validation.
PROPERTY: Signature validation should not crash on invalid signatures.
STRATEGY: FuzzedDataProvider generates random signature strings.
INVARIANT: Response status code in [200, 401, 403, 422] (no 500 errors).
RADII: 10000 iterations sufficient for signature space.
Fuzzed fields:
- Signature: Invalid length, invalid chars, None, empty strings
- Token segments: Malformed JWT structure (header.payload.signature)
Tests protected endpoint with tokens containing fuzzed signatures.
"""
# Override database dependency
app.dependency_overrides[get_db] = lambda: db_session
client = TestClient(app)
def fuzz_one_input(data: bytes):
"""Fuzzing target for JWT signature validation."""
try:
fdp = fp.FuzzedDataProvider(data)
# Generate fuzzed JWT segments
header = fdp.ConsumeRandomLengthString(100)
payload = fdp.ConsumeRandomLengthString(100)
signature = fdp.ConsumeRandomLengthString(100)
# Create malformed JWT token
fuzzed_token = f"{header}.{payload}.{signature}"
# Call protected endpoint with fuzzed token
headers = {"Authorization": f"Bearer {fuzzed_token}"}
response = client.get("/api/agents", headers=headers)
# Assert no crashes
assert response.status_code in [200, 401, 403, 422], \
f"Unexpected status {response.status_code}: {response.text[:200]}"
except (ValueError, json.decoder.JSONDecodeError):
pass
except Exception as e:
raise Exception(f"JWT signature validation crashed: {e}")
atheris.Setup(
sys.argv + [atheris.FuzzedDataProviderFlag],
fuzz_one_input
)
iterations = int(os.getenv("FUZZ_ITERATIONS", "10000"))
atheris.Fuzz(iterations=iterations)
app.dependency_overrides = {}
# ============================================================================
# FUZZING TEST 4: AUTHORIZATION HEADER FORMAT
# ============================================================================
@pytest.mark.fuzzing
@pytest.mark.slow
@pytest.mark.timeout(300)
def test_jwt_header_fuzzing(db_session: Session, authenticated_user):
"""
Fuzz Authorization header format variations.
PROPERTY: Authorization header parser should not crash on malformed headers.
STRATEGY: FuzzedDataProvider generates random header formats.
INVARIANT: Response status code in [200, 401, 403, 422] (no 500 errors).
RADII: 10000 iterations sufficient for header format space.
Fuzzed fields:
- Header prefix: "Bearer", "bearer", lowercase, no prefix
- Spacing: No space, multiple spaces, random whitespace
- Token: Random string, None, empty
Tests various malformed Authorization header formats:
- Missing "Bearer" prefix
- Lowercase "bearer"
- No space after "Bearer"
- Multiple spaces
- Empty token
- None token
"""
import random
# Override database dependency
app.dependency_overrides[get_db] = lambda: db_session
client = TestClient(app)
def fuzz_one_input(data: bytes):
"""Fuzzing target for Authorization header format."""
try:
fdp = fp.FuzzedDataProvider(data)
# Generate fuzzed token
token = fdp.ConsumeRandomLengthString(500)
# Generate fuzzed header format
header_formats = [
f"Bearer {token}", # Standard format
f"bearer {token}", # Lowercase
f"BEARER {token}", # Uppercase
f"{token}", # Missing prefix
f"Bearer{token}", # No space
f"Bearer {token}", # Multiple spaces
f"Bearer {token} ", # Trailing spaces
f"", # Empty header
f"Bearer ", # Empty token
f" {token}", # Leading space
]
# Select random header format
if len(data) > 0:
header_value = header_formats[ord(data[0]) % len(header_formats)]
else:
header_value = f"Bearer {token}"
# Call protected endpoint with fuzzed header
headers = {"Authorization": header_value}
response = client.get("/api/agents", headers=headers)
# Assert no crashes
assert response.status_code in [200, 401, 403, 422], \
f"Unexpected status {response.status_code}: {response.text[:200]}"
except (ValueError, json.decoder.JSONDecodeError):
pass
except Exception as e:
raise Exception(f"Authorization header parsing crashed: {e}")
atheris.Setup(
sys.argv + [atheris.FuzzedDataProviderFlag],
fuzz_one_input
)
iterations = int(os.getenv("FUZZ_ITERATIONS", "10000"))
atheris.Fuzz(iterations=iterations)
app.dependency_overrides = {}