harnessx is a concurrent, dependency-aware check orchestration engine for Go. It lets you define a graph of checks with explicit dependencies, run them in parallel waves, and collect structured observations — without writing any scheduling or concurrency boilerplate. Common use cases include security scanning, compliance checks, health checks, and quality gates.
| Feature | Description |
|---|---|
| DAG scheduling | Checks are topologically sorted and executed in parallel waves |
| Two execution scopes | ScopeGlobal runs once per target; ScopePerResource fans out over discovered resources |
| Resource discovery | Global checks can emit Resource objects consumed by downstream per-resource checks |
| Conditional execution | Skip checks based on prior results using composable Condition predicates |
| Skip decisions | Skip a whole check or individual resources at runtime via SkipAlways / SkipWhen / SkipResourceWhen |
| Bounded concurrency | Separate semaphores for level-wide and per-resource parallelism |
| Panic recovery | A panicking check is recorded as failed; the scan continues uninterrupted |
| Context cancellation | Full context.Context propagation with per-check timeouts |
| Reporter hooks | Real-time OnCheckStart / OnCheckComplete / OnScanComplete callbacks |
| Structured observations | Checks emit typed observations with title, description, evidence, and free-form metadata |
| Scenarios | Run named check subsets (REST scan, GraphQL scan) without executing all registered checks |
| Zero dependencies | Pure Go standard library — no external runtime dependencies |
go get github.com/cerberauth/harnessxRequires Go 1.22+.
package main
import (
"context"
"fmt"
"net/http"
"github.com/cerberauth/harnessx"
)
func main() {
// 1. Describe the target.
target := harnessx.Target{URL: "https://example.com", Host: "example.com"}
// 2. Define checks.
tlsCheck := harnessx.Check{
ID: "tls",
Name: "TLS configuration",
Scope: harnessx.ScopeGlobal,
Run: func(ctx context.Context, t harnessx.Target, _ harnessx.ResultStore) (harnessx.Result, error) {
resp, err := http.Get(t.URL)
if err != nil || !resp.TLS.HandshakeComplete {
return harnessx.Result{
Observations: []harnessx.Observation{{
Title: "TLS not negotiated",
}},
}, nil
}
return harnessx.Result{}, nil
},
}
headerCheck := harnessx.Check{
ID: "headers",
Name: "Security headers",
Scope: harnessx.ScopeGlobal,
DependsOn: []harnessx.CheckID{"tls"},
// Only runs if TLS passed cleanly.
Conditions: []harnessx.Condition{harnessx.IfCheckPassed("tls")},
Run: func(ctx context.Context, t harnessx.Target, _ harnessx.ResultStore) (harnessx.Result, error) {
resp, err := http.Get(t.URL)
if err != nil {
return harnessx.Result{}, err
}
var observations []harnessx.Observation
if resp.Header.Get("Strict-Transport-Security") == "" {
observations = append(observations, harnessx.Observation{
Title: "Missing HSTS header",
})
}
return harnessx.Result{Observations: observations}, nil
},
}
// 3. Create the engine and register checks.
engine := harnessx.New(
harnessx.WithMaxConcurrency(4),
harnessx.WithChecks(tlsCheck, headerCheck),
)
// 4. Run the scan.
summary, err := engine.Run(context.Background(), target)
if err != nil {
panic(err)
}
fmt.Printf("Executed: %d Skipped: %d Failed: %d\n",
summary.Executed, summary.Skipped, summary.Failed)
for _, o := range summary.Observations {
fmt.Printf("%s: %s\n", o.Title, o.Description)
}
}A Check is the fundamental unit of work. Each check has a unique ID, an execution Scope, and either a Run or RunResource function.
type Check struct {
ID CheckID
Name string
Description string
Tags []string
// Dependency graph
DependsOn []CheckID
Conditions []Condition // AND-evaluated; any false → skip
// Skip control
Skip SkipDecision // static/dynamic skip, optionally per-resource
// Execution
Scope CheckScope // ScopeGlobal or ScopePerResource
Run CheckFunc // used when Scope == ScopeGlobal
RunResource ResourceCheckFunc // used when Scope == ScopePerResource
Timeout time.Duration // 0 → engine default (30s)
Concurrency int // per-resource parallelism; 0 → engine default
}| Scope | Runs | Function |
|---|---|---|
ScopeGlobal |
Once per scan | Run(ctx, target, store) (Result, error) |
ScopePerResource |
Once per resource discovered so far | RunResource(ctx, target, resource, store) (Result, error) |
A ScopeGlobal check can return a Resources slice in its result. Those resources are accumulated in the engine's store and made available to all subsequent ScopePerResource checks.
crawl := harnessx.Check{
ID: "crawl",
Scope: harnessx.ScopeGlobal,
Run: func(ctx context.Context, t harnessx.Target, _ harnessx.ResultStore) (harnessx.Result, error) {
endpoints := discover(ctx, t.URL) // returns []Resource
return harnessx.Result{Resources: endpoints}, nil
},
}
probe := harnessx.Check{
ID: "probe",
Scope: harnessx.ScopePerResource,
DependsOn: []harnessx.CheckID{"crawl"},
RunResource: func(ctx context.Context, t harnessx.Target, r harnessx.Resource, _ harnessx.ResultStore) (harnessx.Result, error) {
// called once for each Resource returned by "crawl"
return test(ctx, r), nil
},
}Conditions gate whether a check runs. All conditions in a check's Conditions slice must pass (AND semantics). Built-in predicates:
| Predicate | Description |
|---|---|
IfCheckPassed(id) |
Prior check completed with no observations and no error |
IfCheckObserved(id) |
Prior check produced at least one observation |
IfCheckSkipped(id) |
Prior check was skipped |
All(c1, c2, ...) |
All conditions must hold |
Any(c1, c2, ...) |
At least one condition must hold |
Not(c) |
Negates a condition |
// Run "deep-probe" only if "detect" produced any observations.
deepProbe := harnessx.Check{
ID: "deep-probe",
Scope: harnessx.ScopeGlobal,
DependsOn: []harnessx.CheckID{"detect"},
Conditions: []harnessx.Condition{
harnessx.IfCheckObserved("detect"),
},
Run: ...,
}Skip gates whether a check (or, for ScopePerResource checks, an individual resource) runs at all — evaluated before Conditions and before the check function. A non-empty reason skips and records Result.SkipReason; reporters still receive OnCheckComplete for it.
type SkipDecision struct { /* built via SkipAlways / SkipWhen / SkipResourceWhen */ }
func SkipAlways(reason string) SkipDecision
func SkipWhen(fn func(ctx context.Context, target Target, store ResultStore) string) SkipDecision
func SkipResourceWhen(fn func(ctx context.Context, target Target, resource Resource, store ResultStore) string) SkipDecisionSkipAlways/SkipWhenare check-wide: for aScopePerResourcecheck, a non-empty reason skips the entire check once, before it fans out over resources.SkipResourceWhenis evaluated once per resource, so different resources on the same check can be skipped for different reasons (or not at all).- If a check's
Skiphas no per-resource decision, per-resource evaluation falls back to the check-wide one.
// Skip the whole check if the target isn't HTTPS.
tlsOnly := harnessx.Check{
ID: "hsts-header",
Skip: harnessx.SkipWhen(func(ctx context.Context, t harnessx.Target, _ harnessx.ResultStore) string {
if !strings.HasPrefix(t.URL, "https://") {
return "target is not HTTPS"
}
return ""
}),
Run: ...,
}
// Skip only resources that opted out via metadata.
endpointAuth := harnessx.Check{
ID: "endpoint-auth",
Scope: harnessx.ScopePerResource,
Skip: harnessx.SkipResourceWhen(func(ctx context.Context, t harnessx.Target, r harnessx.Resource, _ harnessx.ResultStore) string {
if r.Metadata["auth"] == "none" {
return "endpoint declares no auth"
}
return ""
}),
RunResource: ...,
}- Checks are validated and sorted into parallel levels via Kahn's topological sort.
- All checks within a level execute concurrently, bounded by
WithMaxConcurrency. - Each level receives a frozen snapshot of the result store taken before any check in that level starts — intra-level races are impossible by design.
ScopePerResourcechecks within a level fan out over all currently known resources, bounded byWithMaxResourceConcurrency(or the check's ownConcurrencyfield).
A Scenario groups a named set of checks to be executed together. Use RunScenario instead of Run to execute only that subset — the engine's registered checks are ignored.
restScenario := harnessx.Scenario{
ID: "rest-api",
Name: "REST API Scan",
Checks: []harnessx.Check{discoveryCheck, authCheck, schemaCheck},
}
summary, err := engine.RunScenario(ctx, target, restScenario)To share business logic across scenarios while varying the dependency order, define the Run function as a variable and reference it in multiple Check values with different DependsOn fields:
var checkAuthFn harnessx.CheckFunc = func(...) (harnessx.Result, error) { ... }
// REST: auth after endpoint discovery
restAuth := harnessx.Check{ID: "rest-auth", DependsOn: []harnessx.CheckID{"rest-discovery"}, Run: checkAuthFn}
// GraphQL: same logic, wired after schema introspection
gqlAuth := harnessx.Check{ID: "gql-auth", DependsOn: []harnessx.CheckID{"gql-introspection"}, Run: checkAuthFn}Implement the Reporter interface to receive real-time events:
type Reporter interface {
OnScanStart(target Target, totalChecks int)
OnCheckStart(check Check, target Target, resource *Resource)
OnCheckComplete(result Result)
OnScanComplete(summary ScanSummary)
}OnScanStart fires before any check runs with the total registered check count — use it to initialise a progress bar. OnScanComplete is always called — even after a context cancellation or early error.
engine := harnessx.New(
harnessx.WithReporters(myReporter, otherReporter),
)- Advanced Scan: A comprehensive example demonstrating multi-level dependencies, resource discovery, custom conditions, and a pretty-printing reporter.
- Multi-Scenario Scan: REST API and GraphQL API scenarios sharing business logic with different dependency graphs. Select a scenario at runtime via CLI argument.
// New creates a new Engine with the given options.
func New(opts ...Option) *Engine
// Register adds checks to the engine. Returns ErrDuplicateCheckID if any ID conflicts.
func (e *Engine) Register(checks ...Check) error
// Run executes all registered checks against target.
// Always calls Reporter.OnScanComplete before returning.
func (e *Engine) Run(ctx context.Context, target Target) (ScanSummary, error)
// RunScenario executes only the checks in scenario against target.
// Ignores checks registered via Register or WithChecks.
// Always calls Reporter.OnScanComplete before returning.
func (e *Engine) RunScenario(ctx context.Context, target Target, scenario Scenario) (ScanSummary, error)// SkipAlways always returns reason — the check (or resource) is always skipped.
func SkipAlways(reason string) SkipDecision
// SkipWhen evaluates fn once for the whole check.
func SkipWhen(fn func(ctx context.Context, target Target, store ResultStore) string) SkipDecision
// SkipResourceWhen evaluates fn once per resource, for ScopePerResource checks.
func SkipResourceWhen(fn func(ctx context.Context, target Target, resource Resource, store ResultStore) string) SkipDecision| Option | Default | Description |
|---|---|---|
WithMaxConcurrency(n) |
runtime.NumCPU() |
Maximum checks running concurrently within a level |
WithMaxResourceConcurrency(n) |
runtime.NumCPU() |
Default maximum resource goroutines per check |
WithDefaultTimeout(d) |
30s |
Per-check timeout when Check.Timeout is zero |
WithReporters(reporters...) |
NoopReporter |
Real-time event callbacks (multiple reporters supported) |
WithChecks(checks...) |
— | Register checks at construction time |
| Error | Meaning |
|---|---|
ErrNoChecks |
Run was called with no checks registered |
ErrDuplicateCheckID |
Two checks share the same CheckID |
ErrUnknownDependency |
A DependsOn entry references a non-existent check |
ErrCycleDetected |
The dependency graph contains a cycle |
*ScanError |
A check's Run/RunResource returned an error or panicked |
This repository is licensed under the MIT License @ CerberAuth.