probe

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package probe mints synthetic tokens and verifies consumer behavior against real staging endpoints (PRD §6). Minted tokens are NEVER persisted.

Index

Constants

View Source
const (
	ProbeValidToken         = "valid_token"
	ProbeExpired            = "expired"
	ProbeNotYetValid        = "not_yet_valid"
	ProbeWrongIssuer        = "wrong_issuer"
	ProbeWrongAudience      = "wrong_audience"
	ProbeAlgNone            = "alg_none"
	ProbeAlgConfusion       = "alg_confusion"
	ProbeTamperedSignature  = "tampered_signature"
	ProbeMissingClaim       = "missing_required_claim"
	ProbeRetiredKey         = "retired_key"
	ProbeSiblingClientToken = "sibling_client_token"
	ProbeHeaderBypass       = "header_bypass"
	ProbeCanaryKey          = "canary_key"
)

Probe ID constants — stable identifiers referenced by evidence and the API.

Variables

View Source
var ErrProbeNotApplicable = errors.New("probe not applicable")

ErrProbeNotApplicable signals that a probe cannot run for a consumer (e.g. no canary key announced, no retired key, no sibling audience). The engine records it as skipped, never as a finding.

Functions

func BaselineClaims

func BaselineClaims(iss model.Issuer, c model.Consumer, now time.Time) map[string]any

BaselineClaims builds the standard claim set for a minted token (PRD §6.2). Every required claim gets a placeholder value. The returned map is fresh per call so probes can mutate it without cross-contamination.

func RawUnsignedToken

func RawUnsignedToken(claims map[string]any) (string, error)

RawUnsignedToken builds a compact JWT with header {"alg":"none"} and an empty signature segment (probe 6). Most JOSE libraries refuse to emit alg=none, so the string is assembled by hand: base64url(header) + "." + base64url(payload) + "."

func SignHS256

func SignHS256(secret []byte, claims map[string]any) (string, error)

SignHS256 signs claims with HMAC-SHA256 using the given secret (probe 7). The attack presents an RS256 issuer's PUBLIC key PEM bytes as the HMAC secret; a server that naively trusts the token's alg header will verify it.

func TamperSignature

func TamperSignature(token string) (string, error)

TamperSignature flips the final byte of a compact JWS signature (probe 8), producing a structurally valid but cryptographically invalid token.

Types

type ConsumerOutcome

type ConsumerOutcome struct {
	ConsumerID string
	Verified   bool // baseline valid-token probe passed
	Skipped    bool // not probeable, host not allowed, or kill switch
	Reason     string
	Results    []model.ProbeResult
}

ConsumerOutcome summarizes what happened for one consumer.

type Engine

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

Engine runs probes against consumers.

func NewEngine

func NewEngine(cfg EngineConfig) *Engine

NewEngine constructs an engine with the default probe set.

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, iss model.Issuer, mint MintFunc, consumers []model.Consumer) ([]model.ProbeResult, []ConsumerOutcome, error)

Run probes the applicable consumers and returns all probe results plus a per-consumer outcome summary. iss provides the JWKS key ids; mint signs tokens (the issuer adapter's MintToken).

func (*Engine) RunHarness

func (e *Engine) RunHarness(ctx context.Context, iss model.Issuer, mint MintFunc, consumers []model.Consumer) ([]model.ProbeResult, []ConsumerOutcome, error)

RunHarness fires the generative attack corpus (internal/attack) at each applicable consumer's endpoint. It reuses the same safety machinery as Run — the staging allowlist, response scrubbing, and dry-run — and records one ProbeResult per attack token, keyed "harness:<threat>:<name>". A result with Passed=false whose token expected rejection is a live vulnerability.

func (*Engine) WithClock

func (e *Engine) WithClock(now func() time.Time) *Engine

WithClock injects a clock (tests).

type EngineConfig

type EngineConfig struct {
	MaxConcurrentPerConsumer int           // reserved; probes run sequentially per consumer
	MaxConcurrentGlobal      int           // default 20 — consumers probed in parallel
	RequestTimeout           time.Duration // default 10s
	InterProbeDelay          time.Duration // default 200ms
	AbortOnConsecutive5xx    int           // default 3
	DryRun                   bool

	// Allowlist of host substrings the engine may target. Empty = deny all
	// unless AllowProduction is set (the --i-know-this-is-production override).
	Allowlist       []string
	AllowProduction bool

	// KillSwitch is polled before each consumer; returning true stops probing.
	KillSwitch func() bool

	// MaxRequiredClaimProbes caps probe-9 sub-probes (PRD OPEN-3, default 8).
	MaxRequiredClaimProbes int
}

EngineConfig controls probe execution (PRD §6.3).

func DefaultEngineConfig

func DefaultEngineConfig() EngineConfig

DefaultEngineConfig returns the PRD §6.3 defaults.

func (EngineConfig) HostAllowed

func (c EngineConfig) HostAllowed(host string) bool

HostAllowed reports whether the engine may target a host (staging guard). Default deny: an empty allowlist without the production override blocks all. Matching is exact or on a dot-delimited suffix boundary — NEVER a bare substring — so an allowlist of "staging.internal" does not match "staging.internal.attacker.com" or "evil-127.0.0.1.attacker.com".

type Expectation

type Expectation struct {
	AcceptStatuses []int
	ShouldAccept   bool
}

Expectation encodes what a PASS looks like for a probe.

func (Expectation) Accepts

func (e Expectation) Accepts(status int) bool

Accepts reports whether a status code is a PASS for this expectation.

type MintContext

type MintContext struct {
	Issuer   model.Issuer
	Consumer model.Consumer
	// Claims is the baseline claim set (PRD §6.2), fresh per probe invocation.
	Claims map[string]any
	Now    time.Time
	Mint   MintFunc

	// Key IDs resolved from the issuer's JWKS.
	ActiveKID    string
	AnnouncedKID string // canary; empty if none announced
	RetiredKID   string // empty if none retired
	// ActiveKeyPEM is the active key's PEM-encoded public key (probe 7 secret).
	ActiveKeyPEM string
	// SiblingAudience is another consumer's audience (probe 11); empty if none.
	SiblingAudience string
	// OmitClaim, when set, is the required claim to drop (probe 9 sub-probes).
	OmitClaim string
}

MintContext is passed to a probe's Mutate function. It exposes everything a probe needs to construct its Authorization header without knowing how signing works.

type MintFunc

type MintFunc func(kid string, claims map[string]any) (string, error)

MintFunc signs claims with the named key (empty kid = active key).

type MutateFunc

type MutateFunc func(ctx MintContext) (authHeader string, extraHeaders map[string]string, err error)

MutateFunc returns the Authorization header value and any extra headers.

type Probe

type Probe struct {
	ID                 string
	Name               string
	Description        string
	RequiresPrivateKey bool
	AppliesTo          func(c model.Consumer) bool
	Mutate             MutateFunc
	Expect             Expectation
	Severity           model.Severity
}

Probe is one verification behavior.

func Definitions

func Definitions() []Probe

Definitions returns the 13 probe definitions with their mutation logic (PRD §6.2). Probe 9 (missing_required_claim) is expanded into per-claim sub-probes by the engine; its Mutate drops the single claim named in ctx.OmitClaim.

Jump to

Keyboard shortcuts

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