attack

package
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package attack defines the Executor interface and shared utilities for all Batesian attack implementations.

Index

Constants

View Source
const DryRunOOBPlaceholderURL = "http://oob.batesian.invalid"

DryRunOOBPlaceholderURL is the base callback URL substituted for a real OOB listener during a dry run. SSRF executors normally bind a local listener to catch callbacks; a dry run must bind no socket, so they use this non-resolving placeholder instead. The .invalid TLD (RFC 6761) never resolves, and the recorded plan still shows a representative callback URL.

Variables

View Source
var ErrInconclusive = errors.New("rule could not reach a testable endpoint")

ErrInconclusive signals that a rule could not reach a testable endpoint, so it was not exercised. The engine surfaces this as a skipped result (neither a finding nor an error) so reporting can distinguish "tested, nothing found" from "could not test". Wrap it with %w to attach detail.

View Source
var Version = "dev"

Version is the build-time version string injected from main via attack.Version. It is embedded in the User-Agent header on every outbound HTTP request. Defaults to "dev" so go run / unit tests have a useful value.

Functions

func Register added in v1.0.0

func Register(attackType string, c Constructor)

Register associates an attack-type string with its Executor constructor. It panics on duplicate registration, which can only happen via a programming error (two executors claiming the same type) and is caught at startup.

func RegisteredTypes added in v1.0.0

func RegisteredTypes() []string

RegisteredTypes returns the sorted list of all registered attack types. Useful for diagnostics and for validating that every shipped rule has a matching executor.

func Transport added in v1.2.0

func Transport(opts Options) http.RoundTripper

Transport returns the RoundTripper for a scan-path HTTP client. In a dry run it returns a recording transport that sends nothing; otherwise a real *http.Transport honoring opts.SkipTLS. Routing every scan-path client through this one function is what makes the dry-run "send nothing" guarantee total.

Types

type Artifact added in v1.0.0

type Artifact struct {
	// Kind classifies the datum so consumers can query by type.
	Kind ArtifactKind
	// Value is the primary payload (token string, URL, capability name, ID, ...).
	Value string
	// Principal names the identity/tenant this artifact belongs to. Empty means
	// anonymous / the default principal. Multi-principal and multi-tenant rules
	// use this to keep one principal's artifacts distinct from another's.
	Principal string
	// Producer is the rule ID that published the artifact, for provenance.
	Producer string
	// Meta carries optional extra fields (granted scopes, aud, mime type, ...).
	Meta map[string]string
}

Artifact is a single typed datum on the Blackboard.

type ArtifactKind added in v1.0.0

type ArtifactKind string

ArtifactKind classifies a piece of state discovered or produced during a scan. Chained executors publish artifacts of a given kind and consume the kinds they depend on, which lets the engine order producers before consumers and lets one rule build on another's results (e.g. a discovered capability driving a targeted follow-up, or an accepted token driving a downstream access test).

const (
	// ArtifactToken is a bearer token (real, forged, or replayed) that downstream
	// rules can present to test access. Principal identifies whose token it is.
	ArtifactToken ArtifactKind = "token"
	// ArtifactEndpoint is a confirmed live protocol endpoint (e.g. the resolved
	// MCP JSON-RPC URL or an A2A RPC URL) so consumers need not re-discover it.
	ArtifactEndpoint ArtifactKind = "endpoint"
	// ArtifactCapability is a capability the target advertised (e.g. "prompts",
	// "resources", "push-notifications", "extended-agent-card").
	ArtifactCapability ArtifactKind = "capability"
	// ArtifactTaskID is an A2A task identifier created during a scan.
	ArtifactTaskID ArtifactKind = "task-id"
	// ArtifactContextID is an A2A context identifier created during a scan.
	ArtifactContextID ArtifactKind = "context-id"
	// ArtifactSession is a transport session identifier (e.g. an MCP
	// Mcp-Session-Id) that a downstream rule can present to test whether the
	// session can be borrowed or replayed across principals.
	ArtifactSession ArtifactKind = "session-id"
	// ArtifactAudience is the resource server's expected JWT `aud` value, once
	// known (operator-supplied or discovered via RFC 9728).
	ArtifactAudience ArtifactKind = "audience"
	// ArtifactClient is an OAuth client registered during a scan (e.g. via DCR).
	ArtifactClient ArtifactKind = "registered-client"
)

