> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗

Back to Articles

Fuzz-Lightyear: The Stateful API Fuzzer That Thinks Like an Attacker

[ View on GitHub ]

Fuzz-Lightyear: The Stateful API Fuzzer That Thinks Like an Attacker

Hook

Most API fuzzers celebrate when they break things. Fuzz-lightyear does the opposite—it flags vulnerabilities when unauthorized requests unexpectedly succeed. That subtle inversion makes all the difference for catching authorization bugs.

Context

Traditional API security testing has a blind spot. Tools like Burp Suite and OWASP ZAP excel at finding injection flaws and obvious crashes, but they struggle with authorization logic bugs—the kind where Alice can delete Bob's resources simply by knowing his user ID. These Insecure Direct Object Reference (IDOR) vulnerabilities and broken access control issues are notoriously difficult to detect because they require understanding context: what should fail but doesn't.

The challenge compounds in microservice architectures where authorization logic scatters across dozens of services. You might have a user service that creates accounts, an order service that manages purchases, and a notification service that sends emails. An attacker could create a user in service A, grab a resource ID from service B, then manipulate it through service C—a multi-step attack sequence that no single-request fuzzer would catch. Yelp built fuzz-lightyear to solve this exact problem in their sprawling microservice ecosystem, treating API security testing like a chaos engineering experiment where the goal is discovering what unauthorized actions the system permits.

Technical Insight

Fuzz-lightyear's architecture rests on three pillars: Swagger-driven test generation, Hypothesis-powered fuzzing, and a pytest plugin system. When you point it at an OpenAPI specification, it doesn't just validate that your API matches the spec—it actively tries to break your authorization model by generating malicious request sequences.

The framework starts by parsing your Swagger spec to build an internal model of your API's operations, parameters, and relationships. It then uses Hypothesis, Python's property-based testing library, to generate diverse input variations—not random garbage, but structured payloads that respect your API's schema while pushing boundaries. Here's a minimal example of how you'd configure it:

from fuzz_lightyear import register_factory
from fuzz_lightyear.datastore import get_user_session

# Define a fixture to create test users with different privilege levels
@register_factory('user')
def create_user_factory():
    """Creates users for testing authorization boundaries."""
    def factory(is_admin=False):
        user = MyUserService.create(
            username=f'testuser_{uuid.uuid4()}',
            is_admin=is_admin
        )
        # Store session for later request authentication
        get_user_session().add(user.id, user.auth_token)
        return user
    return factory

# The framework will automatically generate test sequences like:
# 1. Create admin user A and regular user B
# 2. User A creates a protected resource
# 3. User B attempts to access/modify that resource
# 4. Flag if the unauthorized operation succeeds

The stateful aspect is where fuzz-lightyear shines. Unlike tools that treat each request independently, it maintains a dependency graph between operations. If your API has a POST /users endpoint that returns a user ID, and a DELETE /users/{id} endpoint, fuzz-lightyear will automatically chain them: create a user with one set of credentials, then attempt to delete it with different credentials. It tracks which operations produce resources (like IDs, tokens, or session cookies) and intelligently reuses them in subsequent requests to explore attack paths.

The plugin system enables vulnerability-specific detection logic. The built-in IDORPlugin, for instance, doesn't just check HTTP status codes—it analyzes response patterns to identify privilege escalation:

class IDORPlugin:
    def test_idor(self, operation, low_privilege_user, high_privilege_resource):
        # Attempt to access high-privilege resource with low-privilege creds
        response = operation.call(
            resource_id=high_privilege_resource.id,
            auth=low_privilege_user.token
        )
        
        # The vulnerability: request succeeded when it should have failed
        if response.status_code == 200:
            # Generate reproducible cURL command for security team
            return VulnerabilityReport(
                severity='HIGH',
                curl_command=operation.to_curl(response),
                hypothesis_seed=current_seed()  # For reproduction
            )

Because it's built on pytest, you get familiar developer ergonomics: run with pytest --fuzz-lightyear, filter tests with -k flags, and integrate into CI/CD pipelines without custom runners. When a vulnerability is found, fuzz-lightyear outputs the exact cURL command sequence to reproduce the issue, complete with the Hypothesis seed value so you can replay the same random variations during debugging.

The fixture system addresses a practical problem in microservice testing: your API might not expose all CRUD operations. Perhaps users can only be created through an admin panel, or resources require complex setup across multiple services. Fuzz-lightyear lets you inject custom setup logic through pytest fixtures, bridging the gap between your test environment and production-like authorization boundaries. This is crucial because effective authorization testing requires realistic resource ownership scenarios—you can't test if Alice can access Bob's data unless you can actually create both Alice and Bob with proper identity separation.

Gotcha

The Swagger dependency is both a strength and a limitation. If your OpenAPI specs are incomplete, outdated, or absent entirely, fuzz-lightyear has nothing to work with. This is particularly painful for legacy microservices that evolved before API documentation became a priority, or for APIs using custom authentication schemes (like proprietary header-based tokens) that don't fit OpenAPI's security definitions. You'll need to maintain spec accuracy, which can become a documentation burden.

The fixture investment can be substantial in complex environments. For a simple CRUD API, setup is straightforward, but in real-world microservice architectures with intricate resource dependencies, you might spend days writing factories for users, organizations, subscriptions, permissions, and the relationships between them. Each service might require different authentication mechanisms, and you'll need to handle cleanup to avoid test pollution. The framework doesn't magically understand your business logic—you have to encode the "Alice creates resource, Bob tries to access it" scenarios through fixture orchestration. If your team doesn't have the discipline to maintain these fixtures as your API evolves, the test suite will bitrot quickly. Additionally, fuzz-lightyear is firmly REST/HTTP-focused; if you're moving toward GraphQL or gRPC, you're out of luck without significant custom work.

Verdict

Use if: You have a microservice architecture with maintained Swagger/OpenAPI specs and need to systematically find authorization bugs like IDOR vulnerabilities. The investment pays off when you have complex multi-service interactions where manual test case writing would be prohibitive, and your team can commit to maintaining test fixtures. It's particularly valuable in environments where different teams own different services but share security responsibility—the automated test generation ensures coverage across service boundaries without requiring each team to become security experts. Skip if: Your APIs lack OpenAPI documentation, use non-REST protocols, or if you need quick ad-hoc security testing without upfront fixture investment. For simpler authorization models or monolithic applications, manual security test cases or tools like Postman with scripted collections might give you faster results. Also skip if your API landscape changes too rapidly for fixture maintenance to keep pace—you'll end up with a brittle, ignored test suite.