Documentation
¶
Overview ¶
Package denial implements the attacker-mintable denial lane (MCP-EVENT-007): pre-queue admission control and attacker-rate-independent coalescing for authentication-failure and authorization-denial events, so N equivalent denials in one bucket/window cost O(1) durable records. The aggregator holds bounded in-memory aggregates keyed by capability/listener × normalized source bucket × denial reason × bounded time window (plus verified tenant/principal only where identity exists — never invented before identity). It NEVER commits to disk itself and NEVER consumes the P-CRIT reserve; the manager commits the flushed aggregates into P-DEN and, on failure, records the DISTINCT denial-loss counter and enters denial-lane-degraded. A denial-lane failure never blocks authenticated work and never enters critical-durability-degraded.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NormalizeSource ¶
NormalizeSource collapses a raw source address into a stable, bounded bucket token, following the repository precedent for client-evidence tokens: an IPv6 address is collapsed to its /64 prefix (so per-host churn within a /64 folds into one bucket), an IPv4 address is kept raw (a /24 would over-collapse a NAT fleet), and a non-IP source is sanitized and bounded. The result is a bucket key, never a raw unbounded source text.
Types ¶
type Aggregator ¶
type Aggregator struct {
// contains filtered or unexported fields
}
Aggregator coalesces denial observations into bounded O(1) aggregates.
func NewAggregator ¶
func NewAggregator(cfg Config) *Aggregator
NewAggregator builds an aggregator. It does not start any goroutine; the manager drives Flush on a bounded cadence.
func (*Aggregator) Flush ¶
Flush returns the denial-aggregate events whose window has closed (windowIdx < current window), removing them from the map. force=true flushes every aggregate (used at shutdown). The manager commits the returned events into P-DEN; a commit failure is the manager's denial-loss/degraded concern, not the aggregator's — a returned event is already coalesced O(1).
func (*Aggregator) Observe ¶
func (a *Aggregator) Observe(o Observation) bool
Observe records one denial. It is O(1) and never blocks. It returns true when the denial was folded into an aggregate, and false when a cardinality bound dropped it (the triggering request is still denied by the caller regardless — admission to the aggregate is not admission of the request). A dropped observation is counted so the manager can surface denial-lane pressure.
type Config ¶
type Config struct {
Capability model.Capability
NodeID string
Window time.Duration
MaxBuckets int
MaxPerSource int
// IDGen mints a safe, unpredictable, prefixed id (evt_/rpl_/cor_). Injected so
// tests are deterministic and production uses crypto/rand.
IDGen func(prefix string) string
}
Config parameterises the aggregator. Bounds are validated event limits threaded through by the manager. Window is the coalescing window; MaxBuckets and MaxPerSource bound aggregation-key cardinality so an attacker cannot create an unbounded number of keys.
type Observation ¶
type Observation struct {
Now time.Time
Listener string
Source string // raw source; normalized internally
Reason string
Tenant string // "" before identity exists
Principal string // "" before identity exists
}
Observation is one denial event fed to the aggregator BEFORE it can occupy any shared queue. Tenant and Principal are set ONLY when verified identity exists (the authenticated-but-unauthorized case); for a pre-authentication failure they are empty and no tenant is ever invented from an attacker hint.