type Blackboard added in v1.0.0

type Blackboard struct {
	// contains filtered or unexported fields
}

Blackboard is the concurrency-safe shared state for a single scan. Executors publish artifacts they discover and read artifacts published by earlier rules. Its zero value is not usable; construct it with NewBlackboard.

func NewBlackboard added in v1.0.0

func NewBlackboard() *Blackboard

NewBlackboard returns an empty, ready-to-use Blackboard.

func (*Blackboard) All added in v1.0.0

func (b *Blackboard) All() []Artifact

All returns a copy of every artifact on the blackboard, in publication order.

func (*Blackboard) ByKind added in v1.0.0

func (b *Blackboard) ByKind(kind ArtifactKind) []Artifact

ByKind returns all artifacts of the given kind, in publication order.

func (*Blackboard) Find added in v1.0.0

func (b *Blackboard) Find(pred func(Artifact) bool) []Artifact

Find returns all artifacts matching pred, in publication order. It lets consumers filter by principal, producer, or Meta beyond a simple kind lookup.

func (*Blackboard) First added in v1.0.0

func (b *Blackboard) First(kind ArtifactKind) (Artifact, bool)

First returns the first artifact of the given kind, if any.

func (*Blackboard) Publish added in v1.0.0

func (b *Blackboard) Publish(a Artifact)

Publish records an artifact on the blackboard.

type ChainExecutor added in v1.0.0

type ChainExecutor interface {
	Executor
	ExecuteChained(ctx context.Context, target string, opts Options, bb *Blackboard) ([]Finding, error)
}

ChainExecutor is the opt-in interface for rules that participate in stateful multi-step chaining. The engine calls ExecuteChained (passing the shared Blackboard) when an executor implements this interface, and falls back to the plain Executor.Execute otherwise. Existing single-shot rules are unaffected.

A ChainExecutor must tolerate an empty or partial blackboard: if the artifacts it needs were not produced (e.g. the producing rule was filtered out, or the target lacked the precondition), it should return no findings rather than an error, consistent with the project's clean-skip convention.

type ChainStep added in v1.0.0

type ChainStep struct {
	// Hop is the 1-based position of this step in the chain.
	Hop int
	// Principal is the identity/tenant that performed the step (empty = default).
	Principal string
	// Action describes what was attempted (e.g. "authenticate as tenant A").
	Action string
	// Outcome describes the result (e.g. "token issued", "read of tenant B granted").
	Outcome string
}

ChainStep is one hop in a multi-step attack's provenance trail, attached to a Finding so chain-of-custody and auditability are visible in output.

type Confidence

type Confidence string

Confidence describes how certain the finding is. "confirmed" means the attack demonstrably succeeded (e.g., unauthenticated data returned). "indicator" means a suspicious pattern was detected but exploitability is not proven (e.g., heuristic scan).

const (
	ConfirmedExploit  Confidence = "confirmed"
	RiskIndicator     Confidence = "indicator"
	ConfidenceDefault Confidence = "confirmed" // legacy - callers that don't set Confidence get confirmed
)

type Constructor added in v1.0.0

type Constructor func(RuleContext) Executor

Constructor builds an Executor for a given rule context. Each attack implementation registers a Constructor under its attack-type string.

func Resolve added in v1.0.0

func Resolve(attackType string) (Constructor, bool)

Resolve returns the Constructor registered for attackType, if any.

type Dependencies added in v1.0.0

type Dependencies interface {
	// Produces lists the artifact kinds this executor may publish.
	Produces() []ArtifactKind
	// Requires lists the artifact kinds this executor consumes from upstream.
	Requires() []ArtifactKind
}

Dependencies is the opt-in interface an executor implements to declare the artifact kinds it produces and consumes. The engine uses these declarations to order execution so that producers of a kind run before its consumers. An executor that does not implement Dependencies is treated as having no declared dependencies and keeps its original position in the run order.

type Executor

type Executor interface {
	// Execute runs the attack and returns a (possibly empty) list of findings.
	Execute(ctx context.Context, target string, opts Options) ([]Finding, error)
}

Executor runs a single attack rule against a target and returns findings.

type Finding

