Documentation
¶
Overview ¶
Package specriot turns an OpenAPI document into deterministic, executable HTTP requests and reports anomalies.
It is designed to be used both as a library and from the CLI:
import "github.com/MouXiaoJun/specriot"
func TestAPI(t *testing.T) {
s, err := specriot.Load("openapi.yaml")
if err != nil {
t.Fatal(err)
}
report, err := s.Run(context.Background(), "http://localhost:8080",
specriot.WithSeed(42),
specriot.WithIterations(10),
)
if err != nil {
t.Fatal(err)
}
if len(report.Failures) > 0 {
t.Errorf("found %d API failures", len(report.Failures))
}
}
Generation is deterministic: the same spec, options and seed always produce the same inputs in the same order.
Example ¶
This example shows the intended library usage: load a spec, run generated requests against a target, and inspect the report.
package main
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"github.com/MouXiaoJun/specriot"
)
func main() {
// A target that returns schema-compliant responses for the petstore spec.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case r.Method == http.MethodPost && r.URL.Path == "/users":
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"id":1,"email":"u@e.com","role":"admin","age":30}`))
case r.Method == http.MethodGet && r.URL.Path == "/users":
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`[]`))
case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/users/"):
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"id":1,"email":"u@e.com","role":"admin","age":30}`))
case r.Method == http.MethodPost && r.URL.Path == "/orders":
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"id":"11111111-2222-3333-4444-555555555555","user_id":1,"status":"pending","items":["a"],"created_at":"2026-01-01T00:00:00Z"}`))
case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/orders/"):
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"id":"11111111-2222-3333-4444-555555555555","user_id":1,"status":"pending","items":["a"],"created_at":"2026-01-01T00:00:00Z"}`))
case r.Method == http.MethodDelete && strings.HasPrefix(r.URL.Path, "/orders/"):
w.WriteHeader(http.StatusNoContent)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
path := filepath.Join("testdata", "petstore.yaml")
s, err := specriot.Load(path)
if err != nil {
log.Fatal(err)
}
report, err := s.Run(context.Background(), srv.URL,
specriot.WithSeed(42),
specriot.WithIterations(3),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("operations: %d\n", report.Operations)
fmt.Printf("executed: %d\n", report.Executed)
fmt.Printf("failures: %d\n", len(report.Failures))
}
Output: operations: 6 executed: 18 failures: 0
Index ¶
- Constants
- type Config
- type Dependency
- type Failure
- type Finding
- type Graph
- type OperationInfo
- type OperationStat
- type Option
- func WithFuzzRatio(ratio float64) Option
- func WithHeader(key, value string) Option
- func WithIterations(n int) Option
- func WithMaxSequenceLength(n int) Option
- func WithSeed(seed int64) Option
- func WithSequences(n int) Option
- func WithStaleRate(rate float64) Option
- func WithTimeout(d time.Duration) Option
- type Reason
- type Report
- type SequenceFailure
- type Signal
- type Spec
- type StatefulReport
- type StepInfo
- type ValueRef
Examples ¶
Constants ¶
const DefaultSeed int64 = 0
DefaultSeed is used when no seed is supplied.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Seed drives the deterministic PRNG.
Seed int64
// Iterations is the number of generated executions per operation.
Iterations int
// Timeout applies to every request. Zero means no per-request timeout.
Timeout time.Duration
// Headers are attached to every request.
Headers map[string]string
// FuzzRatio controls how often boundary/invalid values are generated
// instead of valid ones. 0 (default) = all valid; 0.3 is a good
// fuzzing default; 1 = always interesting.
FuzzRatio float64
// Sequences is the number of stateful sequences to generate and execute.
// Used by RunStateful. Default 10.
Sequences int
// MaxSequenceLength is the maximum number of steps in a stateful sequence.
// Used by RunStateful. Default 12.
MaxSequenceLength int
// StaleRate is the probability (0-1) of a consumer step using deleted/stale
// values. Used by RunStateful. Default 0.1.
StaleRate float64
}
Config carries the knobs for a run.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns the default run configuration.
type Dependency ¶ added in v0.3.0
type Dependency = graph.Dependency
Dependency is a confidence-scored producer→consumer edge.
type Failure ¶
type Failure struct {
Key string
Method string
Path string
URL string
StatusCode int
Body string
Err string
// Params holds the generated values used for this request.
Params map[string]any
// Findings holds the oracle findings that classify this failure.
Findings []Finding
}
Failure describes an executed request that exposed a problem.
type Finding ¶
type Finding struct {
// Class is a machine-readable category: server_error, request_error,
// undocumented_status, invalid_content_type, malformed_response,
// invalid_response_body.
Class string
// Message is a human-readable explanation.
Message string
}
Finding is a single oracle finding attached to a failure.
type OperationInfo ¶
type OperationInfo struct {
Key string
Method string
Path string
OperationID string
Summary string
Tags []string
}
OperationInfo is a lightweight, public description of a discovered operation.
type OperationStat ¶
type OperationStat struct {
Key string
Method string
Path string
Executed int
Failures int
Statuses map[int]int
}
OperationStat aggregates execution results per operation.
type Option ¶
type Option func(*Config)
Option configures a run.
func WithFuzzRatio ¶
WithFuzzRatio controls how often boundary/invalid ("interesting") values are generated instead of valid ones. ratio must be in [0, 1]. 0 (the default) produces only valid values; 0.3 is a good fuzzing default; 1 always generates interesting values.
func WithHeader ¶
WithHeader attaches a static header to every request.
func WithIterations ¶
WithIterations sets how many generated executions run per operation.
func WithMaxSequenceLength ¶ added in v0.4.0
WithMaxSequenceLength sets the maximum number of steps in a stateful sequence. Used by RunStateful. Default 12.
func WithSequences ¶ added in v0.4.0
WithSequences sets the number of stateful sequences to generate and execute. Used by RunStateful. Default 10.
func WithStaleRate ¶ added in v0.4.0
WithStaleRate sets the probability (0-1) of a consumer step using deleted/stale values. Used by RunStateful. Default 0.1.
func WithTimeout ¶
WithTimeout sets the per-request timeout.
type Report ¶
type Report struct {
Title string
Version string
Seed int64
Iterations int
Operations int
Executed int
Failures []Failure
Stats []OperationStat
}
Report is the result of a Run.
type SequenceFailure ¶ added in v0.4.0
type SequenceFailure struct {
StepIndex int
OperationKey string
Method string
Path string
StatusCode int
Err string
Findings []Finding
Params map[string]any
// Sequence is the full sequence of steps that led to this failure.
Sequence []StepInfo
}
SequenceFailure is a failure discovered during stateful fuzzing, with full sequence context for reproduction.
type Spec ¶
type Spec struct {
// contains filtered or unexported fields
}
Spec is a loaded, normalized OpenAPI document.
func (*Spec) Graph ¶ added in v0.3.0
Graph infers the producer-consumer dependency graph between operations. It analyzes response schemas (producers) and request parameters/bodies (consumers), then matches them using name, type and path signals to produce confidence-scored edges.
The returned Graph supports Text(), Mermaid() and JSON() serialization, as well as SortedDependencies() for programmatic access.
func (*Spec) Operations ¶
func (s *Spec) Operations() []OperationInfo
Operations returns the discovered operations in deterministic order.
Example ¶
This example shows discovering the operations of a spec without executing anything.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/MouXiaoJun/specriot"
)
func main() {
dir, err := os.MkdirTemp("", "specriot")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
specFile := filepath.Join(dir, "mini.yaml")
_ = os.WriteFile(specFile, []byte(`openapi: 3.0.3
info:
title: Mini
version: 0.0.1
paths:
/ping:
get:
operationId: ping
responses:
'200':
description: ok
`), 0o644)
s, err := specriot.Load(specFile)
if err != nil {
log.Fatal(err)
}
ops := s.Operations()
for _, op := range ops {
fmt.Printf("%s %s (%s)\n", op.Method, op.Path, op.Key)
}
}
Output: GET /ping (ping)
func (*Spec) Run ¶
Run executes deterministic generated requests against target.
target is the base URL (for example "http://localhost:8080"). Requests are executed sequentially in a deterministic order: operations sorted by path and method, then iterations.
func (*Spec) RunStateful ¶ added in v0.4.0
func (s *Spec) RunStateful(ctx context.Context, target string, opts ...Option) (*StatefulReport, error)
RunStateful executes dependency-aware stateful request sequences against target. It builds a dependency graph, generates sequences using a dependency-guided weighted random walk, and executes each sequence with variable binding (response values from producer operations are fed to consumer operations). Mutations include stale-value reuse and step repetition (e.g., double DELETE).
This can discover bugs that require multiple related requests and cannot be reproduced by isolated endpoint fuzzing.
type StatefulReport ¶ added in v0.4.0
type StatefulReport struct {
Title string
Version string
Seed int64
Sequences int
Operations int
Executed int
Failures []SequenceFailure
Graph *Graph
}
StatefulReport holds the results of a stateful fuzzing run.
func (*StatefulReport) Failed ¶ added in v0.4.0
func (r *StatefulReport) Failed() bool
Failed reports whether any sequence failures were found.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
specriot
command
Command specriot executes deterministic, generated HTTP requests from an OpenAPI document and reports anomalies.
|
Command specriot executes deterministic, generated HTTP requests from an OpenAPI document and reports anomalies. |
|
internal
|
|
|
execute
Package execute turns a normalized operation and a value generator into a real HTTP request and records the outcome.
|
Package execute turns a normalized operation and a value generator into a real HTTP request and records the outcome. |
|
generate
Package generate produces deterministic, constraint-aware values for a normalized schema.
|
Package generate produces deterministic, constraint-aware values for a normalized schema. |
|
graph
Package graph infers producer-consumer dependencies between OpenAPI operations.
|
Package graph infers producer-consumer dependencies between OpenAPI operations. |
|
openapi
Package openapi loads and normalizes an OpenAPI 3 document into a model that the SpecRiot engine can execute.
|
Package openapi loads and normalizes an OpenAPI 3 document into a model that the SpecRiot engine can execute. |
|
oracle
Package oracle validates HTTP responses against an OpenAPI operation and reports contract violations.
|
Package oracle validates HTTP responses against an OpenAPI operation and reports contract violations. |
|
schema
Package schema defines the normalized constraint model that SpecRiot's generator understands.
|
Package schema defines the normalized constraint model that SpecRiot's generator understands. |
|
sequence
Package sequence builds and executes dependency-aware request sequences for stateful fuzzing.
|
Package sequence builds and executes dependency-aware request sequences for stateful fuzzing. |
|
state
Package state manages the state store for stateful fuzzing.
|
Package state manages the state store for stateful fuzzing. |