Agentic-AFL is a neuro-symbolic fuzzing orchestration framework designed around one principle: the fuzzer must never be blocked. The agent runs as anDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/AdithyaaSivamal/Agentic-AFL/llms.txt
Use this file to discover all available pages before exploring further.
asyncio daemon in a separate process, communicates with AFL++ exclusively through the filesystem, and degrades gracefully under failure rather than crashing. Every inter-component boundary uses typed Python dataclasses defined in models.py, giving the pipeline a clear, auditable data contract from binary ingestion to payload injection.
Component Map
The system is divided into three cooperating subsystems. Each subsystem is isolated from the others by filesystem IPC — no shared memory, no blocking sockets.Fuzzer Bridge
Monitors AFL++ and writes payloads back to its sync directory.
- StallDetector — parses
fuzzer_statsandplot_datafor edge plateaus; performs GDB-based frontier discovery - PayloadInjector — atomically writes solved payloads to the AFL++ sync directory
- DiversityGenerator — after a successful solve, injects valid ICS protocol frame variants for all frame types to maximize post-bypass coverage
Extractor Pipeline
Converts a binary and stall address into a formal constraint description.
- PCodeSlicer — shells out to Ghidra headless to extract a taint-bounded backward P-Code slice
- ConstraintProfiler — deterministic heuristic engine that produces structural
ConstraintTagsets (no LLM calls) - SpecExporter — packages outputs into a
VulnerabilitySpecand upserts it to PostgreSQL
Orchestrator
Coordinates the Extract → CARM → Generate → Solve → Inject loop.
- AgentLoop — the central asyncio daemon; manages the priority queue and ReAct loop
- CARMRetriever — two-stage PostgreSQL Jaccard retrieval of historical Z3 templates
- LLMClient — K-way parallel Z3 script generation with multi-provider support
- Z3Sandbox — subprocess-isolated Z3 script execution with resource limits
Data Flow
Every artifact that crosses a component boundary is a typed dataclass defined inmodels.py. The pipeline flows as follows:
Binary + StallAddr
AFL++ stalls on a coverage plateau.
StallDetector emits a StallReport containing the binary path, stall address, severity, and closest seed input.PCodeSlice
PCodeSlicer invokes Ghidra headless analysis and extracts a taint-bounded backward P-Code slice at the stall address. The result is a PCodeSlice dataclass containing ordered PCodeInstruction objects, architecture metadata, and Ghidra’s decompiled C pseudocode.ConstraintProfile
ConstraintProfiler analyzes the PCodeSlice with deterministic heuristics and produces a ConstraintProfile — a frozenset[ConstraintTag] plus numerical density metrics. No LLM call is made at this stage.VulnerabilitySpec
SpecExporter bundles the PCodeSlice and ConstraintProfile into a VulnerabilitySpec, generates a deterministic spec_id (SHA-256 of binary_path + stall_address), and upserts the record to PostgreSQL.StallReport → Z3GenerationRequest
AgentLoop queries CARMRetriever for historical Z3 templates with a similar ConstraintProfile, then constructs a Z3GenerationRequest bundling the spec, seed input, retrieved templates, and correction history.Z3Script (×K)
LLMClient sends the request to the configured LLM provider and receives K candidate Z3Script objects in parallel via asyncio.gather.Z3Result (×K)
Z3Sandbox executes each script in an isolated subprocess and returns a Z3Result per script, carrying a Z3Verdict (SAT / UNSAT / TIMEOUT / SYNTAX_ERROR / RUNTIME_ERROR / UNKNOWN) and, on SAT, a concrete variable assignment model.Async Architecture
Three design principles govern how the agent interacts with AFL++: 1. Never block AFL++ The agent loop runs as anasyncio daemon process entirely separate from AFL++. All IPC is filesystem-based — the agent reads fuzzer_stats and writes to sync_dir/. AFL++ never waits on the agent; if the agent is computing, AFL++ keeps fuzzing. This ensures the LLM’s response latency (seconds) never impacts AFL++‘s throughput (10,000+ executions per second).
2. Priority queue
Detected stalls are enqueued in an asyncio.PriorityQueue keyed by StallSeverity. CRITICAL stalls (zero new edges for N cycles) are dequeued and processed before HIGH, MEDIUM, and LOW stalls. This ensures the most impactful coverage blockages are attacked first regardless of detection order.
3. Graceful degradation
If all ReAct turns are exhausted without producing a SAT result, the stall is deferred back to AFL++ for probabilistic mutation. The failure is recorded in the VulnerabilitySpec’s correction_history for future learning. The agent never raises an unhandled exception that would terminate the fuzzing campaign.
PostgreSQL Role
Agentic-AFL uses PostgreSQL as its persistence and retrieval backend rather than a vector database. This is a deliberate architectural choice rooted in the nature of CARM retrieval:- Jaccard similarity on formal tag sets, not cosine similarity on embeddings — the
ConstraintProfileis afrozenset[ConstraintTag]— a discrete set of structural labels. Jaccard similarity (|A ∩ B| / |A ∪ B|) is the correct metric for comparing set membership, not cosine similarity over dense embedding vectors. - Server-side computation — PostgreSQL with the
intarrayextension computesjaccard_similarity()entirely in the database engine using GIN-indexedINTEGER[]columns. Python receives only the top-N pre-sorted rows —O(log N)index lookup, not anO(N)Python loop over every stored spec. - Upsert semantics — re-running the extractor on the same
(binary_path, stall_address)pair updates the existing record rather than creating a duplicate, keeping the corpus clean across long fuzzing campaigns.
All filesystem operations that cross the AFL++ boundary — payload writes and temp-file creation — use an atomic write pattern: the data is written to a temporary file in the same directory, then
os.rename() is called to move it to the final path. This guarantees AFL++ never reads a partially written payload.