type Finding struct {
	RuleID   string
	RuleName string
	Severity string
	// Confidence describes whether the finding is a confirmed exploit or a risk indicator.
	// Confirmed findings: the attack demonstrably succeeded (auth bypass proven, data returned).
	// Indicator findings: a suspicious pattern was detected; manual verification recommended.
	Confidence  Confidence
	Title       string
	Description string
	Evidence    string
	Remediation string
	TargetURL   string
	// Chain is the optional provenance trail for findings produced by a
	// multi-step chained rule. Nil/empty for single-shot rules.
	Chain []ChainStep
}

Finding represents a confirmed vulnerability or notable observation.

type HTTPClient

type HTTPClient struct {
	// contains filtered or unexported fields
}

HTTPClient is a thin wrapper around net/http.Client with helpers for attack requests.

func NewHTTPClient

func NewHTTPClient(opts Options, vars Vars) *HTTPClient

NewHTTPClient creates an attack HTTP client.

func NewUnauthHTTPClient

func NewUnauthHTTPClient(opts Options, vars Vars) *HTTPClient

NewUnauthHTTPClient creates an attack HTTP client with no bearer token. Use this for requests that are intentionally unauthenticated (e.g. baseline probes that test whether an endpoint can be reached without credentials). Using the standard NewHTTPClient would inject opts.Token, which would cause "no auth" tests to silently become authenticated when --token is set.

func (*HTTPClient) GET

func (c *HTTPClient) GET(ctx context.Context, urlTpl string, headers map[string]string) (*Response, error)

GET sends a GET request to the expanded URL.

func (*HTTPClient) OPTIONS

func (c *HTTPClient) OPTIONS(ctx context.Context, urlTpl string, headers map[string]string) (*Response, error)

OPTIONS sends an OPTIONS request (used for CORS preflight probes).

func (*HTTPClient) POST

func (c *HTTPClient) POST(ctx context.Context, urlTpl string, headers map[string]string, body interface{}) (*Response, error)

POST sends a POST request with a JSON body. body may be a map or struct.

type Options

type Options struct {
	// OOBListenerURL is the base URL of the local OOB callback listener.
	// Empty if OOB is not enabled.
	OOBListenerURL string

	// Token is an optional bearer token for authenticated requests.
	Token string

	// TimeoutSeconds is the per-request HTTP timeout.
	TimeoutSeconds int

	// SkipTLS disables TLS certificate verification.
	SkipTLS bool

	// Verbose enables debug logging.
	Verbose bool

	// AudienceClaim is the operator-supplied expected JWT `aud` value for the
	// target MCP resource server. Currently consumed only by mcp-oauth-audience-002,
	// which derives canary-mismatch probes (substring/case/array-shape) from this
	// value. When empty, that rule attempts RFC 9728 protected-resource-metadata
	// auto-discovery and otherwise reports Inconclusive.
	AudienceClaim string

	// Principals are additional authenticated identities (each with its own token
	// and optional tenant) that multi-step chained rules can act as. Cross-tenant
	// and handoff rules need at least two principals to prove that one identity
	// cannot reach another's objects. Empty for single-principal scans, where
	// Token is the only identity.
	Principals []Principal

	// DryRun, when true, records every outbound request instead of sending it, so
	// an operator can review the exact traffic a scan would generate before
	// authorizing it. Every scan-path HTTP client honors this via Transport.
	DryRun bool

	// Recorder collects the requests captured during a dry run. It must be non-nil
	// when DryRun is set; the engine stamps the active rule onto it per rule.
	Recorder *Recorder
}

Options carries per-scan configuration into each executor.

type Principal added in v1.0.0

type Principal struct {
	// Name is a logical label for the identity, e.g. "tenant-a". Used in evidence
	// and ChainStep provenance.
	Name string
	// Token is the bearer token authenticating this principal.
	Token string
	// Tenant is the tenant/org identifier this principal belongs to, when the
	// target models multi-tenancy. May equal Name.
	Tenant string
	// Headers are extra request headers a server uses to route or scope the
	// principal's tenant (e.g. {"X-Tenant-Id": "A"}). Optional.
	Headers map[string]string
}

Principal is an authenticated identity a chained rule can act as. Multi-tenant isolation and session-handoff rules use two or more principals to attempt cross-principal access that must hard-fail.

type RecordedRequest added in v1.2.0

type RecordedRequest struct {
	RuleID  string
	Method  string
	URL     string
	Headers map[string]string // Authorization value is redacted
	Body    string
}

