https://github.com/cerberauth/harnessx
harnessx is a concurrent, dependency-aware security check orchestration engine for Go. It lets you define a graph of security checks with explicit dependencies, run them in parallel waves, and collect structured findings — without writing any scheduling or concurrency boilerplate.
https://github.com/cerberauth/harnessx
Last synced: about 2 months ago
JSON representation
harnessx is a concurrent, dependency-aware security check orchestration engine for Go. It lets you define a graph of security checks with explicit dependencies, run them in parallel waves, and collect structured findings — without writing any scheduling or concurrency boilerplate.
- Host: GitHub
- URL: https://github.com/cerberauth/harnessx
- Owner: cerberauth
- License: mit
- Created: 2026-03-01T11:13:20.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-05-30T15:42:50.000Z (about 2 months ago)
- Last Synced: 2026-05-30T17:14:41.859Z (about 2 months ago)
- Language: Go
- Homepage:
- Size: 53.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# harnessx
[](https://github.com/cerberauth/harnessx/actions/workflows/ci.yml)


[](https://goreportcard.com/report/github.com/cerberauth/harnessx)
[](https://godoc.org/github.com/cerberauth/harnessx)
**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.
---
## Features
| 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 |
| 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 |
| Zero dependencies | Pure Go standard library — no external runtime dependencies |
---
## Installation
```bash
go get github.com/cerberauth/harnessx
```
Requires **Go 1.22+**.
---
## Quick Start
```go
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)
}
}
```
---
## Concepts
### Checks
A `Check` is the fundamental unit of work. Each check has a unique `ID`, an execution `Scope`, and either a `Run` or `RunResource` function.
```go
type Check struct {
ID CheckID
Name string
Description string
Tags []string
// Dependency graph
DependsOn []CheckID
Conditions []Condition // AND-evaluated; any false → skip
// 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
}
```
### Scopes
| 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)` |
### Resource Discovery
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.
```go
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
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 |
```go
// 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: ...,
}
```
### Execution Model
1. Checks are validated and sorted into parallel **levels** via Kahn's topological sort.
2. All checks within a level execute concurrently, bounded by `WithMaxConcurrency`.
3. 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.
4. `ScopePerResource` checks within a level fan out over all currently known resources, bounded by `WithMaxResourceConcurrency` (or the check's own `Concurrency` field).
### Reporter
Implement the `Reporter` interface to receive real-time events:
```go
type Reporter interface {
OnCheckStart(check Check, target Target)
OnCheckComplete(result Result)
OnScanComplete(summary ScanSummary)
}
```
`OnScanComplete` is **always** called — even after a context cancellation or early error.
```go
engine := harnessx.New(
harnessx.WithReporters(myReporter, otherReporter),
)
```
---
## Examples
- [Advanced Scan](./examples/advanced-scan/main.go): A comprehensive example demonstrating multi-level dependencies, resource discovery, custom conditions, and a pretty-printing reporter.
---
## API Reference
### Engine
```go
// 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)
```
### Options
| 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 |
### Errors
| 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 |
---
## License
This repository is licensed under the [MIT License](https://github.com/cerberauth/harnessx/blob/main/LICENSE) @ [CerberAuth](https://www.cerberauth.com/).