restler-fuzzing
Runs stateful REST API fuzzing using Microsoft's RESTler - infers producer-consumer dependencies from an OpenAPI spec, drives sequences of requests (POST → GET → DELETE chains), and reports 5xx errors, resource leaks, and hierarchy violations. Wraps the canonical 4-stage workflow (compile → test → fuzz-lean → fuzz). Use when the API is stateful (resources are created, queried, modified, deleted) and Schemathesis's stateless fuzzing is missing the multi-step bugs.
Install with skills.sh (any agent)
npx skills add testland/qa --skill restler-fuzzingrestler-fuzzing
Overview
RESTler is a stateful REST API fuzzer that finds security and reliability bugs (restler-readme (opens in new window)). Its differentiator vs. stateless fuzzers like schemathesis-fuzzing is that it infers producer-consumer dependencies from the OpenAPI spec - if POST /resources returns an id and GET /resources/{id} accepts that id, RESTler sequences them in that order to reach deeper state.
When to use
If the API is stateless (search endpoints, calculator endpoints, report-generation endpoints), Schemathesis is sufficient and lighter to operate. RESTler shines on resource lifecycle APIs.
Install
Prerequisites per restler-readme (opens in new window): Python 3.12.8 and .NET 8.0.
From source
git clone https://github.com/microsoft/restler-fuzzer.git
cd restler-fuzzer
mkdir restler_bin
python ./build-restler.py --dest_dir "$(pwd)/restler_bin"(Per restler-readme (opens in new window).)
Via Docker (preferred for CI)
docker build -t restler .For one-off CI runs:
docker run --rm -v "$PWD/output:/output" restler ...The four-stage workflow
Per restler-readme (opens in new window):
Stage 1 - Compile
Generate a RESTler grammar from the OpenAPI spec. The grammar captures the producer-consumer dependencies RESTler will exploit during fuzzing.
restler compile --api_spec openapi.jsonOutput: Compile/grammar.py plus Compile/dict.json (a starter dictionary RESTler uses to seed parameter values).
Stage 2 - Test (smoke)
Run a single end-to-end pass to verify the spec, auth, and target URL are wired correctly. Measures endpoint coverage - what fraction of the API RESTler can reach with the current grammar / dictionary.
restler test --grammar_file Compile/grammar.py \
--dictionary_file Compile/dict.json \
--target_ip <api-host> \
--target_port 443 \
--use_sslIf endpoint coverage is below the team's threshold (often 80%+), stop and amend the dictionary or grammar before continuing.
Stage 3 - Fuzz-lean
One pass through every endpoint with default checkers active - fast bug discovery focused on the obvious failure modes.
restler fuzz-lean --grammar_file Compile/grammar.py \
--dictionary_file Compile/dict.json \
--target_ip <api-host> \
--target_port 443 \
--use_sslStage 4 - Fuzz (deep)
Aggressive breadth-first exploration. Run for a fixed time budget (hours to days for a comprehensive run).
restler fuzz --grammar_file Compile/grammar.py \
--dictionary_file Compile/dict.json \
--target_ip <api-host> \
--target_port 443 \
--use_ssl \
--time_budget 8.0 # hoursBug detection
Per restler-readme (opens in new window), RESTler reports two bug categories:
| Category | Trigger |
|---|---|
| 5xx errors | Any 5xx response is a bug; RESTler triages by URL pattern. |
| Checker violations | Targeted sequences look for resource leaks, hierarchy violations (e.g. accessing a resource after deletion), and use-after-free patterns. |
Each bug appears in RestlerResults/.../bug_buckets/ with a replay log - the exact sequence of requests that triggered it. Replay logs are deterministic - the bug reproduces on demand.
Authentication
RESTler doesn't bake in an auth strategy; for tokens, the canonical pattern is:
TOKEN=$(curl -s ... | jq -r .access_token)
echo "{\"restler_custom_payload_header\": {\"Authorization\": [\"Bearer $TOKEN\"]}}" > auth-dict.json
restler fuzz-lean ... --dictionary_file auth-dict.jsonFor OAuth flows that rotate tokens during a long fuzz run, supply a refresh script RESTler invokes periodically. See restler-readme (opens in new window) for the --token_refresh_command flag and cadence settings.
Output and triage
Results land under RestlerResults/ with Compile/, Test/, FuzzLean/, and Fuzz/ subtrees; each unique bug gets a bug_buckets/Bug_N/ folder holding a bug_replay_log.txt and bug_request.txt. The full directory layout is in references/triage-and-ci.md.
Per-bug triage:
CI integration
RESTler is not a per-PR tool by default - fuzz-lean takes 5-30 minutes typically; deep fuzz is hours. The canonical cadence:
| Cadence | Stage | Time budget | Trigger |
|---|---|---|---|
| Per-PR | Test only | <1 minute | Schema-validation smoke; fail fast. |
| Nightly | Fuzz-lean | 30-60 minutes | New endpoint regression scan. |
| Weekly | Fuzz | 4-12 hours | Deep state-machine exploration. |
| Pre-release | Fuzz | 24-72 hours | Final security / reliability gate. |
The nightly cadence runs on a schedule: cron trigger: build the RESTler Docker image, compile the grammar from openapi.json, run fuzz-lean against staging, upload RestlerResults/ as an artifact (if: always()), then fail the job when any bug_replay_log.txt exists (find ... | wc -l). The complete GitHub Actions workflow is in references/triage-and-ci.md.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Running RESTler on production | Generated requests mutate live data; 5xx alerts spam oncall. | Always target staging; production is for runtime monitoring, not fuzz traffic. |
| Skipping Stage 2 (Test) | Spec-vs-impl drift is invisible until Stage 4 wastes hours. | Always run Test first; check coverage before fuzz-lean. |
| Stage 4 (Fuzz) on every PR | Hours-long PR CI; no team accepts that. | Fuzz is nightly / weekly only. |
| Triaging bugs without replay-log confirmation | Some bugs are environmental (test DB state); confirm reproducibility. | Always run restler replay on each bug before opening a ticket. |
| Letting bug counts grow unbounded | Backlog of unfixed bugs becomes noise; team learns to ignore RESTler reports. | Treat each bug as a P1 / P2 ticket; fix or document waiver per the team's escape-defect policy. |
Limitations
References
RESTler output tree and nightly CI workflow
View source (opens in new window)RESTler output tree and nightly CI workflow
Companion to the restler-fuzzing skill: the full results directory layout and the complete nightly GitHub Actions workflow. Sourced from restler-readme (opens in new window).
Output tree
RestlerResults/
Compile/
grammar.py
dict.json
Test/
coverage_failures_to_investigate.txt
bug_buckets/
FuzzLean/
bug_buckets/ # one folder per unique bug pattern
Bug_1/
bug_replay_log.txt
bug_request.txt
Fuzz/
bug_buckets/
progress/Nightly CI workflow (GitHub Actions)
# .github/workflows/restler-nightly.yml
name: restler-nightly
on:
schedule:
- cron: '0 2 * * *' # nightly at 02:00 UTC
workflow_dispatch:
jobs:
fuzz-lean:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v5
- name: Build RESTler image
run: docker build -t restler ./.restler/
- name: Compile grammar
run: |
docker run --rm -v "$PWD:/work" restler \
compile --api_spec /work/openapi.json
- name: Fuzz-lean
env:
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
run: |
docker run --rm -v "$PWD:/work" \
-e API_TOKEN="$API_TOKEN" \
restler fuzz-lean \
--grammar_file /work/Compile/grammar.py \
--dictionary_file /work/Compile/dict.json \
--target_ip staging.example.com \
--target_port 443 \
--use_ssl
- name: Upload bug buckets
if: always()
uses: actions/upload-artifact@v4
with:
name: restler-results
path: |
RestlerResults/
retention-days: 30
- name: Fail if bugs found
run: |
BUG_COUNT=$(find RestlerResults -name 'bug_replay_log.txt' | wc -l)
if [ "$BUG_COUNT" -gt 0 ]; then
echo "::error::RESTler found $BUG_COUNT bug(s) - see artifacts"
exit 1
fiRelated skills
api-chaos-runner
Runs the project's existing API tests under injected network chaos - latency, timeouts, dropped connections, bandwidth caps, packet loss - via Toxiproxy (notes on Pumba / Gremlin / LitmusChaos). Builds a per-scenario chaos matrix and reports which assertions break under which conditions, verifying resilience patterns (retry, circuit-breaker, timeout, fallback). Unlike schemathesis-fuzzing and restler-fuzzing, which generate new tests from a schema, this drives your EXISTING example-based suite.
api-testing-overview
Teaches API testing from zero: what functional API testing covers, how it differs from contract testing and load testing, and a decision table that picks one tool from observable project facts (language and build file, whether an OpenAPI or GraphQL schema exists, functional vs spec-conformance fuzzing vs stateful security fuzzing, whether non-engineers read the tests). Names the real options (Postman with newman, REST Assured, Karate, Tavern, Schemathesis, RESTler), gives install and first-run commands with what a passing run looks like, and the traps that bite first: asserting only on HTTP status, order-dependent tests sharing server state, and hardcoded environment URLs and secrets. Use when an HTTP API needs automated tests and no tool has been chosen, or when an inherited suite only checks status codes.
karate-testing
Authors Karate `.feature` files using its Gherkin-flavored DSL for HTTP API tests, leverages the `match` keyword with fuzzy validators (#number / #string / #regex / contains / arrays), runs the suite via JUnit 5 plus Maven Surefire, and produces JUnit XML for CI gating. Use when the project is on the JVM and prefers a feature-file authoring flow over Java-DSL fluent chains; for those fluent chains use restassured-testing, for the same YAML-style flow on a Python/pytest stack use tavern-testing.
postman-collections
Authors Postman collections (requests + tests + variables + environments), runs them headless via the Newman CLI, configures reporters (cli / json / junit / html) for CI artifact upload, and uses iteration data files (JSON / CSV) for data-driven runs. Use when the project ships HTTP API tests authored in Postman and the team needs CI execution alongside or instead of the Postman desktop runner.
restassured-testing
Authors REST Assured (Java) API tests using the given().when().then() BDD-style DSL - status code + JSON/XML path assertions + authentication (Basic, OAuth2, API key). Configures Maven / Gradle dependencies, runs via JUnit 5, and emits Surefire / JaCoCo reports for CI gating. Use when the project is on the JVM and wants type-safe API tests in the app's own language; for a Gherkin feature-file flow on the same JVM use karate-testing, for YAML tests on the pytest stack use tavern-testing.
schemathesis-fuzzing
Generates property-based API tests automatically from an OpenAPI 2/3.x or GraphQL schema using Schemathesis, runs them via the `schemathesis run` CLI or as a pytest decorator, configures the canonical checks (status_code_conformance, response_schema_conformance, content_type_conformance, response_headers_conformance, not_a_server_error), and gates CI on schema-conformance failures plus 5xx detection. Use when the project ships an OpenAPI or GraphQL schema and the team wants schema-driven coverage that scales as the API evolves.
tavern-testing
Authors Tavern API tests as YAML files (`test_*.tavern.yaml`) with `test_name` + `stages` + `request` + `response` blocks, runs them through the Tavern pytest plugin (auto-discovered), and gates CI on the resulting JUnit XML. Covers RESTful, MQTT, and gRPC variants. Use when the project runs on pytest and prefers YAML over a Python- or Java-DSL; on the JVM use karate-testing or restassured-testing instead, and for schema-driven property-based fuzzing on the same pytest stack use schemathesis-fuzzing.