RecordedRequest is one outbound HTTP request captured during a dry run.

type Recorder added in v1.2.0

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder collects the requests a dry run would have sent instead of sending them. It is safe for concurrent use, though the engine drives rules sequentially and stamps the active rule via SetCurrentRule.

func (*Recorder) Requests added in v1.2.0

func (r *Recorder) Requests() []RecordedRequest

Requests returns a copy of the recorded requests in capture order.

func (*Recorder) SetCurrentRule added in v1.2.0

func (r *Recorder) SetCurrentRule(ruleID string)

SetCurrentRule labels subsequently recorded requests with ruleID. The engine calls this before running each rule so the dry-run plan can group by rule.

type Response

type Response struct {
	URL        string
	StatusCode int
	Headers    http.Header
	Body       []byte
	Elapsed    time.Duration
}

Response captures an HTTP response for assertion evaluation.

func (*Response) BodyString

func (r *Response) BodyString() string

BodyString returns the response body as a string.

func (*Response) ContainsAny

func (r *Response) ContainsAny(substrings ...string) bool

ContainsAny returns true if the body contains any of the given substrings. Empty substrings are skipped: strings.Contains(body, "") is always true, so an empty needle (e.g. an optional, absent value like a missing contextId) must not be treated as a match against any body.

func (*Response) IsAccepted added in v1.5.0

func (r *Response) IsAccepted() bool

IsAccepted reports whether the response represents a successful JSON-RPC result: an HTTP 2xx whose body is valid JSON carrying a "result" envelope and no "error" envelope. This is the canonical "the JSON-RPC call succeeded" oracle.

It exists because the older idiom IsSuccess() && !isJSONRPCError(body) treats any 2xx that is not a JSON-RPC error envelope as success - including an HTML login page, an empty body, "{}", or a bare object. Those are not results, and judging them as "accepted" produces false positives whenever a target answers an unauthenticated probe with a 2xx non-JSON body (common: redirects to a login page, generic 200 acks, HTML error interstitials).

A JSON-null or empty-object result ({"result":null}, {"result":{}}) still counts as accepted: both are valid JSON-RPC success shapes, and rejecting them would risk false negatives on methods that legitimately return an empty result (e.g. logging/setLevel).

func (*Response) IsJSON added in v1.5.0

func (r *Response) IsJSON() bool

IsJSON reports whether the response body is a JSON object. Use it for raw HTTP responses that are not JSON-RPC result envelopes (for example an A2A extended agent card fetched over HTTP GET) to reject HTML, empty, or non-JSON bodies before applying a structural shape check. Prefer IsAccepted for JSON-RPC method calls, which additionally requires a result envelope.

func (*Response) IsSuccess

func (r *Response) IsSuccess() bool

IsSuccess returns true for 2xx status codes.

func (*Response) JSONField

func (r *Response) JSONField(path string) string

JSONField extracts a nested field from the response body using a dot-path. Example: JSONField("scope") returns the "scope" value from a flat JSON object. Returns empty string if the field is absent or the body is not valid JSON.

func (*Response) NormalizeHeaders

func (r *Response) NormalizeHeaders() map[string]string

NormalizeHeaders returns a lowercase-keyed map of the response headers. Multiple values for the same header are joined with ", ".

type RuleContext

type RuleContext struct {
	ID          string
	Name        string
	Severity    string
	Remediation string
}

RuleContext carries the metadata from a rule that executors need to populate findings. This avoids importing the rules package inside executor packages (preventing import cycles).

type Vars

type Vars struct {
	BaseURL     string
	OOBListener string
	RandID      string
}

Vars holds template variable substitutions for a single attack execution.

func NewVars

func NewVars(baseURL, oobListener string) Vars

NewVars creates a Vars instance for the given target, pre-populating RandID.

func (Vars) Expand

func (v Vars) Expand(s string) string

Expand replaces {{BaseURL}}, {{OOBListener}}, and {{RandID}} in s.

func (Vars) ExpandMap

func (v Vars) ExpandMap(m map[string]string) map[string]string

ExpandMap returns a copy of m with all values expanded.

Directories

Path Synopsis
Package a2a contains attack executors for the A2A protocol.
Package a2a contains attack executors for the A2A protocol.
Package mcp contains attack executors for the MCP (Model Context Protocol).
Package mcp contains attack executors for the MCP (Model Context Protocol).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL