aicverifier

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

README

aic-verifier

Give any HTTP service the ability to check who the agent is, what it is allowed to do, and what was actually recorded — for agents that present an AIC (Agent Identity Certificate) over mTLS or as a signed JWT.

It is a small Go library, not a gateway: wrap your handler, or put the bundled reverse proxy in front of an API.

English · 中文

Status: early. The API may change before the first stable release. It evaluates CLC-1.5; see docs/DESIGN.md for the layering and docs/evidence.md for the evidence side.

What it does

Per request, one pipeline decides: certificate validity → CRL/OCSP → roles → AIC decision → capability ∩ (principal authorization) → parameter bounds → allow / allow_unresolved / deny, and (optionally) writes a decision record you can recompute later.

  • Two transports: mTLS client certificate carrying the AIC extension, or Authorization: Bearer <AIC-JWT>.
  • Two integration styles: (*Config).Handler / AuthMiddleware around your own handler, or Server as a reverse proxy that injects X-AIC-* identity headers.
  • Evidence, not logs: each decision can be emitted as a DSSE-wrapped CLC decision record, plus admission records for refusals that happen before the language layer and outcome records from the effect boundary.

Requirements

Requirement Why
Go 1.26 or newer the module declares go 1.26; no cgo
github.com/varwof/register v0.3.0 the CLC evaluator and the decision-record format
github.com/varwof/types v0.6.0 AIC / AIC-JWT structures
github.com/varwof/pkcs7 v0.1.0 the semantics-layer detached signature check
github.com/mark3labs/mcp-go v1.0.0 only if you import the mcp subpackage

They come in with go get; there are no local replace directives and nothing is vendored. The SDK itself needs no external service: CRL/OCSP responders and an RFC 3161 timestamp authority are optional and only used when you configure them.

For the demo below you additionally need curl (any mTLS-capable client works) and three free local ports: 9444 (proxy), 9081 (demo backend it starts itself), and later 9443 (the evidence demo).

Quick start (2 minutes)

Four steps, each one short. The demo generates its own CA and certificates, so there is nothing to configure first.

1. Get the code and generate demo certificates — a CA plus a server certificate and an agent certificate that carries an AIC extension:

git clone https://github.com/varwof/aic-verifier && cd aic-verifier
go run ./examples/mtls-backend/gen-cert -out ./demo-certs

2. Start the protected service — it terminates mTLS on :9444 and forwards admitted requests to a demo backend on :9081 (started by the same process). Leave it running in this shell:

go run ./examples/mtls-backend --certs ./demo-certs

3. Call it as the agent — in a second shell, present the agent certificate:

curl -sS --cert demo-certs/client-cert.pem --key demo-certs/client-key.pem \
     --cacert demo-certs/ca-cert.pem https://localhost:9444/api
# {"backend":"real-api-mtls","identity":{"X-Forwarded-For":"127.0.0.1, 127.0.0.1"}}

4. Watch a refusal — ask for something the agent is not allowed to do; the request never reaches the backend:

curl -sS --cert demo-certs/client-cert.pem --key demo-certs/client-key.pem \
     --cacert demo-certs/ca-cert.pem https://localhost:9444/api/transfer
# {"code":"access_denied","message":"agent missing required capabilities"}

Stop the demo with Ctrl-C in the first shell. What the request left behind — the decision record, and how to recompute it — is docs/quickstart.md, which also covers calling the API without a client certificate and reading the record back.

Use it in your own service

Wrap an existing handler — the SDK authenticates the request, refuses early, and hands your handler the verified identity:

conf := &aicverifier.Config{
    CACertFile:           "certs/ca-cert.pem",
    AuthMode:             aicverifier.MTLSOnly,
    RequireAIC:           true,
    RequiredCapabilities: []string{"demo/example-v1:api:read"},
}

mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
    ac := aicverifier.FromContext(r.Context()) // agent id, principal, caps, verdict
    fmt.Fprintf(w, "hello %s\n", ac.AgentID)
})

handler, err := conf.Handler(mux) // runs the whole pipeline around mux
if err != nil {
    log.Fatal(err)
}
srv := &http.Server{
    Addr:      ":8443",
    Handler:   handler,
    TLSConfig: muxTLSConfig, // ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: <your CA>
}
log.Fatal(srv.ListenAndServeTLS("certs/server-cert.pem", "certs/server-key.pem"))

Or run the reverse proxy and keep your backend untouched:

target, _ := url.Parse("http://127.0.0.1:8080")
server, err := aicverifier.NewServer(conf, []aicverifier.Route{
    {Path: "/api", Target: target, RequiredCapabilities: []string{"demo/example-v1:api:read"}},
})
log.Fatal(server.ListenAndServe(":9444"))

Refusals are typed: *aicverifier.AuthError carries the HTTP status, a stable reason code, the records the refusal produced, and — when presenting evidence could fix it — an RFC 9457 problem document with a CLC-CHALLENGE-v1 challenge.

Configuration you will actually touch

Field What it does
CACertFile / JWTCAFile trust anchors for mTLS client certs / bearer tokens
AuthMode MTLSOnly, BearerOnly, MTLSOrBearer (default)
RequireAIC reject certificates that carry no AIC extension
RequiredCapabilities capability ids the caller must hold (on Config and per Route)
EnforceConstraints evaluate authorization constraints (time window, CIDR, max_rows)
AdmissionConfig CRL/OCSP, roles, SPIFFE, delegation chain, monitoring hooks
Evidence sink, evidence profile, freshness TTL, signing, outcome emission
Challenge whether a refusable refusal carries a challenge, and its TTL/audience

The full list, with types and defaults, is in docs/api.md.

Evidence in one paragraph

A decision is not much use if nobody can check it later. With Evidence set, the SDK emits a CLC decision record (inputs frozen at the canonical boundaries, a digest over them, the verdict and its stable reason), optionally signed, wrapped in a DSSE envelope, through an EvidenceSink; refusals that never reach the language layer produce an admission record instead, and the proxy can report what the effect boundary observed. Recognised-but-unevaluated constraints stay visible as residual obligations — allow_unresolved, never silently allow. Details and the profiles that pin the shape: docs/evidence.md.

Examples

Example Shows
examples/mtls-backend mTLS + AIC, reverse proxy, identity headers, supervision/evidence demo
examples/bearer-jwt-backend the same service protected by Authorization: Bearer AIC-JWT
examples/mcp-server / mcp-behind-proxy AIC-gated MCP server, and one behind the proxy
examples/smoke-verify minimal server used by the smoke test and the quick start
examples/inspect-record read a decision record back and recompute its verdict
examples/supervision-demo approver + evidence exporter wiring used by the mTLS example

License

Apache-2.0.

Documentation

Overview

Package aic-verifier provides a drop-in Go SDK for HTTP services that need to enforce AIC (Authorization Identity Certificate) authorization, derived from varwof/gateway-core.

It exposes two integration styles with a shared admission pipeline:

  1. Middleware: wrap any http.Handler with aicverifier.AuthMiddleware, and the SDK authenticates every request (mTLS client certificate or a Bearer AIC-JWT) and runs the full AIC decision chain before your handler runs.
  2. Reverse proxy: aicverifier.Server listens on one address and forwards verified requests to a real backend API, injecting the verified client identity to the backend via X-AIC-* headers.

Both styles run the identical pipeline (CRL -> OCSP -> RBAC -> AIC -> constraints -> plugins) so a request admitted by one is admitted by the other.

Package gw provides the shared security engine for varwof gateways.

Capability plugin types (CapabilityPlugin, PluginContext, PluginResult, PluginDecision, HTTPFacts, PluginRegistry) are defined in the types module and re-exported here for backward compatibility.

Package gw provides the shared gateway core library for the varwof project.

Index

Constants

View Source
const (
	OCSPFallbackAllow = "allow"
	OCSPFallbackDeny  = "deny"
	OCSPFallbackCRL   = "crl"
)

OCSPFallbackAllow/Deny/CRL are OCSP fallback policy constants.

View Source
const (
	// OutcomeExecuted means the executor established that the effect occurred.
	OutcomeExecuted = "executed"
	// OutcomeFailed means it established the effect did not occur.
	OutcomeFailed = "failed"
	// OutcomeIndeterminate means it could not establish either.
	OutcomeIndeterminate = "indeterminate"
	// OutcomeObserved means a response was observed but the deployment has not
	// classified the effect (the honest default for a proxy that only saw HTTP).
	OutcomeObserved = "observed"
)

Recommended outcome tokens. The field is a plain string: the vocabulary belongs to the execution boundary (AEB), not to this SDK.

View Source
const (
	RoleAdmin  = "gateway:admin"
	RoleOps    = "gateway:ops"
	RoleAudit  = "gateway:audit"
	RoleDeploy = "gateway:deploy"
	RoleRead   = "gateway:read"
	RoleWild   = "gateway:*"
)

RoleAdmin/Ops/Audit/Deploy/Read/Wild are role constants.

View Source
const (
	SupervisionDecisionApproved = pki.SupervisionDecisionApproved
	SupervisionDecisionDenied   = pki.SupervisionDecisionDenied
	SupervisionDecisionPending  = pki.SupervisionDecisionPending
)

Supervision decisions referenced by SupervisionResult.Decision (aliases of the shared pki constants).

View Source
const AdmissionRecordPredicateType = "https://varwof.com/aic/v1/admission-record"

AdmissionRecordPredicateType identifies pipeline-level (pre-language) records.

View Source
const AdmissionRecordVersion = "AIC-ADMISSION-RECORD-v1"

AdmissionRecordVersion is the record revision.

View Source
const CLCRevision = semantics.CLCRevision

CLCRevision is the CLC language revision this SDK decides with.

View Source
const ConstraintAuditRequiredKey = "op:audit:required"

ConstraintAuditRequiredKey is the capabilityId for audit-required operation constraint.

View Source
const ConstraintCIDRKey = "network:cidr"

ConstraintCIDRKey is the capabilityId for IP network range constraint.

View Source
const ConstraintConcurrentKey = "session:max-concurrent"

ConstraintConcurrentKey is the capabilityId for max concurrent connections constraint.

View Source
const ConstraintGeoFenceKey = "geo-fence"

ConstraintGeoFenceKey is the capabilityId for geo fence constraint.

View Source
const ConstraintHardTimeoutKey = "session:hard-timeout"

ConstraintHardTimeoutKey is the capabilityId for session hard timeout constraint.

View Source
const ConstraintIdleTimeoutKey = "session:idle-timeout"

ConstraintIdleTimeoutKey is the capabilityId for session idle timeout constraint.

View Source
const ConstraintReadOnlyKey = "op:readonly"

ConstraintReadOnlyKey is the capabilityId for read-only operation constraint.

View Source
const ConstraintTimeWindowKey = "time:window"

ConstraintTimeWindowKey is the capabilityId for time window constraint.

View Source
const DefaultDAAgeMax = 30 * time.Second

DefaultDAAgeMax is the default value for the DelegationAuthorization.timestamp freshness window (the delegation-authorization validation flow: |now - timestamp| ≤ 30s).

View Source
const DefaultMaskRune = '*'

DefaultMaskRune is the character used to replace sensitive content.

View Source
const DefaultMaxBodyBytes = 1 << 20 // 1 MiB

DefaultMaxBodyBytes is the maximum request body the pipeline reads for capability plugin evaluation (bounded copy; the full body is restored before forwarding or calling the next handler).

View Source
const DefaultMaxChainLength = 8

DefaultMaxChainLength is the default upper bound for maximum delegation chain length (anti-certificate-bomb). Chains exceeding this are rejected (specification maxDepth is set by the top Principal; this serves as the gateway-side hard limit). Real-world Agent delegation depths are typically 2–3 levels; default 8 provides ample margin.

View Source
const HardTimeoutMax = 86400

HardTimeoutMax is the maximum value for session:hard-timeout (seconds).

View Source
const HardTimeoutMin = 60

HardTimeoutMin is the minimum value for session:hard-timeout (seconds).

View Source
const IdleTimeoutMax = 3600

IdleTimeoutMax is the maximum value for session:idle-timeout (seconds).

View Source
const IdleTimeoutMin = 30

IdleTimeoutMin is the minimum value for session:idle-timeout (seconds).

View Source
const MaxConcurrentMax = 1024

MaxConcurrentMax is the maximum value for the max parameter of the max-concurrent constraint.

View Source
const MaxConcurrentMin = 1

MaxConcurrentMin is the minimum value for the max parameter of the max-concurrent constraint.

View Source
const OfflineLifetimeLimit = time.Hour

OfflineLifetimeLimit is the maximum remaining certificate validity enforced in offline mode (G2(b): ≤1h).

View Source
const OutcomeRecordPredicateType = "https://varwof.com/aic/v1/outcome-record"

OutcomeRecordPredicateType identifies execution-boundary records.

View Source
const OutcomeRecordVersion = "AIC-OUTCOME-RECORD-v1"

OutcomeRecordVersion is the record revision.

View Source
const ProblemContentType = "application/problem+json"

ProblemContentType is the RFC 9457 media type.

View Source
const ProblemTypeEvidenceRequired = "https://varwof.com/clc/v1/problems/evidence-required"

ProblemTypeEvidenceRequired identifies this profile's problem type.

View Source
const RolePrefix = "gateway:"

RolePrefix is the gateway role OU prefix.

Variables

View Source
var (
	OIDSigECDSAWithSHA256  = pki.OIDSigECDSAWithSHA256
	OIDSigECDSAWithSHA384  = pki.OIDSigECDSAWithSHA384
	OIDSigECDSAWithSHA512  = pki.OIDSigECDSAWithSHA512
	OIDSigRSAWithSHA256    = pki.OIDSigRSAWithSHA256
	OIDSigRSAWithSHA384    = pki.OIDSigRSAWithSHA384
	OIDSigRSAWithSHA512    = pki.OIDSigRSAWithSHA512
	OIDSigRSAPSSWithSHA256 = pki.OIDSigRSAPSSWithSHA256
	OIDSigEd25519          = pki.OIDSigEd25519
	OIDSHA256              = pki.OIDSHA256
	OIDSHA384              = pki.OIDSHA384
	OIDSHA512              = pki.OIDSHA512
)

OID re-exports — signature algorithm OIDs.

View Source
var (
	AIAOID  = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 1}
	OCSPOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1}
)

AIAOID is the Authority Information Access method OID. OCSPOID is the OCSP responder OID.

View Source
var ErrRecordSignerMismatch = errors.New("aic-verifier: record signature does not verify against the pinned key")

ErrRecordSignerMismatch is returned when a record's signature does not verify against the key a deployment pinned via VerifyFnFromPublicKey.

SecureCipherSuites is the list of secure cipher suites (GCM/CHACHA).

View Source
var Version = "0.1.0"

Version is the package version, set via -ldflags -X github.com/varwof/gateway-core.Version=x.y.z.

Functions

func AICFingerprint

func AICFingerprint(cert *x509.Certificate) string

AICFingerprint computes the SHA-256 hex fingerprint of the AIC extension DER encoding in the certificate. Returns empty string if the certificate has no AIC extension (audit field uses omitempty, keeping old entries readable).

func ArchiveAuditFile

func ArchiveAuditFile(path string) error

ArchiveAuditFile archives (compress-rotates) the audit log file.

func AuditDuration

func AuditDuration(start time.Time, entry *AuditEntry)

AuditDuration computes and sets the duration of an audit entry.

func AuthorizeCapabilities

func AuthorizeCapabilities(caps []Capability, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeCapabilities decides a concrete operation against an explicit capability list.

func AuthorizeCapabilitiesWithConstraints

func AuthorizeCapabilitiesWithConstraints(caps, constraints []Capability, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeCapabilitiesWithConstraints decides an operation against a capability list whose grants also carry the connection's authorization constraints.

Constraints are what make the three-valued verdict interesting: the CLC core evaluates a few of them itself (max_rows), recognises the rest (time:window, network:cidr) and reports them as residual obligations rather than silently dropping them — so a caller that reads only "allow" fails closed.

func AuthorizeGrants

func AuthorizeGrants(grants []semantics.Grant, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeGrants decides opID/params against an explicit CLC-v1 grant set.

func AuthorizeOperation

func AuthorizeOperation(aic *AIC, pa *PrincipalAuthorization, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeOperation decides a concrete operation for an admitted connection.

The effective authority is the AIC capability set intersected with the PrincipalAuthorization grants when a PA is present (the P∩C model): the operation has to be authorized by both, so each set is decided and the results are combined — deny wins, and residual obligations union.

func BaseTLSConfig

func BaseTLSConfig(cipherSuites []string, minTLSVersion string) *tls.Config

BaseTLSConfig creates a base TLS configuration.

func BuildCipherSuites

func BuildCipherSuites(names []string) []uint16

BuildCipherSuites builds a cipher suite list from name strings.

func BuildSourceChain added in v0.2.0

func BuildSourceChain(clientCert *x509.Certificate, aic *AIC, userCert *x509.Certificate) (*semantics.SourceChain, error)

BuildSourceChain returns the authorization sources this admission can prove it rested on: the AIC (or plain leaf) certificate it verified, plus the principal certificate when the deployment configured one.

func CanonicalJSON

func CanonicalJSON(v any) ([]byte, error)

CanonicalJSON serializes v with keys sorted lexicographically at every level (map-key order guaranteed by encoding/json) and no extraneous whitespace.

func CheckAuthorizationConstraints

func CheckAuthorizationConstraints(constraints []Capability, clientIP string) error

CheckAuthorizationConstraints performs offline validation of authorizationConstraints. Supports: network:cidr (requires ClientIP), time:window, geo-fence (requires ClientIP), session:max-concurrent, session:hard-timeout, session:idle-timeout, op:readonly, op:audit:required. Unknown constraint types are ignored by default (forward compatible); the caller logs audit warnings; after registering a custom constraint executor (RegisterConstraint), it will be recognized and executed.

func CheckAuthorizationConstraintsAt

func CheckAuthorizationConstraintsAt(constraints []Capability, clientIP, timeHHMM string) error

CheckAuthorizationConstraintsAt is the same as CheckAuthorizationConstraints, but evaluates time-window constraints at a specified UTC time (HH:MM), useful for testing and offline decision demonstrations. When timeHHMM is empty, uses the current time. The tz field in time-window is converted to the corresponding timezone during evaluation.

func CheckDAFreshness

func CheckDAFreshness(ts time.Time, now time.Time, maxAge time.Duration) error

CheckDAFreshness validates that DelegationAuthorization.timestamp is within the freshness window. When now is nil, uses time.Now(); when maxAge <= 0, uses DefaultDAAgeMax. Zero-value timestamp (never set) is treated as expired.

func CheckDelegatedAgentCert

func CheckDelegatedAgentCert(cert *x509.Certificate) string

CheckDelegatedAgentCert validates the legitimacy of a Delegated-Agent certificate (for non-HTTP protocols like TCP). Returns empty string on success, non-empty rejection reason on failure.

A certificate that merely carries the "Delegated-Agent" OU is not legitimate on its own (finding 17): the OU is a plaintext subject attribute anyone can mint, so a Delegated-Agent cert must additionally be core-signed (carry a valid AIC extension) and be within its validity window. Certificates without the OU are not delegated-agent certs and pass through.

func CheckDelegatedAgentHeaders deprecated

func CheckDelegatedAgentHeaders(cert *x509.Certificate, r *http.Request) string

Deprecated: CheckDelegatedAgentHeaders validates the X-Agent-User/X-Agent-TTL headers of a Delegated-Agent certificate (B1 username delegation path). The username cannot be cryptographically bound to the certificate; identity propagation has been changed to B2 (X-Client-Cert-DER certificate passthrough). This function is kept for legacy client compatibility only. Certificates without Delegated-Agent OU pass through directly. Returns empty string on success, non-empty rejection reason on failure.

Security note (G4): The client's X-Agent-User / X-Agent-TTL headers are entirely controlled by the requestor and must never be trusted as the true identity of the proxied user. This function only performs "declarative" validation (prompting ops to configure the delegation channel). The real delegation identity is derived by DelegatedAgentServerIdentity from the core-signed AIC/GatewaySession, and the gateway overwrites X-Agent-User / X-Agent-TTL headers before forwarding.

func CheckRole

func CheckRole(roles []string, allowed []string) bool

CheckRole checks whether the role list contains an allowed role.

func ClientTLSConfig

func ClientTLSConfig(caCertFile, certFile, keyFile string, cipherSuites []string, minTLSVersion string) (*tls.Config, error)

ClientTLSConfig creates an mTLS client-side configuration.

func ConnectionConstraintEvaluator added in v0.2.0

func ConnectionConstraintEvaluator(clientIP string) func(op Operation, unresolved []string) bool

ConnectionConstraintEvaluator returns an UnresolvedEvaluator that discharges the obligations the connection-level constraint registry can evaluate for the given client IP (source CIDRs, time windows, and whatever else is registered).

It exists because the connection-level check and the language-level obligation are two halves of one rule: the language *declares* the constraint, and this evaluator *discharges* it. Passing it is an explicit act — a deployment that does not pass it keeps the §8.4 default, which is fail-closed — so the release is a declared policy rather than an implicit bypass. Anything the registry cannot evaluate (for example `max_rows`) is not discharged here: return false and let the caller decide.

func ConstraintRecheckLoop

func ConstraintRecheckLoop(aicConstraints, paConstraints []Capability, clientIP string, interval time.Duration, done <-chan struct{}, onViolation func(reason string))

ConstraintRecheckLoop periodically re-evaluates authorizationConstraints (G3: constraint timing consistency).

Constraints on long-lived data plane connections like TCP are only checked once at handshake; time-window / revocation constraints that expire over time cease to be effective after crossing the window — for example, a "weekdays 9-18 only" connection established during the night window remains active during the day. This function re-evaluates the authorizationConstraints of both AIC and PrincipalAuthorization at the given interval (using the current time), calling onViolation when a constraint is no longer met (gateway disconnects and audits accordingly). Stops when done is closed (idempotent).

Any single constraint evaluation failure is treated as a violation; a and pa may both be nil/empty (skipping the corresponding set). Callers should ensure onViolation is non-nil (nil internally only logs, no actual action).

func ConstraintStrings

func ConstraintStrings(constraints []Capability) []string

ConstraintStrings renders authorization constraints in CLC form: <scheme>:<type>[:<params-json>], e.g. varwof/constraint-v1:network:cidr:["192.0.2.0/24"].

func DAHash

func DAHash(cert *x509.Certificate) string

DAHash computes the SHA-256 hex hash of the DelegationAuthorization signatureValue in the AIC (authorization evidence fingerprint). Returns empty string if AIC is missing or has no DA signature.

func DAHashFromAIC

func DAHashFromAIC(aic *AIC) string

DAHashFromAIC computes the SHA-256 hash of the DelegationAuthorization signatureValue for a parsed AIC. Returns empty string if no DA signature is present.

func DecodeBase64

func DecodeBase64(s string) ([]byte, error)

DecodeBase64 performs Base64 decoding.

func DelegatedAgentServerIdentity

func DelegatedAgentServerIdentity(cert *x509.Certificate, principal string) (user string, expiry time.Time, reason string)

DelegatedAgentServerIdentity derives the server-asserted delegation identity from the core-signed certificate extension (AIC), preventing G4 identity spoofing: never trust client-supplied plaintext headers. Returns:

  • user: the server-asserted proxied subject (from AIC.PrincipalUid or cert CN/OU fallback)
  • expiry: delegation validity deadline (zero time.Time{} when no hard-timeout constraint)
  • reason: non-empty rejection reason (illegal conditions other than missing Delegated-Agent OU)

func EffectiveDelegationCapabilities

func EffectiveDelegationCapabilities(chain []*x509.Certificate, principalCaps []pki.Capability, maxChainLen int) ([]pki.Capability, error)

EffectiveDelegationCapabilities validates per-level capability subsets and computes the intersection along the delegation chain.

chain goes top-down: chain[0]=topmost delegated Agent, chain[len-1]=bottom Agent. principalCaps is the effective capabilities P of the original principal (top Principal). Returns C_eff = P ∩ C_1 ∩ … ∩ C_n.

Process: eff = P; for each level C_i, first verify C_i ⊆ eff (permissions only decrease, escalation is rejected), then eff = filterCovered(C_i, eff) (retain this level's capabilities that are authorized by eff).

Note: This function only performs capability semantic validation; signature verification is handled separately by Verify.

func EffectiveDelegationCapabilitiesFromAIC

func EffectiveDelegationCapabilitiesFromAIC(chain []*x509.Certificate, topPrincipal *x509.Certificate, maxChainLen int) ([]pki.Capability, error)

EffectiveDelegationCapabilitiesFromAIC is a convenience entry point: extracts AIC.capabilities from the top Principal certificate as P, then recursively computes the intersection. Chain signature verification is performed separately by the caller.

func EncodeBase64

func EncodeBase64(data []byte) string

EncodeBase64 performs Base64 encoding.

func ExtractOCSPURL

func ExtractOCSPURL(cert *x509.Certificate) string

ExtractOCSPURL extracts the OCSP URL from the certificate's AIA extension.

func ExtractPolicyRoles

func ExtractPolicyRoles(cert *x509.Certificate) []string

ExtractPolicyRoles extracts policy roles from the certificate OU. Uses the global authorization policy's (if set) OU→role mapping to resolve role names; when no policy is set, falls back to the hardcoded ExtractRoles (only identifies gateway: prefix OUs). Returned role names include both policy role names and original gateway:* OUs (if present), for compatibility with both configuration styles.

func ExtractRoles

func ExtractRoles(cert *x509.Certificate) []string

ExtractRoles extracts the gateway role list from the certificate OU.

func ExtractSPIFFEIDFromCert

func ExtractSPIFFEIDFromCert(cert *x509.Certificate) string

ExtractSPIFFEIDFromCert extracts a SPIFFE ID from a certificate's SAN URIs. Returns "" if no SPIFFE URI is found.

func FetchOCSPResponseRaw

func FetchOCSPResponseRaw(cert, issuer *x509.Certificate, ocspURL string) ([]byte, error)

FetchOCSPResponseRaw fetches the raw OCSP response bytes.

func FilterAuditFile

func FilterAuditFile(file string, since time.Time, action string, cn, serial, mapping string) error

FilterAuditFile filters the audit file by conditions and prints matching entries.

func FindStartOffsetByTime

func FindStartOffsetByTime(file string, target time.Time) (int64, error)

FindStartOffsetByTime binary-searches for the file offset corresponding to a given time.

func HasAIC

func HasAIC(cert *x509.Certificate) bool

HasAIC reports whether the certificate carries a valid AIC extension (G2: short-lived certificate identification). Malformed AIC returns false (such certificates are denied in the admission pipeline and will not enter the data plane).

func HasDelegatedAgentOU

func HasDelegatedAgentOU(cert *x509.Certificate) bool

HasDelegatedAgentOU is the exported wrapper of hasDelegatedAgentOU, for gateways to check delegation identity before forwarding.

func HashLeaf

func HashLeaf(data []byte) []byte

HashLeaf computes the SHA256 hash of a Merkle tree leaf node. Finding 22: the leaf is domain-separated (prefix 0x00) from internal node hashes so a leaf hash can never be presented as an internal node hash or vice versa.

func HashNode

func HashNode(left, right []byte) []byte

HashNode computes the SHA256 hash of a Merkle tree internal node. Finding 22: internal nodes use a distinct domain prefix (0x01) so lone-leaf roots (HashLeaf, 0x00) cannot collide with node hashes.

func IsAdminOU

func IsAdminOU(ou string) bool

IsAdminOU checks whether the OU is an admin role (compatible with gateway:admin and bare admin).

func LoadCA

func LoadCA(caCertFile string) (*x509.CertPool, error)

LoadCA loads a CA certificate pool and validates that every certificate in the bundle is a CA.

func LoadCACert

func LoadCACert(caCertFile string) (*x509.Certificate, error)

LoadCACert loads and parses a single CA certificate (first PEM block).

func LoadCAFromFile

func LoadCAFromFile(path string) (*x509.CertPool, error)

LoadCAFromFile loads a PEM CA chain into a CertPool.

func LoadCert

func LoadCert(certFile, keyFile string) (*tls.Certificate, error)

LoadCert loads a TLS certificate key pair.

func LoadEvidenceRecord added in v0.2.0

func LoadEvidenceRecord(path string) (*semantics.DecisionRecord, error)

LoadEvidenceRecord reads a DSSE envelope written by FileSink and returns the decision record it carries, checking both the envelope (structure, subject binding) and the record (re-computation) on the way. It is the bridge from the emission side to the reporting side: an EvidenceBundle gets its authority from this record.

func LoadGatewayPolicy

func LoadGatewayPolicy(policyPath, sigSuffix string, opts *PolicyVerifyOptions, require bool) error

LoadGatewayPolicy loads and sets the gateway authorization policy. If authorization_file is configured, loads it (with signature verification); opts=nil skips signature verification. On successful load, sets the global policy via SetAuthorizationPolicy. On failure, if require=true returns an error; otherwise degrades by keeping the existing policy.

func LogAdmission

func LogAdmission(result AdmissionResult, clientIP string, logger *slog.Logger)

LogAdmission records the admission decision log.

func LogPluginDecision

func LogPluginDecision(logger *AuditLogger, entry PluginAuditEntry)

LogPluginDecision writes a plugin decision event to the audit log. Deny and execution errors are logged as WARN, allow as INFO. When entry.Level is empty, it is inferred from Decision ("allow"→INFO, others→WARN).

func MTLSServerConfig

func MTLSServerConfig(caCertFile string, cert *tls.Certificate, cipherSuites []string, minTLSVersion string) (*tls.Config, error)

MTLSServerConfig creates an mTLS server-side configuration.

func MarshalTSARequest

func MarshalTSARequest(req TimeStampReq) ([]byte, error)

MarshalTSARequest serializes a TSA request to DER.

func MaskCertSerial

func MaskCertSerial(serial string) string

MaskCertSerial masks a certificate serial number, keeping only the last 4 hex chars. Example: "A1:B2:C3:D4:E5:F6" → "**********E5:F6"

func MaskEmail

func MaskEmail(email string) string

MaskEmail masks an email address, keeping domain visible. Example: "alice@example.com" → "a***e@example.com"

func MaskFilePath

func MaskFilePath(path string) string

MaskFilePath masks the filename portion of a path, keeping the directory. Example: "/etc/pki/certs/secret.pem" → "/etc/pki/certs/********.pem"

func MaskString

func MaskString(s string, visible int) string

MaskString replaces all but the last `visible` characters with the mask rune. If the input is shorter than visible, it returns the input unchanged. If visible is 0, the entire string is masked. Returns empty string unchanged.

func MaskToken

func MaskToken(token string) string

MaskToken masks an API token or key, keeping only first and last 4 chars for long tokens, and proportionally fewer for short tokens so that at least half of the token is always masked (finding 21: a 9-char token must not reveal 8 chars). Example: "sk-abc123def456ghi789" → "sk-a*******i789"

func MatchCapability

func MatchCapability(id, pattern string) bool

MatchCapability checks whether a capability matches a pattern (supports * and a:b:* prefix).

func MatchCapabilityPriority

func MatchCapabilityPriority(id, pattern string) int

MatchCapabilityPriority checks whether id matches pattern and returns a five-level priority (pki.MatchPriorityExact .. MatchPriorityGlobal, 0 means no match). Semantics: capabilityId is segmented by ':', '*' matches a single segment, '**' crosses segments, priority: exact(5) > single-segment(4) > multi-segment(3) > scheme(2) > global(1).

func MatchCapabilityRules

func MatchCapabilityRules(id string, rules []pki.CapabilityRule) pki.CapabilityRuleMatch

MatchCapabilityRules makes a decision within a rule set (allow + deny) by priority: takes the highest-priority matching rule; at equal priority, deny takes precedence over allow. Returns Matched=false when no rule matches.

func NeedRevoke

func NeedRevoke(cert *x509.Certificate) bool

NeedRevoke checks whether a certificate needs proactive revocation.

func NewAdmissionEnvelope added in v0.2.0

func NewAdmissionEnvelope(rec AdmissionRecord) (semantics.Envelope, error)

NewAdmissionEnvelope wraps the record in the same DSSE/in-toto envelope the CLC records use, with a pipeline-level predicate type. A consumer that already speaks the envelope needs no new transport, only this predicate.

The statement's subjects are the facts the refusal rested on (client certificate DER, AIC extension, ...), so the envelope is bound to the same material an in-toto consumer would expect — matched purely by digest.

func NewOutcomeEnvelope added in v0.2.0

func NewOutcomeEnvelope(rec OutcomeRecord) (semantics.Envelope, error)

NewOutcomeEnvelope wraps the record in the same DSSE/in-toto envelope used by the decision and admission records; only the predicate type differs.

func NewReplayNonceStore

func NewReplayNonceStore(ttl time.Duration, max int) *memReplayStore

NewReplayNonceStore returns a process-local replay-protection store. Nonces are retained for ttl (default 24h) and at most max entries (default 4096); beyond that the oldest are evicted. The store is intended for single-node gateways; multi-node deployments should share a distributed nonce store instead.

func NormalizeSerial

func NormalizeSerial(serial *big.Int) string

NormalizeSerial converts a certificate serial number to standard hex format (uppercase, no 0x prefix, zero-padded to 40 characters).

func OfflineLifetimeFor

func OfflineLifetimeFor(ocspFallback string) time.Duration

OfflineLifetimeFor returns the enforced offline limit based on the OCSP fallback policy. OCSPFallbackAllow (fail-open) and OCSPFallbackCRL (crl data can lag a recent revocation) → 1h cap; deny/disabled → 0 (not enforced; deny is fail-closed). Called by the gateway when constructing PipelineConfig.OfflineMaxCertLifetime.

func ParseCertPEM

func ParseCertPEM(data []byte) (*x509.Certificate, error)

ParseCertPEM parses the first certificate from PEM bytes.

func ParseCertPEMFile

func ParseCertPEMFile(path string) (*x509.Certificate, error)

ParseCertPEMFile reads and parses a PEM certificate file.

func ParsePrivateKeyPEM

func ParsePrivateKeyPEM(data []byte) (crypto.Signer, error)

ParsePrivateKeyPEM parses a PEM private key (PKCS#1/PKCS#8/EC/RSA).

func ParsePrivateKeyPEMFile

func ParsePrivateKeyPEMFile(path string) (crypto.Signer, error)

ParsePrivateKeyPEMFile reads and parses a PEM private key file.

func PeerCertRoles

func PeerCertRoles(r *http.Request) []string

PeerCertRoles extracts roles from the first peer certificate in the request's TLS connection. Returns nil if the request has no TLS or no peer certs.

func ProfileSubjectDigest added in v0.2.0

func ProfileSubjectDigest(env semantics.Envelope) (semantics.Digest, bool, error)

ProfileSubjectDigest returns the digest a consumer should look for when it wants to know which evidence shape a statement carries.

func RecorderSubjectDigest added in v0.2.0

func RecorderSubjectDigest(env semantics.Envelope) (semantics.Digest, bool, error)

RecorderSubjectDigest returns the digest of the recorder descriptor that emitted this envelope, when it carries one.

func RegisterConstraint

func RegisterConstraint(ev ConstraintEvaluator) error

RegisterConstraint registers a constraint evaluator in the global registry (extension point).

func RegisterGeoResolver

func RegisterGeoResolver(name string, fn GeoResolver)

RegisterGeoResolver registers a custom geographic resolver (extension point) for use in the geo-fence resolver mode (e.g. third-party geographic databases like ip2region).

func RegisterParameterValidator

func RegisterParameterValidator(v ParameterValidator) error

RegisterParameterValidator registers a validator in the built-in parameter boundary validator registry.

func RegisterPlugin

func RegisterPlugin(p CapabilityPlugin) error

RegisterPlugin registers a plugin in the global registry.

func ReplaceConstraint

func ReplaceConstraint(ev ConstraintEvaluator) error

ReplaceConstraint replaces an evaluator in the global registry (hot update extension point).

func RequireRoles

func RequireRoles(r *http.Request, allowedRoles []string) bool

RequireRoles checks whether the mTLS peer certificate in the request carries at least one of the given allowedRoles. It is a convenience wrapper around PeerCertRoles + CheckRole for HTTP handler middleware.

func ResetConstraints

func ResetConstraints()

ResetConstraints clears the global registry and re-registers built-in types (for testing only).

func ResetParameterValidators

func ResetParameterValidators()

ResetParameterValidators clears the built-in parameter boundary validator registry (testing only).

func ResetPlugins

func ResetPlugins()

ResetPlugins clears the global registry (testing only).

func SanitizeString

func SanitizeString(s string) string

SanitizeString removes non-printable characters and trims whitespace. Useful for sanitizing user input before logging.

func ServerTLSConfig

func ServerTLSConfig(cert *tls.Certificate, cipherSuites []string, minTLSVersion string) *tls.Config

ServerTLSConfig creates a server-side TLS configuration.

func SetAuthorizationPolicy

func SetAuthorizationPolicy(p *AuthorizationPolicy)

SetAuthorizationPolicy sets the global authorization policy.

func SetGlobalCapabilityRegistry

func SetGlobalCapabilityRegistry(cr CapabilityRegistry)

SetGlobalCapabilityRegistry sets the package-level default capability registry. Passing nil clears it (disables capability registration validation).

func SetIdentityHeaderMode

func SetIdentityHeaderMode(r *http.Request, mode IdentityHeaderMode)

SetIdentityHeaderMode is kept for the per-request (middleware) integration style. Security note: callers must only pass an identity mode derived from server configuration, never from a client-supplied header value; the reverse proxy reads its mode from Config.IdentityMode so clients cannot influence how much identity is disclosed to backends.

func SignPolicy

func SignPolicy(policyData []byte, cert *x509.Certificate, signer crypto.Signer) ([]byte, error)

SignPolicy uses an admin identity to create a PKCS#7 detached signature (SHA-256) for policy data, returning .sig DER. signer must be a crypto.Signer (supports RSA/ECDSA/Ed25519).

func SignerHasAdminOU

func SignerHasAdminOU(cert *x509.Certificate) bool

SignerHasAdminOU checks whether the signer certificate carries the admin OU.

func StartOCSPStapling

func StartOCSPStapling(tlsCert *tls.Certificate, cfg *tls.Config, caCertFile string, stopCh <-chan struct{}, translator Translator, lang string)

StartOCSPStapling starts the OCSP stapling background refresh.

func SubjectDigestByName added in v0.2.0

func SubjectDigestByName(env semantics.Envelope, name string) (semantics.Digest, bool, error)

SubjectDigestByName reads a named subject digest back out of an envelope, for the CLC statement and for the pipeline/outcome statements alike.

func SynthesizeCertFromJWT

func SynthesizeCertFromJWT(outer *aicjwt.OuterClaims) (*x509.Certificate, error)

SynthesizeCertFromJWT builds an X.509 certificate carrying the AIC claims of an AIC-JWT, so downstream certificate-based pipeline stages (CheckAdmission, capability matching, audit) work unchanged.

func TLSVersionFromString

func TLSVersionFromString(s string) uint16

TLSVersionFromString converts a version string to a TLS version number.

func ToGrant

func ToGrant(c Capability) (semantics.Grant, error)

ToGrant converts a declared capability (AIC or PrincipalAuthorization) into a CLC-v1 grant. The identifier grammar and params shape are validated, so a declaration that cannot be decided is reported instead of silently ignored.

func ToGrantSet

func ToGrantSet(caps []Capability) ([]semantics.Grant, error)

ToGrantSet converts a capability list into a CLC-v1 grant set.

func ValidateAIC

func ValidateAIC(aic *AIC) error

ValidateAIC delegates to pki-types.

func VerifyAuditEntry

func VerifyAuditEntry(data []byte, tsaClient *TSAClient) error

VerifyAuditEntry verifies the TSA timestamp signature of an audit entry.

func VerifyBundle

func VerifyBundle(bundle *CredentialBundle, roots *x509.CertPool) error

VerifyBundle verifies the credential bundle dual chain:

  • Agent chain → trust root (default client authentication EKU);
  • Principal chain → same trust root;
  • keyHash match: AIC.PrincipalUid.KeyHash == SHA256(Principal SPKI).

roots is the trust root pool; if empty, falls back to bundle.CACerts. Returns an error if any verification step fails.

func VerifyDelegationAuth

func VerifyDelegationAuth(aic *AIC, userCert *x509.Certificate) error

VerifyDelegationAuth verifies the validity of a DelegationAuthorization signature. aic must contain a non-empty DelegationAuthorization; userCert is the authorizing user's certificate. The signed content is the DelegationAuthTBS DER encoding (a specific subset, not the entire AIC).

func VerifyDelegationChain

func VerifyDelegationChain(chain []*x509.Certificate, topPrincipal *x509.Certificate, maxDepth int) error

VerifyDelegationChain is a convenience entry point: creates a default verifier and verifies the chain. chain goes from top to bottom: chain[0]=top-level delegating Agent, chain[len-1]=bottom-level Agent. maxDepth is set by the top Principal.

func VerifyDelegationChainWithCaps

func VerifyDelegationChainWithCaps(chain []*x509.Certificate, topPrincipal *x509.Certificate, principalCaps []pki.Capability, maxDepth, maxChainLen int) ([]pki.Capability, error)

VerifyDelegationChainWithCaps verifies a multi-level delegation chain and computes the effective capability intersection:

  • Basic signature verification (bottom-up per level) and depth limits;
  • Anti-cycle (serial number deduplication within chain);
  • Anti-certificate-bomb (chain length upper bound);
  • Per-level capability subset validation + C_eff recursive intersection.

maxDepth is set by the top Principal (≤0 means no limit); maxChainLen is the gateway-side hard upper bound (≤0 uses DefaultMaxChainLength). Returns C_eff = P ∩ C_1 ∩ … ∩ C_n.

func VerifyFnFromPublicKey added in v0.2.0

func VerifyFnFromPublicKey(pub *ecdsa.PublicKey) func(keyID string, pae, sig []byte) error

VerifyFnFromPublicKey returns the DSSE verification callback that VerifyEvidenceDir / VerifyEvidenceEnvelope expect, pinned to one trusted ECDSA public key (P-256 / P-384 / P-521). It closes the "who trusts the record signer" question with one line instead of hand-rolled crypto per call site: the key passed here is the trust anchor, the unauthenticated keyid label is deliberately ignored.

Both the raw r‖s form (the common DSSE/JSON-Sign convention, P-256 = 64 bytes) and ASN.1 DER are accepted, over the SHA-256 of the DSSE PAE.

func VerifyLayer2

func VerifyLayer2(chain []*x509.Certificate, cfg *PipelineConfig, roles []string) (AdmissionResult, *Layer2Result)

VerifyLayer2 performs Layer 2 representation verification: AIC parsing + PA parsing + delegation representation check (completed within CheckAdmission). Returns AdmissionResult and Layer2Result.

func VerifyPrincipalKeyHash

func VerifyPrincipalKeyHash(agent, principal *x509.Certificate) error

VerifyPrincipalKeyHash verifies that AIC.PrincipalUid.KeyHash matches the SHA-256 of the principal certificate's SPKI. Returns an error (fail-close) if keyHash is missing or empty.

func VerifyProof

func VerifyProof(leaf []byte, proof []ProofStep, root []byte) bool

VerifyProof verifies a Merkle audit proof.

func VerifyProofBounded

func VerifyProofBounded(leaf []byte, proof []ProofStep, root []byte, maxProofLen int) bool

VerifyProofBounded is like VerifyProof but enforces a maximum proof length (finding 22): a proof for a tree with n leaves has at most ceil(log2 n) steps, so any longer proof is rejected regardless of hash match.

func VerifySPIFFESAN

func VerifySPIFFESAN(cert *x509.Certificate, expectedID string) bool

VerifySPIFFESAN validates that a certificate carries the expected SPIFFE ID in its SAN URIs.

func VerifySignedPolicy

func VerifySignedPolicy(sigDER, policyData []byte, roots *x509.CertPool, requireAdminOU bool) (*x509.Certificate, error)

VerifySignedPolicy verifies a PKCS#7 detached signature (policyData is the raw policy bytes). Returns the signer certificate on success, for further OU/status checks by the caller.

Types

type AIC

type AIC = pki.AIC

── Type aliases ──

func ParseAIC

func ParseAIC(cert *x509.Certificate) (*AIC, error)

ParseAIC delegates to pki-types.

type AdmissionConfig

type AdmissionConfig struct {
	// RequireAIC when set to true rejects connections without AIC extension.
	RequireAIC bool
	// RequiredProtocol requires the Agent to have the specified protocol capability (empty = no check).
	RequiredProtocol string
	// RequiredRuleId requires the Agent to have the specified CapabilityId permission (empty = no check).
	RequiredRuleId string
	// RequiredCapabilities requires the Agent to have all specified CapabilityIds (empty = no check).
	RequiredCapabilities []string
	// Operations are the concrete actions (capability id + parameters) this
	// request wants to perform.  When set, each is decided with the CLC core
	// against the effective authority — the AIC capabilities intersected with
	// the PrincipalAuthorization grants — so parameter bounds take part in the
	// decision instead of matching capability ids alone.
	// Operation decisions are recorded in AdmissionResult.OperationDecisions so
	// upstream can surface verdict / reason / unresolved to downstream.
	Operations []Operation
	// UnresolvedEvaluator is the §8.4 residual-obligation release hook.  When
	// an operation's CLC decision lands on allow_unresolved (recognized but
	// not-evaluated constraint, e.g. time:window), the default is fail-closed
	// deny.  This evaluator can confirm those obligations out-of-band: returning
	// true releases the operation — the call site must still honor the residual
	// obligations (semantics.Decision.Unresolved) at runtime.  nil (default)
	// keeps fail-closed deny.
	UnresolvedEvaluator func(op Operation, unresolved []string) bool
	// DischargeObligations enables the strict consumer-side obligation rule of
	// CLC §8.4 with XACML 3.0 §2.13/§7.2.1: before an allow_unresolved decision
	// can be released, every obligation identity must be one this deployment
	// understands and can and will discharge (ObligationsUnderstood).  An
	// obligation it does not understand fails closed with obligation_unknown
	// instead of being handed to UnresolvedEvaluator as opaque text.  false
	// (default) keeps the legacy path, where UnresolvedEvaluator alone decides.
	DischargeObligations bool
	// ObligationsUnderstood lists the obligation (scheme,type) identities this
	// deployment understands and can and will discharge, e.g.
	// "varwof/constraint-v1:time".  Only consulted when DischargeObligations is
	// true.
	ObligationsUnderstood []string
	// RequireFreshDecisionContext requires the deployment to pin a freshness
	// context (RATS §10: explicit clock, nonce, or epoch id) and requires it to
	// still be fresh when an operation is allowed.  A missing, stale, or
	// future-dated context denies with a stable reason code; false (default)
	// keeps the pre-context behaviour.
	RequireFreshDecisionContext bool
	// DecisionContext is the pinned freshness input (semantics.DecisionContext).
	// Required when RequireFreshDecisionContext is set.
	DecisionContext *semantics.DecisionContext
	// DisallowRepresentative when set to true rejects DelegationRepresentative mode connections.
	DisallowRepresentative bool
	// RequireUserPermission when set to true rejects connections without UserPermission extension.
	RequireUserPermission bool
	// RejectOverflow when set to true rejects AIC containing CapabilityIds not authorized by UserPermission.
	RejectOverflow bool
	// RequireUserAuth when set to true requires DelegationAuthorization signature verification in the AIC.
	RequireUserAuth bool
	// EnforceCapSizeConstraints when set to true validates Capability field lengths (schemeId 1-128, capabilityId 1-256, parameters 0-4096).
	EnforceCapSizeConstraints bool
	// NonceCache is used for DelegationAuthorization nonce replay protection.
	// nil means skip nonce replay check (not recommended).
	NonceCache *NonceCache
	// EnforceSize32 when set to true validates Nonce is exactly 32 bytes.
	EnforceSize32 bool
	// UserCert is the authorized user's certificate for verifying DelegationAuthorization signatures.
	// When nil, if UserCertResolver is not nil, resolves via KeyHash from varwof-core;
	// otherwise falls back to the connection peer certificate (only agent == user can verify).
	UserCert *x509.Certificate
	// UserCertResolver resolves user certificates based on PrincipalUid.KeyHash (via varwof-core API).
	// Automatically called when UserCert is nil and RequireUserAuth is true.
	UserCertResolver func(keyHash []byte) (*x509.Certificate, error)
	// EnforceConstraints when set to true enforces authorizationConstraints (CIDR/time window/concurrency).
	EnforceConstraints bool
	// StrictConstraints when set to true directly rejects connections with unregistered constraint types
	// in authorizationConstraints (unknown capabilityId under constraint/constraint-v1 scheme),
	// fail-closed. Default false only logs audit warnings for unknown constraints and ignores them
	// (forward compatible, strict mode).
	StrictConstraints bool
	// ClientIP is used for authorizationConstraints allowed-cidr checks.
	ClientIP string
	// AuditLogger is used for authorization decision audit logging. When non-nil, logs warnings
	// for unknown constraint types, etc.
	AuditLogger *AuditLogger
	// CheckDAAge when set to true validates DelegationAuthorization.timestamp freshness
	// (|now - timestamp| ≤ DAAgeMax). Default off — specification delegates lifecycle validation
	// to X.509 NotAfter (dev-docs/aic/06-delegation-auth.md §validation flow (gateway runtime));
	// deployments requiring stricter time window defense can enable this.
	CheckDAAge bool
	// DAAgeMax is the DA timestamp freshness window (|now - timestamp| ≤ DAAgeMax).
	// Only effective when CheckDAAge=true; <=0 uses DefaultDAAgeMax (30 seconds).
	DAAgeMax time.Duration
	// CredentialBundle is the client-submitted credential bundle (agent, principal and CA chains).
	// When RequireUserAuth is true and UserCert is nil, prioritizes the Principal certificate
	// from the credential bundle for DA signature verification (including keyHash cross-validation);
	// falls back to UserCertResolver when missing.
	CredentialBundle *CredentialBundle
}

AdmissionConfig is the configuration for the admission engine.

func (AdmissionConfig) Validate

func (c AdmissionConfig) Validate() error

Validate checks the configuration for conflicting options.

type AdmissionFact added in v0.2.0

type AdmissionFact struct {
	Type   string           `json:"type"`
	Digest semantics.Digest `json:"digest"`
	Note   string           `json:"note,omitempty"`
}

AdmissionFact is one content-addressed fact that decided the outcome: the client certificate, the AIC extension, the requested operations, and so on. Only digests travel here — the material stays where it was verified.

type AdmissionIdentity added in v0.2.0

type AdmissionIdentity struct {
	Principal string `json:"principal,omitempty"`
	AgentID   string `json:"agentId,omitempty"`
	Serial    string `json:"serial,omitempty"`
	SPIFFEID  string `json:"spiffeId,omitempty"`
}

AdmissionIdentity is who the request claimed to be, as far as the pipeline got.

type AdmissionRecord added in v0.2.0

type AdmissionRecord struct {
	Ver        string            `json:"ver"`
	Stage      string            `json:"stage"`
	Outcome    string            `json:"outcome"` // refused
	Reason     string            `json:"reason,omitempty"`
	RecorderID string            `json:"recorderId,omitempty"`
	At         time.Time         `json:"at"`
	Identity   AdmissionIdentity `json:"identity"`
	Facts      []AdmissionFact   `json:"facts,omitempty"`
}

AdmissionRecord says what the pipeline decided before (or instead of) reaching the language layer, and why. It never claims a CLC verdict.

func NewAdmissionRecord added in v0.2.0

func NewAdmissionRecord(ctx EvidenceContext, err *AuthError, facts []AdmissionFact) AdmissionRecord

NewAdmissionRecord builds a pipeline-level record for a refusal.

func ParseAdmissionEnvelope added in v0.2.0

func ParseAdmissionEnvelope(env semantics.Envelope) (AdmissionRecord, error)

ParseAdmissionEnvelope decodes and structurally checks a pipeline-level record.

func (AdmissionRecord) Digest added in v0.2.0

func (r AdmissionRecord) Digest() (semantics.Digest, error)

Digest identifies the record (over its canonical JSON).

type AdmissionResult

type AdmissionResult struct {
	Decision               DecisionResult
	Reason                 string
	AIC                    *AIC
	PrincipalAuthorization *PrincipalAuthorization
	PrincipalUid           string
	// EffectiveCaps is the P∩C (AIC declarations ∩ PA grants) intersection result,
	// preserving full Capability (including SchemeId/Parameters). When no PA is present,
	// equals the full AIC declarations. Phase two plugin evaluation only acts on this set
	// — declarations outside the intersection (including unrelated schemes) do not participate
	// in decisions or block connections (operation-level mapping).
	EffectiveCaps []Capability
	// OperationDecisions records the per-operation CLC verdicts when
	// cfg.Operations was set.  Populated on the allow path (and kept on the
	// deny path for the operation that caused it) so upstream can surface
	// verdict / reason / unresolved to downstream.
	OperationDecisions []OperationDecision
	// Sources is the authorization chain this admission can prove it rested on
	// (CLC SourceChain): the verified leaf certificate, plus the principal
	// certificate when configured.  Nil when no certificate was presented.
	Sources *semantics.SourceChain
}

AdmissionResult contains the complete admission check result.

func CheckAdmission

func CheckAdmission(cert *x509.Certificate, cfg AdmissionConfig) AdmissionResult
  1. Parse GatewaySession extension
  2. Verify AgentType is in the allowed list
  3. Check protocol capability match
  4. Check RuleId permission
  5. Check required capability subset
  6. Check delegation mode
  7. Parse UserPermission extension
  8. Check merge with UserPermission

Returns AdmissionResult; caller decides whether to allow based on the Decision field.

type AlgorithmIdentifier

type AlgorithmIdentifier = pki.AlgorithmIdentifier

── Type aliases ──

type ApprovalRequester

type ApprovalRequester interface {
	// Request asks a human to approve (or deny) the operation described by
	// risk.  A nil result or a denied/pending decision denies the request.
	Request(ctx context.Context, risk RiskAssessment) (*SupervisionResult, error)
}

ApprovalRequester is the mid-operation (runtime) human approval hook. It is invoked when the admission decision path decides a request needs on-site human approval. The default (nil) fails closed: the request is denied with approval_required.

type AuditAction

type AuditAction string

AuditAction represents the action type of an audit event.

const (
	// ActionConnected indicates the client has connected.
	ActionConnected AuditAction = "connected"
	// ActionDisconnected indicates the client has disconnected.
	ActionDisconnected AuditAction = "disconnected"
	// ActionDenied indicates the connection was rejected.
	ActionDenied AuditAction = "denied"
	// ActionRevoked indicates the certificate has been revoked.
	ActionRevoked AuditAction = "revoked"
	// ActionProxied indicates the proxy forwarding has been established.
	ActionProxied AuditAction = "proxied"
	// ActionCompleted indicates the proxy forwarding has completed.
	ActionCompleted AuditAction = "completed"
	// ActionNoRoute indicates no matching route was found.
	ActionNoRoute AuditAction = "no_route"
	// ActionWSConnect indicates a WebSocket has been connected.
	ActionWSConnect AuditAction = "ws_connect"
	// ActionWSClose indicates a WebSocket has been closed.
	ActionWSClose AuditAction = "ws_close"
	// ActionPluginDecision indicates a plugin decision has been executed.
	ActionPluginDecision AuditAction = "plugin_decision"
	// ActionUnknownConstraint indicates an unknown constraint type was ignored (forward compatibility).
	ActionUnknownConstraint AuditAction = "unknown_constraint"
)

type AuditChain

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

AuditChain manages a sequence of Merkle tree batches for audit trail integrity.

func NewAuditChain

func NewAuditChain(batchSize int, onSeal func(root []byte)) *AuditChain

NewAuditChain creates an audit chain.

func (*AuditChain) BatchCount

func (c *AuditChain) BatchCount() int

BatchCount returns the number of sealed batches.

func (*AuditChain) Dump

func (c *AuditChain) Dump() string

Dump exports a text summary of all batches.

func (*AuditChain) GetTree

func (c *AuditChain) GetTree(batchNumber int) *SealedTree

GetTree returns the sealed tree for a given batch number.

func (*AuditChain) LatestRoot

func (c *AuditChain) LatestRoot() string

LatestRoot returns the root hash (hex) of the most recent batch.

func (*AuditChain) LatestRootBytes

func (c *AuditChain) LatestRootBytes() []byte

LatestRootBytes returns the root hash (raw bytes) of the most recent batch.

func (*AuditChain) Seal

func (c *AuditChain) Seal(entries [][]byte, previousRoot string) *SealedTree

Seal seals a batch of audit entries into a Merkle tree.

func (*AuditChain) SealChecked

func (c *AuditChain) SealChecked(entries [][]byte, previousRoot string) (*SealedTree, error)

SealChecked seals a batch like Seal but enforces chain continuity (finding 8): when the chain is non-empty, previousRoot must equal the latest root; passing an empty previousRoot for a non-empty chain is refused. Tamper-evidence is only meaningful if every batch links to its predecessor.

func (*AuditChain) Verify

func (c *AuditChain) Verify(batchNumber int, leaf []byte, proof []ProofStep) (bool, error)

Verify verifies the audit proof for a given batch.

func (*AuditChain) VerifyContinuity

func (c *AuditChain) VerifyContinuity() error

VerifyContinuity checks that every batch (after the first) links to its predecessor's root, so a reordered/inserted/deleted batch is detected (finding 8).

func (*AuditChain) VerifyJSON

func (c *AuditChain) VerifyJSON(req *VerifyRequest) *VerifyResponse

VerifyJSON verifies an audit proof based on a JSON request.

type AuditEntry

type AuditEntry struct {
	Time           string   `json:"time"`
	Action         string   `json:"action"`
	SrcIP          string   `json:"src_ip"`
	ClientCN       string   `json:"client_cn,omitempty"`
	ClientSerial   string   `json:"client_serial,omitempty"`
	Roles          []string `json:"roles,omitempty"`
	Mapping        string   `json:"mapping"`
	Target         string   `json:"target"`
	TargetID       string   `json:"target_id,omitempty"`
	Duration       string   `json:"duration,omitempty"`
	DenyReason     string   `json:"deny_reason,omitempty"`
	BytesIn        int64    `json:"bytes_in,omitempty"`
	BytesOut       int64    `json:"bytes_out,omitempty"`
	TraceId        string   `json:"trace_id,omitempty"`
	SessionId      string   `json:"session_id,omitempty"`
	GatewayId      string   `json:"gateway_id,omitempty"`
	Protocol       string   `json:"protocol,omitempty"`
	AgentId        string   `json:"agent_id,omitempty"`
	SPIFFEID       string   `json:"spiffe_id,omitempty"`
	PrincipalUid   string   `json:"principal_uid,omitempty"`
	DelegationMode int      `json:"delegation_mode,omitempty"`
	Decision       string   `json:"decision,omitempty"`
	Capabilities   []string `json:"capabilities,omitempty"`
	// Level is the audit entry level (INFO/WARN/ERROR). Plugin decisions: allow=INFO,
	// deny/execution error=WARN.
	Level string `json:"level,omitempty"`
	// DaHash is the SHA-256 hex hash of the DelegationAuthorization signatureValue
	// (authorization evidence fingerprint, Task 4: binding authorization evidence to action records).
	DaHash string `json:"da_hash,omitempty"`
	// AICFingerprint is the SHA-256 hex of the AIC extension DER encoding (Task 4).
	AICFingerprint string `json:"aic_fingerprint,omitempty"`
	// PolicyVersion is the policy version effective at decision time (Task 5a: binding decision records to policy version).
	// 0 when PolicyManager is not enabled (omitempty omits from output).
	PolicyVersion uint64 `json:"policy_version,omitempty"`
	// RecordDigest is the input digest (hex) of the evidence record this
	// decision produced (refs[0].Digest).  It pins the audit line to the
	// replayable record chain so the two no longer drift apart; empty when the
	// decision produced no record.
	RecordDigest string `json:"record_digest,omitempty"`
}

AuditEntry is an audit log entry that records connection or decision events.

func NewAuditEntryDenied

func NewAuditEntryDenied(srcIP, mappingName, target, reason string, cert *x509.Certificate) AuditEntry

NewAuditEntryDenied creates an audit entry for a denied connection.

func NewAuditEntryFromConn

func NewAuditEntryFromConn(srcIP, mappingName, target string, cert *x509.Certificate) AuditEntry

NewAuditEntryFromConn creates an audit entry from connection information.

func ReadAuditEntries

func ReadAuditEntries(file string, filter AuditFilter) ([]AuditEntry, error)

ReadAuditEntries reads audit entries by filter.

func (*AuditEntry) SetV12Fields

func (e *AuditEntry) SetV12Fields(protocol, gatewayId, traceId, sessionId, decision string)

SetV12Fields sets the v1.2 spec extension fields for the audit log.

func (*AuditEntry) WithEvidenceFingerprints

func (e *AuditEntry) WithEvidenceFingerprints(cert *x509.Certificate) *AuditEntry

WithEvidenceFingerprints populates the audit entry's authorization evidence fingerprint fields (Task 4). Returns the original entry for method chaining.

type AuditFilter

type AuditFilter struct {
	Since    time.Time
	Until    time.Time
	Limit    int
	Offset   int
	Sort     string
	Action   string
	ClientCN string
	Serial   string
	Mapping  string
}

AuditFilter is the audit log query filter.

type AuditLogger

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

AuditLogger is the audit log writer that writes in JSON Lines format.

func NewAuditLogger

func NewAuditLogger(file string, tsa *TSAClient, maxSize int64, maxBak int) (*AuditLogger, error)

NewAuditLogger creates an audit log writer (returns nil if file is empty).

func (*AuditLogger) Close

func (l *AuditLogger) Close() error

Close closes the audit log writer, draining buffered entries. It is idempotent: closing an already-closed logger returns nil instead of re-draining the closed channel (which would spin and write garbage forever).

func (*AuditLogger) Dropped

func (l *AuditLogger) Dropped() int64

Dropped returns the number of audit entries discarded because the buffer was full (M6). Data-plane Log calls must never block, so overflow is dropped and counted rather than stalling the caller.

func (*AuditLogger) File

func (l *AuditLogger) File() string

File returns the audit log file path.

func (*AuditLogger) Log

func (l *AuditLogger) Log(entry AuditEntry)

Log enqueues an audit entry. It never blocks the caller (M6): if the buffer is full the entry is dropped and counted. This prevents a slow audit sink (e.g. TSA) from stalling the data plane. Security-critical entries (WARN/ERROR, revocation/denial actions) are never dropped on the fast path: Log waits up to a bounded timeout for them so an attacker flooding the log cannot evict the evidence of its own activity (finding 15).

type AuditVerifier

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

AuditVerifier verifies audit log entries via TSA timestamps.

type AuthContext

type AuthContext struct {
	// ClientCert is the admitted client certificate (real mTLS peer or a
	// synthesized carrier for Bearer AIC-JWT).
	ClientCert *x509.Certificate
	// Principal is the AIC PrincipalUid string.
	Principal string
	// AgentID is the AIC AgentId.
	AgentID string
	// SPIFFEID is the SPIFFE ID from the certificate SAN (empty if none).
	SPIFFEID string
	// Roles are the extracted policy roles.
	Roles []string
	// Capabilities lists the admitted AIC capability ids.
	Capabilities []string
	// AIC is the parsed AIC extension (nil for non-AIC certificates).
	AIC *AIC
	// Bearer reports whether the request was authenticated by a JWT bearer.
	Bearer bool
	// Serial is the normalized client certificate serial.
	Serial string
	// Verdict is the overall CLC verdict for the requested operations (B3):
	// "allow" when every operation was authorized outright, "allow_unresolved"
	// when one or more carried §8.4 residual obligations (released or pending).
	// Empty when no Operations were configured.
	Verdict string
	// Reason is the aggregated CLC reason for the verdict.
	Reason string
	// Unresolved lists recognized-but-unevaluated constraints carried by an
	// allow_unresolved verdict (§8.4 residual-obligation channel).
	Unresolved []string
	// OperationDecisions is the per-operation CLC verdict detail (B3).
	OperationDecisions []OperationDecision
	// Evidence points at the decision records this admission produced (digest,
	// verdict and, for file-like sinks, where it was written).  Empty when
	// evidence emission is not configured.
	Evidence []RecordRef
	// Satisfaction is the evidence-requirement evaluation for this request
	// (nil when no requirement is configured): satisfied / violated / unknown,
	// with the per-constraint detail and the missing roles.  It answers "was
	// enough evidence presented", not "is this action authorized".
	Satisfaction *semantics.RequirementResult
}

AuthContext is the verified identity attached to the request context after a successful admission.

func AuthContextFromHeaders

func AuthContextFromHeaders(r *http.Request) *AuthContext

AuthContextFromHeaders reconstructs a minimal AuthContext from the identity headers the reverse proxy injects after admission (X-AIC-Agent-Id, X-AIC-Principal-Uid, X-AIC-Capabilities-Full, X-Agent-ID). It is meant for backends that run behind the aic-verifier proxy on a trusted network: the proxy strips this whole header namespace from client input and only re-emits values it verified, so a loopback-only backend may trust them. Returns nil when no identity headers are present, so callers can fail closed.

Capabilities are restored as full scheme:capabilityId identifiers into both Capabilities (the bare capability ids, as regular admission produces) and AIC.Capabilities (with SchemeId/CapabilityId split for FullID matching). The rest of the AuthContext (ClientCert, DA evidence, cross-signed chains) is not recoverable from headers and stays empty — authorization on such a backend must not depend on data the proxy did not forward.

func FromContext

func FromContext(ctx context.Context) *AuthContext

FromContext retrieves the verified AuthContext from a request context. Returns nil when the request did not pass through a aic-verifier middleware.

type AuthError

type AuthError struct {
	Code    ErrorCode
	Status  int
	Message string
	// Stage, when set, overrides the AdmissionRecord stage for this refusal
	// (the default is Code.String()).  The middleware's pre-language refusals
	// keep the default; the reverse proxy names the layer that refused
	// ("route_denied", "method_not_allowed", "capability_denied", ...) so one
	// proxy can tell its refusals apart.
	Stage string
	// Evidence points at the records a refused request still produced, so the
	// caller can log or forward them next to the challenge.
	Evidence []RecordRef
	// Problem, when set, is written as an RFC 9457 problem document instead of
	// the SDK's compact JSON error — the carrier for CLC-CHALLENGE-v1.
	Problem *ProblemDetails
	// Satisfaction, when the refusal is an unsatisfied evidence requirement, is
	// the machine-readable result (violated / unknown with the missing roles),
	// so a caller can act on *why* the evidence bar was not met without parsing
	// the challenge back out.
	Satisfaction *semantics.RequirementResult
}

AuthError is the typed admission failure.

func (*AuthError) Error

func (e *AuthError) Error() string

type AuthMode

type AuthMode int

AuthMode selects which credential transports are accepted.

const (
	// MTLSOnly accepts requests authenticated by an mTLS client certificate.
	MTLSOnly AuthMode = iota
	// BearerOnly accepts requests authenticated by an Authorization: Bearer AIC-JWT.
	BearerOnly
	// MTLSOrBearer accepts either an mTLS client certificate or a Bearer AIC-JWT,
	// matching gateway-core semantics. mTLS takes precedence when both are present.
	MTLSOrBearer
)

type AuthorizationPolicy

type AuthorizationPolicy struct {
	Version           string                     `json:"version"`
	Roles             map[string]PolicyRole      `json:"roles"`
	OUMapping         map[string]string          `json:"ou_mapping"`
	GatewayNamespaces map[string]PolicyNamespace `json:"gateway_namespaces"`
	// CapabilityParameters is the parameter default values map derived by gen-authz from
	// capability.json. Key is "scheme:capability_id" (e.g., "varwof/gateway-v1:admin:config").
	CapabilityParameters map[string]map[string]any `json:"capability_parameters,omitempty"`
}

AuthorizationPolicy is the runtime model for the gateway authorization policy (authz.json). Structurally identical to varwof-core auth/policy.go, but kept as an independent implementation to maintain lib's zero new external dependency policy.

func GetAuthorizationPolicy

func GetAuthorizationPolicy() *AuthorizationPolicy

GetAuthorizationPolicy returns the current global authorization policy (may be nil).

func LoadAuthorizationPolicy

func LoadAuthorizationPolicy(policyPath, sigSuffix string, opts *PolicyVerifyOptions) (*AuthorizationPolicy, error)

LoadAuthorizationPolicy loads the authorization policy from a file. If opts is non-nil and the signature file (policyPath+sigSuffix) exists, signature verification is performed first.

func ParseAuthorizationPolicy

func ParseAuthorizationPolicy(data []byte) (*AuthorizationPolicy, error)

ParseAuthorizationPolicy parses the policy JSON.

func (*AuthorizationPolicy) HasGrant

func (p *AuthorizationPolicy) HasGrant(role, capability string) bool

HasGrant checks whether a role has a given capability (supports wildcards).

func (*AuthorizationPolicy) HasParamDefault

func (p *AuthorizationPolicy) HasParamDefault(scheme, capID, param string) (any, bool)

HasParamDefault checks whether a parameter has a default value (for overflow validation).

func (*AuthorizationPolicy) IntersectGrants

func (p *AuthorizationPolicy) IntersectGrants(roles []string, aicCapIds []string) []string

IntersectGrants returns the subset of aicCapIds that matches any role grants.

func (*AuthorizationPolicy) ParamDefaults

func (p *AuthorizationPolicy) ParamDefaults(scheme, capID string) map[string]any

ParamDefaults returns the parameter defaults for a given scheme:capability_id (gen-authz derived). Returns nil if not found.

func (*AuthorizationPolicy) RoleByOU

func (p *AuthorizationPolicy) RoleByOU(ou string) string

RoleByOU maps a certificate OU to a role name.

func (*AuthorizationPolicy) RoleGrants

func (p *AuthorizationPolicy) RoleGrants(role string) []string

RoleGrants returns the grants list for a role.

type CRLCache

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

CRLCache is a CRL cache that supports periodic refresh and forced reload.

func NewCRLCache

func NewCRLCache(caCert *x509.Certificate, url string, refreshSec int, translator Translator, lang string) *CRLCache

NewCRLCache creates a CRL cache instance.

func (*CRLCache) ForceRefresh

func (c *CRLCache) ForceRefresh() error

ForceRefresh forces an immediate CRL cache refresh.

func (*CRLCache) IsRevoked

func (c *CRLCache) IsRevoked(caDN string, serial *big.Int) (bool, error)

IsRevoked checks whether a given certificate serial number has been revoked.

func (*CRLCache) IsRevokedCert

func (c *CRLCache) IsRevokedCert(cert *x509.Certificate) (bool, error)

IsRevokedCert checks whether the given certificate is revoked, matching the certificate's issuer against this cache's CA robustly (finding 13): the raw issuer bytes are compared first, falling back to a structural RDN comparison that tolerates RDN ordering/formatting differences. A certificate not issued by this cache's CA is not covered by it and returns not-revoked.

func (*CRLCache) LastRefresh

func (c *CRLCache) LastRefresh() time.Time

LastRefresh returns the time of the last successful refresh.

func (*CRLCache) Start

func (c *CRLCache) Start(stop <-chan struct{})

Start starts the CRL periodic refresh loop.

func (*CRLCache) Stats

func (c *CRLCache) Stats() (int, time.Time, time.Time)

Stats returns CRL cache statistics (revocation count, this update, next update).

type CRLRevokedFunc

type CRLRevokedFunc func(caDN string, serial *big.Int) (bool, error)

CRLRevokedFunc reports whether the certificate serial has been revoked by the CA identified by caDN. An error must fail closed (the OCSP crl fallback cannot prove the certificate valid when the CRL cannot be consulted).

type Capability

type Capability = pki.Capability

── Type aliases ──

func ConstraintToCapability added in v0.2.0

func ConstraintToCapability(c string) (Capability, bool)

ConstraintToCapability is the inverse of ConstraintStrings: it maps a CLC constraint string back to the (scheme, capability, parameters) triple the connection-level registry evaluates. A string this SDK cannot map is reported as false so callers fail closed.

type CapabilityPlugin

type CapabilityPlugin = pki.CapabilityPlugin

CapabilityPlugin is the interface for all capability plugins.

type CapabilityRegistry

type CapabilityRegistry interface {
	// ValidateCapability validates the full identifier "scheme:capability_id".
	ValidateCapability(formatted string) error
	// Enabled reports whether the registry has been loaded.
	Enabled() bool
}

CapabilityRegistry is the capability registration validation interface (single source of truth). Gateway data plane validates AIC-declared capabilities against the registry during admission pipeline (RunAccessPipeline) phase one: unregistered scheme/capability is treated as an illegal declaration.

Injected by gateways (gateway-*/protocol modules): internally holds a register.Registry (embedded + disk override), atomically replaced after SIGHUP hot reload.

Returns nil when the capability is registered; returns an error when unregistered (caller rejects the connection).

func GetGlobalCapabilityRegistry

func GetGlobalCapabilityRegistry() CapabilityRegistry

GetGlobalCapabilityRegistry returns the current package-level capability registry (may be nil).

type ChallengeCarrier added in v0.2.0

type ChallengeCarrier interface {
	// Write renders problem as the response body.  The header may be modified
	// before the body is written; returning an error leaves the response
	// partially written and the caller falls back to a minimal JSON error.
	Write(w http.ResponseWriter, problem *ProblemDetails) error
}

ChallengeCarrier writes the HTTP response for a refusal that carries a challenge. The problem is the machine-readable "what is still needed"; the carrier decides how that is conveyed (RFC 9457 problem+json by default, but a deployment that routes through an aggregator may want its own shape). The SDK sets Retry-After from the challenge's retry lower bound before calling Write, so a non-standard carrier still keeps the bound visible. The carrier owns status, content type and body; Write must be safe for concurrent use.

var DefaultChallengeCarrier ChallengeCarrier = problemJSONCarrier{}

DefaultChallengeCarrier is the problem+json carrier used when a Config does not set ChallengeCarrier.

type ChallengeConfig added in v0.2.0

type ChallengeConfig struct {
	// TTL bounds how long the challenge may drive a retry.
	TTL time.Duration
	// Audience names the relying party the challenge is addressed to.
	Audience string
	// RetryAfter, when set, becomes the challenge's retry lower bound (and the
	// Retry-After header).  It exists because retrying a refusal can amplify
	// load: the client is told when a corrected presentation is welcome.
	RetryAfter time.Duration
	// ObtainHints tell the requester where the missing item can be obtained.
	ObtainHints []semantics.ObtainHint
	// Now, NewID and NewNonce are injectable for tests.  Defaults: time.Now,
	// and 16 random bytes hex-encoded (the nonce must be unpredictable; single
	// use of the retry is the enforcement point's business).
	Now      func() time.Time
	NewID    func() string
	NewNonce func() string
}

ChallengeConfig controls whether and how refusals carry a challenge.

type Config

type Config struct {
	// TLSCertFile / TLSKeyFile are the server certificate used to terminate TLS
	// (required for the reverse-proxy server in TLS/mTLS mode).
	TLSCertFile string
	TLSKeyFile  string

	// CACertFile is the CA bundle for mTLS client certificate verification.
	// Empty disables mTLS (Bearer-only or plaintext listeners).
	CACertFile string
	// JWTCAFile is a PEM file with the CA certificates used to build the AIC-JWT
	// trust root. Empty disables Bearer authentication.
	JWTCAFile string

	// BackendRootCA, when non-empty, is the root CA bundle (one or more PEM
	// files, comma/space separated) trusted for the reverse proxy's outbound
	// TLS to HTTPS backends. Appended to the system roots when set; empty uses
	// system roots only.
	BackendRootCA string
	// JWTIssuer, when non-empty, requires the AIC-JWT iss claim to match.
	JWTIssuer string
	// JWTAudience, when non-empty, requires the AIC-JWT aud claim to include one.
	JWTAudience []string
	// ReplayProtection enables one-time-use replay protection on bearer tokens
	// (default true).
	ReplayProtection *bool

	// IdentityMode selects how much verified client identity is forwarded to
	// backends (default IdentityForwardClientCert). This is fixed at startup
	// and MUST come from configuration — it is never read from a client-supplied
	// request header, so clients cannot downgrade the identity disclosed to a
	// backend.
	IdentityMode IdentityHeaderMode

	// AuthMode selects accepted credential transports (default MTLSOrBearer).
	AuthMode AuthMode

	// RequireAIC rejects clients whose certificate carries no AIC extension.
	RequireAIC bool
	// RequiredCapabilities requires the agent to hold all listed CapabilityIds.
	RequiredCapabilities []string
	// RequiredOperations are the concrete actions (id + parameters) this
	// service authorizes.  Unlike RequiredCapabilities, parameter bounds are
	// part of the decision (CLC): an operation asking for more than the grant
	// allows is denied.
	RequiredOperations []Operation
	// UnresolvedEvaluator is the §8.4 residual-obligation release hook for
	// allow_unresolved CLC decisions; forwarded to PipelineConfig → AdmissionConfig.
	// Set programmatically — not parsed from JSON.
	UnresolvedEvaluator func(op Operation, unresolved []string) bool
	// DischargeObligations / ObligationsUnderstood enable the strict
	// consumer-side obligation rule (CLC §8.4 + XACML §2.13/§7.2.1); forwarded
	// to PipelineConfig → AdmissionConfig.  Set programmatically — not parsed
	// from JSON.
	DischargeObligations  bool
	ObligationsUnderstood []string
	// RequireFreshDecisionContext / DecisionContext pin the RATS §10 freshness
	// input for allowed operations; forwarded to PipelineConfig →
	// AdmissionConfig.
	RequireFreshDecisionContext bool
	DecisionContext             *semantics.DecisionContext
	// Challenges enables the CLC-CHALLENGE-v1 carrier: a denial caused by
	// unmet §8.4 residual obligations is answered with 403 +
	// application/problem+json carrying the challenge, and its retry lower
	// bound is mapped to Retry-After.  nil (default) keeps the compact JSON
	// error.
	Challenges *ChallengeConfig
	// ChallengeCarrier shapes the HTTP response for a refusal that carries a
	// challenge.  nil (default) uses the built-in RFC 9457
	// application/problem+json carrier.  A custom carrier may change the body
	// shape; the SDK still sets Retry-After from the challenge's retry bound
	// before delegating, so the lower bound survives regardless.
	ChallengeCarrier ChallengeCarrier
	// EvidenceRequirement is the relying party's evidence sufficiency bar
	// (CLC-REQUIREMENT-v1).  The SDK never takes it from the request: it is this
	// deployment's configuration.  When set, every admitted request is also
	// evaluated against it (via EvidenceFacts), a refusal carries the machine
	// readable challenge, and the emitted records bind the requirement digest.
	EvidenceRequirement *semantics.Requirement
	// EvidenceFacts supplies the evidence facts presented with a request.  The
	// SDK does not parse evidence artifacts; the deployment hands over facts its
	// own verifiers established (type, protected subject id, issuance time,
	// whether it reached VERIFIED).  An error is fail-closed.
	EvidenceFacts func(r *http.Request, ac *AuthContext) ([]semantics.EvidenceFact, error)
	// EvidenceProfile names the evidence shape this deployment emits, e.g.
	// "clc-decision+admission+outcome@1".  It is resolved at configuration time
	// (an unknown name is a configuration error) and fills the shape parts of
	// Evidence: which payloads are produced, whether records carry a freshness
	// context and the source chain, and which requirement is bound.  Changing
	// the emitted shape is a value here, not an edit to the decision path.
	EvidenceProfile string
	// Evidence, when set, freezes a CLC decision record for every decided
	// operation and hands it to a sink (structured log by default, or one DSSE
	// envelope per record on disk).  nil (default) emits nothing — a deployment
	// that only decides online pays no bytes.  Records are produced per source
	// (AIC capabilities, principal authorization); the combined verdict stays in
	// AuthContext.
	Evidence *EvidenceConfig
	// DisallowRepresentative rejects DelegationRepresentative-mode AIC.
	DisallowRepresentative bool
	// RequireUserAuth requires DelegationAuthorization signature verification.
	RequireUserAuth bool
	// EnforceConstraints enforces authorizationConstraints (CIDR / time window /
	// concurrency).
	EnforceConstraints bool

	// UserCert is the authorized user certificate for DA signature verification.
	// UserCertResolver resolves a user certificate by principal KeyHash when
	// RequireUserAuth needs DA signature verification. If both are nil, only
	// self-issued DA (agent == user) verifies.
	UserCert         *x509.Certificate
	UserCertResolver func(keyHash []byte) (*x509.Certificate, error)

	// CRLCache / OCSPCache plug in revocation checking. Nil disables that check.
	CRLCache  *CRLCache
	OCSPCache *OCSPCache

	// AuditLogger records admission decisions. Nil disables audit logging.
	AuditLogger *AuditLogger
	// NonceCache provides anti-replay protection for DA nonces. Nil disables it.
	NonceCache *NonceCache

	// PluginRegistry registers capability plugins consulted during admission.
	// Nil disables phase-one plugin decisions.
	PluginRegistry *PluginRegistry
	// CapabilityRegistry validates AIC-declared capabilities are registered.
	// Nil falls back to the global registry.
	CapabilityRegistry CapabilityRegistry

	// Logger is the structured logger (default slog.Default()).
	Logger *slog.Logger
	// LogFile, when non-empty, appends the SDK's own log output to this file
	// (created with 0644 if missing) instead of stdout.
	LogFile string
	// StreamBody when false (default) limits the copied evaluation body to
	// DefaultMaxBodyBytes. Services streaming large bodies should set this true.
	StreamBody bool

	// Hooks install lifecycle callbacks (see Hooks). This is the reserved
	// extension point for callers that need to observe or veto decisions.
	Hooks *Hooks

	// ApprovalRequester is the mid-operation runtime human approval hook.  A
	// nil requester denies requests flagged by RequireApproval with
	// approval_required.
	ApprovalRequester ApprovalRequester
	// OverrideRecorder records break-glass / override events.  A nil recorder
	// (together with AllowBreakGlass=true) is a startup error: an unlogged
	// break-glass is never available.
	OverrideRecorder OverrideRecorder
	// SupervisionPolicy gates runtime approval, break-glass and evidence
	// export (startup validation rules in newAuthenticator).
	SupervisionPolicy SupervisionPolicy
	// SupervisionStore persists supervision events (append-only JSONL).  Nil
	// skips event persistence; decision outcomes stay fail-closed regardless.
	SupervisionStore *SupervisionStore
	// EvidenceExporter exports evidence bundles for post-operation attribution.
	EvidenceExporter EvidenceExporter
	// RequireApproval, when non-nil, is consulted per request.  Returning true
	// routes the request through ApprovalRequester before it is admitted; nil
	// (or false) admits directly.  The same trigger point is reserved for the
	// LLM semantic gate.
	RequireApproval func(ctx *AuthContext, r *http.Request) bool

	// AuditLogFile / AuditTSAURL / SupervisionLogFile are the file-based
	// supervision inputs read from JSON configuration (see fileConfig).  They
	// are the configuration mirror of the SupervisionStore / audit chain: the
	// built-in EvidenceExporter (FileEvidenceExporter) reads them together
	// with the audit file to build evidence bundles.  Code-injected stores
	// (SupervisionStore, AuditLogger) take precedence when both are set.
	AuditLogFile       string
	AuditTSAURL        string
	SupervisionLogFile string

	// ServerOptions tunes the embedded http.Server used in proxy style
	// (Server.ListenAndServe). Zero values fall back to safe defaults.
	ServerOptions *ServerOptions
	// contains filtered or unexported fields
}

Config is the full aic-verifier configuration. All fields are optional; the zero value disables every check except the raw transport decision pipeline.

func LoadConfigFile

func LoadConfigFile(path string) (*Config, error)

LoadConfigFile reads a JSON configuration file into a Config. Unknown fields are rejected so typos surface as errors instead of silently ignored options.

func ParseConfig

func ParseConfig(data []byte) (*Config, error)

ParseConfig decodes JSON configuration bytes into a Config.

func (*Config) AuthMiddleware

func (c *Config) AuthMiddleware(next http.Handler) http.Handler

AuthMiddleware mirrors Handler but panics on an invalid config, so it can be used inline in http.Server{Handler: ...}. Use Handler when errors must be handled explicitly.

func (*Config) CloseLogger

func (c *Config) CloseLogger() error

CloseLogger releases the file opened for LogFile (no-op when none set).

func (*Config) Handler

func (c *Config) Handler(next http.Handler) (http.Handler, error)

Handler builds a http.Handler that protects next with the AIC admission pipeline. On success the verified client identity is attached to the request context and pass-through headers are set on req.Header.

type ConstraintContext

type ConstraintContext struct {
	// ClientIP is used for source-address-based constraints such as allowed-cidr / geo-fence.
	ClientIP string
	// Now is the evaluation time; defaults to current UTC time. Can be injected for testing and offline decisions.
	Now time.Time
}

ConstraintContext is the runtime context provided during constraint evaluation.

type ConstraintEvaluator

type ConstraintEvaluator interface {
	// CapabilityId returns the constraint type identifier handled by this evaluator.
	CapabilityId() string
	// Evaluate evaluates a single constraint. The cap's SchemeId has already been filtered
	// to constraint / constraint-v1 by the caller.
	Evaluate(cap *Capability, ctx *ConstraintContext) error
}

ConstraintEvaluator evaluates a single constraint from authorizationConstraints. Returns a non-nil error if the constraint is not satisfied; the gateway denies the connection.

type ConstraintRegistry

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

ConstraintRegistry registers/looks up constraint evaluators by capabilityId. It provides an extensible constraint type registration mechanism: when adding a new constraint type, only the corresponding evaluator needs to be registered without modifying the certificate ASN.1 structure or gateway core routing code.

func NewConstraintRegistry

func NewConstraintRegistry() *ConstraintRegistry

NewConstraintRegistry creates an empty registry.

func (*ConstraintRegistry) Find

func (r *ConstraintRegistry) Find(capabilityId string) (ConstraintEvaluator, error)

Find looks up an evaluator by capabilityId.

func (*ConstraintRegistry) Keys

func (r *ConstraintRegistry) Keys() []string

Keys returns the list of registered capabilityIds (for metrics/audit).

func (*ConstraintRegistry) Len

func (r *ConstraintRegistry) Len() int

Len returns the number of registered constraint evaluators.

func (*ConstraintRegistry) Register

Register registers a constraint evaluator. Returns an error if the same capabilityId is registered twice.

func (*ConstraintRegistry) Remove

func (r *ConstraintRegistry) Remove(capabilityId string)

Remove removes an evaluator. After removal, unknown types revert to the "unknown constraint" semantics (ignored by default).

func (*ConstraintRegistry) Replace

Replace atomically replaces a registered evaluator (for hot updates). Registers the evaluator if not already registered.

func (*ConstraintRegistry) Reset

func (r *ConstraintRegistry) Reset()

Reset clears the registry (for testing only).

type ContentInfo

type ContentInfo struct {
	ContentType asn1.ObjectIdentifier
	Content     asn1.RawValue `asn1:"explicit,tag:0"`
}

ContentInfo is CMS content information.

type CredentialBundle

type CredentialBundle struct {
	// AgentChain is the agent certificate chain containing AIC.
	// chain[0]=Agent, subsequent entries are intermediate/root CAs.
	AgentChain []*x509.Certificate
	// PrincipalChain is the principal certificate chain containing PA.
	// chain[0]=Principal (independently issued, not in the agent chain),
	// subsequent entries are intermediate CAs (optional).
	PrincipalChain []*x509.Certificate
	// CACerts are optional CA certs carried in the bundle for informational
	// purposes. They are never used as trust anchors (finding 20): callers must
	// supply operator-configured roots via VerifyBundle.
	CACerts []*x509.Certificate
}

CredentialBundle is the credential bundle submitted by the client (Agent certificate chain + Principal certificate chain + CA).

func NewCredentialBundle

func NewCredentialBundle(agentChain, principalChain, caCerts []*x509.Certificate) (*CredentialBundle, error)

NewCredentialBundle constructs a credential bundle. Returns an error if either chain is empty.

func ParseCredentialBundlePEM

func ParseCredentialBundlePEM(data []byte) (*CredentialBundle, error)

ParseCredentialBundlePEM parses a credential bundle from PEM data (order: agent chain first, principal second, CA chain last). Certificates are classified by extensions: AIC-containing → Agent chain, PA-containing → Principal chain, rest → CA. The parsed result must be verified via VerifyBundle before use.

func (*CredentialBundle) Agent

func (b *CredentialBundle) Agent() *x509.Certificate

Agent returns the Agent certificate (contains AIC, chain[0]).

func (*CredentialBundle) Principal

func (b *CredentialBundle) Principal() *x509.Certificate

Principal returns the Principal certificate (contains PA, chain[0]).

type DecisionResult

type DecisionResult int

DecisionResult is the result of a connection admission decision.

const (
	// DecisionAllow means admission is allowed.
	DecisionAllow DecisionResult = iota
	// DecisionDeny means admission is denied.
	DecisionDeny DecisionResult = iota
	// DecisionNeedAuth means additional authentication is required.
	DecisionNeedAuth
)

type DelegationAuthTBS

type DelegationAuthTBS = pki.DelegationAuthTBS

── Type aliases ──

type DelegationAuthorization

type DelegationAuthorization = pki.DelegationAuthorization

── Type aliases ──

type DelegationChainVerifier

type DelegationChainVerifier struct {
	// MaxDepth is the maximum delegation depth allowed by the top Principal (including intermediate Agent B etc.).
	MaxDepth int
	// MaxChainLength is the hard upper limit to prevent certificate bomb attacks:
	// ≤0 means no extra limit (only constrained by MaxDepth).
	MaxChainLength int
}

DelegationChainVerifier verifies multi-level delegation chains (Zhang→Scheduler-A→Worker-B→…).

Multi-level delegation reuses the same DelegationAuthorization structure (spec dev-docs/aic/06-delegation-auth.md §Multi-level delegation chain, FUTURE reserved): each Agent's AIC contains a DelegationAuthorization signed by the previous level (delegator) certificate's private key. Verification proceeds bottom-up:

chain[i].AIC.DA signed by chain[i-1].cert → chain[i-1].AIC.DA signed by chain[i-2].cert
→ … → chain[0].AIC.DA signed by topPrincipal certificate

chainDepth is the number of delegation certificates in the chain (excluding the top Principal); maxDepth is set by the top Principal; exceeding it results in rejection. No new ASN.1 types needed; the entire chain is verifiable offline.

func (*DelegationChainVerifier) Verify

func (v *DelegationChainVerifier) Verify(chain []*x509.Certificate, topPrincipal *x509.Certificate) error

Verify verifies the delegation chain bottom-up starting from workerCert. chain is the certificate list from top to bottom: chain[0]=Scheduler-A (top-level delegating Agent), chain[len-1]=Worker-B (bottom-level Agent). Each level's AIC.DA is signed by the previous level's certificate.

Verification steps:

  1. Each certificate must contain an AIC with non-empty DA;
  2. Each level's AIC.DA signer = previous certificate (SPKI hash cross-validation);
  3. Top-level chain[0].AIC.DA signer = topPrincipal certificate;
  4. Chain depth (len(chain)) must ≤ MaxDepth;
  5. Entire chain verified offline, no external service dependency.

type DelegationMode

type DelegationMode = pki.DelegationMode

── Type aliases ──

const (
	DelegationAuthorized     DelegationMode = 0
	DelegationRepresentative DelegationMode = 1
)

DelegationMode values.

type DelegationPolicy

type DelegationPolicy = pki.DelegationPolicy

── Type aliases ──

type ErrorCode

type ErrorCode int

ErrorCode identifies the kind of admission failure.

const (
	ErrNoCredential ErrorCode = iota
	ErrDenied
	ErrNoVerifier
	ErrBearerNeedsTLS
	ErrInvalidBearer
	ErrChainInvalid
	ErrConfig
)

func (ErrorCode) String

func (c ErrorCode) String() string

type EvidenceAuditChain

type EvidenceAuditChain struct {
	Entries    []EvidenceAuditEntry `json:"entries,omitempty"`
	MerkleRoot string               `json:"merkleRoot,omitempty"`
	Anchor     string               `json:"anchor,omitempty"` // self | tsa | externalLog
	TSA        string               `json:"tsa,omitempty"`
}

EvidenceAuditChain is the tamper-evidence chain (v0.1 §3 auditChain).

type EvidenceAuditEntry

type EvidenceAuditEntry struct {
	Seq       int64  `json:"seq"`
	Action    string `json:"action"`
	Actor     string `json:"actor"`
	Ts        string `json:"ts"`
	EntryHash string `json:"entryHash"`
	PrevHash  string `json:"prevHash"`
}

EvidenceAuditEntry is a chained audit line digest. prevHash of the first entry is empty.

type EvidenceAuthorization

type EvidenceAuthorization struct {
	DelegationMode string               `json:"delegationMode,omitempty"`
	Principal      string               `json:"principal,omitempty"`
	GrantRef       *EvidenceGrantRef    `json:"grantRef,omitempty"`
	Capabilities   []EvidenceCapability `json:"capabilities,omitempty"`
	Constraints    string               `json:"constraints,omitempty"`
	Ceiling        string               `json:"ceiling,omitempty"`
	ValidFrom      string               `json:"validFrom,omitempty"`
	ExpiresAt      string               `json:"expiresAt,omitempty"`
}

EvidenceAuthorization records who authorized and the boundary (v0.1 §3 authorization).

type EvidenceBundle

type EvidenceBundle struct {
	Manifest      EvidenceManifest           `json:"manifest"`
	Operation     EvidenceOperation          `json:"operation"`
	Subject       EvidenceSubject            `json:"subject"`
	Authorization EvidenceAuthorization      `json:"authorization"`
	Decision      EvidenceDecision           `json:"decision"`
	Supervision   []EvidenceSupervisionEvent `json:"supervision"`
	AuditChain    EvidenceAuditChain         `json:"auditChain"`
	Signatures    map[string]json.RawMessage `json:"signatures"`
}

EvidenceBundle is the exported evidence package. Field layout follows the v0.1 specification: manifest/operation/subject/authorization/decision/ supervision/auditChain/signatures.

func (*EvidenceBundle) CheckDecisions added in v0.2.0

func (b *EvidenceBundle) CheckDecisions() error

CheckDecisions verifies that the bundle's decision section agrees with the record it carries (when it carries one). A bundle without a record is not wrong, but it is a report — not replayable evidence.

func (*EvidenceBundle) JSON

func (b *EvidenceBundle) JSON() ([]byte, error)

JSON serializes the bundle as canonical JSON (keys sorted lexicographically at every level, no extraneous whitespace). The output is reproducible and safe to digest.

type EvidenceCapability

type EvidenceCapability struct {
	Scheme       string `json:"scheme,omitempty"`
	CapabilityID string `json:"capabilityId"`
	Parameters   string `json:"parameters,omitempty"`
}

EvidenceCapability is a capability sub-set entry with parameter boundary summary.

type EvidenceConfig added in v0.2.0

type EvidenceConfig struct {
	// Sink receives every record.  Nil uses SlogSink with the SDK logger.
	Sink EvidenceSink
	// Strict makes an emission failure deny the request (fail-closed).  The
	// default (false) logs and continues: the evidence layer is opt-in, and a
	// broken sink should not silently take a service down — a deployment that
	// needs the record more than the request sets this true.
	Strict bool
	// TTL, when non-zero, pins a RATS §10 explicit-clock freshness input on the
	// record (At=now, MaxAgeSec=TTL).  Zero records no freshness context.
	TTL time.Duration
	// Audience names the relying party the evidence is addressed to.
	Audience string
	// RecorderID identifies this admission point when several of them record
	// the same traffic.  It is a deployment property, not a language field.
	RecorderID string
	// Now is injectable for tests.
	Now func() time.Time
	// OnError is called when emission fails, so a deployment can count or page
	// on evidence gaps instead of only finding them in a log.  Nil logs at
	// error level.
	OnError func(ctx EvidenceContext, err error)
	// Gaps, when set, counts every emission that failed to leave a record.  It
	// makes "the evidence layer itself lost evidence" a queryable meter (not a
	// log-scan archaeology problem): gap events call OnError for paging and
	// increment Gaps for reporting.  Nil disables the counter.
	Gaps *GapCounter
	// Sign, when set, appends a DSSE signature over PAE(payloadType, payload) to
	// every emitted envelope — decision, admission and outcome alike.  nil
	// (.default) leaves records unsigned: content-recomputable, but carrying no
	// key that endorses "which admission point issued this".  The signing
	// primitive is caller-supplied (DSSE deliberately does not pick one); KeyID
	// is an unauthenticated hint, never trusted on its own.  A signing failure
	// follows the existing emission rules: OnError is called, and Strict denies
	// the request.
	Sign  func(pae []byte) ([]byte, error)
	KeyID string
	// EmitOutcome, when true, makes the reverse proxy report an outcome record
	// after every forwarded request (a response → observed + status; a
	// transport failure → indeterminate).  It is an opt-in effect-side channel:
	// classification of executed/failed stays with the deployment.  The
	// decision, admission and standing evidence budget are unaffected.
	EmitOutcome bool
	// Requirement, when set, is bound into each record as its requirement
	// digest: a record then says *which* sufficiency bar was applied, not just
	// what was decided.
	Requirement *semantics.Requirement
	// Profile is the declared evidence shape (EvidenceProfile).  When set, every
	// emitted envelope is tagged with the profile's content-addressed identity,
	// so a consumer can tell which shape it received.
	Profile *EvidenceProfile
	// Recorder, when set, is published as an `evidence-recorder` subject: a
	// consumer holding the descriptor can tell which admission point emitted a
	// record, and one that does not can still tell whether two records came from
	// the same recorder.  RecorderID alone stays a display hint.
	Recorder *RecorderDescriptor
	// contains filtered or unexported fields
}

EvidenceConfig turns on record emission at this admission point.

type EvidenceContainer added in v0.2.0

type EvidenceContainer string

EvidenceContainer names the envelope shape.

const (
	// ContainerDSSEInToto is the DSSE JSON envelope carrying an in-toto
	// Statement.  It is the only container implemented today.
	ContainerDSSEInToto EvidenceContainer = "dsse+in-toto"
)

type EvidenceContext added in v0.2.0

type EvidenceContext struct {
	// Facts are the content-addressed facts the outcome rested on; pipeline
	// records carry them into the statement's subjects.
	Facts       []AdmissionFact
	RecorderID  string
	OperationID string
	Method      string
	Path        string
	TraceID     string
	Principal   string
	AgentID     string
	Serial      string
	Outcome     EvidenceOutcome
	At          time.Time
}

EvidenceContext is what a sink needs to *use* a record: which request it came from, who it was about, and whether that request was admitted or refused. The record itself carries the decision; this carries the correlation.

type EvidenceDecision

type EvidenceDecision struct {
	// Decision mirrors the CLC verdict.  When Record is present this field is
	// derived from it (allow | allow_unresolved | deny) and MUST NOT disagree:
	// a bundle whose summary contradicts its record is not evidence.
	Decision string `json:"decision,omitempty"`
	// RecordDigest is the record's input digest (hex) — the stable identifier
	// of "which decision this bundle is about".
	RecordDigest string `json:"recordDigest,omitempty"`
	// Record is the CLC Decision Record itself: frozen inputs, verdict and
	// residual obligations, independently re-computable by any holder.  The
	// other fields in this section are a reader's summary of it; the record is
	// the authority.
	Record                *semantics.DecisionRecord `json:"record,omitempty"`
	MatchedPolicy         string                    `json:"matchedPolicy,omitempty"`
	ReasonCodes           []string                  `json:"reasonCodes,omitempty"`
	EvaluatedCapabilities []string                  `json:"evaluatedCapabilities,omitempty"`
	PdpContext            string                    `json:"pdpContext,omitempty"`
}

EvidenceDecision is the PDP/PEP decision record (v0.1 §3 decision).

func (*EvidenceDecision) ApplyRecord added in v0.2.0

func (d *EvidenceDecision) ApplyRecord(rec *semantics.DecisionRecord) error

ApplyRecord attaches a CLC decision record to this section and derives the summary fields from it. Deriving (rather than trusting the audit text) is what keeps the bundle from carrying a decision of its own.

type EvidenceExporter

type EvidenceExporter interface {
	// Export returns a self-contained, canonical-serializable evidence bundle.
	Export(ctx context.Context, q EvidenceQuery) (*EvidenceBundle, error)
}

EvidenceExporter produces an evidence bundle (evidence-bundle v0.1) for a query, merging the audit chain with supervision events.

type EvidenceFailure added in v0.2.0

type EvidenceFailure struct {
	Path   string `json:"path"`
	Reason string `json:"reason"`
}

EvidenceFailure names one file that did not verify and why.

type EvidenceGrantRef

type EvidenceGrantRef struct {
	Ref    string `json:"ref"`
	Digest string `json:"digest"`
}

EvidenceGrantRef is a reference + digest to the governing DA.

type EvidenceKind added in v0.2.0

type EvidenceKind string

EvidenceKind names which payload an envelope carries.

const (
	// KindDecision is a language-layer CLC decision record.
	KindDecision EvidenceKind = "clc-decision"
	// KindAdmission is a pipeline-level admission record.
	KindAdmission EvidenceKind = "aic-admission"
	// KindOutcome is an execution-boundary outcome record.
	KindOutcome EvidenceKind = "aic-outcome"
)

func CheckEvidenceEnvelope added in v0.2.0

func CheckEvidenceEnvelope(env semantics.Envelope) (EvidenceKind, error)

CheckEvidenceEnvelope validates either payload type through one entry point, dispatching on the statement's predicate type. A CLC record is checked by the language (structure, subject binding, re-computation); a pipeline record is parsed and shape-checked here.

func VerifyEvidenceEnvelope added in v0.2.0

func VerifyEvidenceEnvelope(env semantics.Envelope, verify func(keyID string, pae, sig []byte) error) (EvidenceKind, error)

VerifyEvidenceEnvelope validates an envelope of any of the three payload kinds. Structural shipping is always performed (dispatched on the statement's predicate type). When verify is non-nil, at least one signature must verify over the DSSE PAE — mirroring Envelope.VerifyRecord's multi-signature rule: the first signature that verifies wins, KeyID is passed through as an unauthenticated hint, and a tampered payload fails because its PAE no longer matches what was signed.

type EvidenceManifest

type EvidenceManifest struct {
	Schema     string    `json:"schema"`
	Version    string    `json:"version"`
	BundleID   string    `json:"bundleId"`
	ExportedAt time.Time `json:"exportedAt"`
	Generator  string    `json:"generator"`
	HashAlg    string    `json:"hashAlg"`
	PolicyRef  string    `json:"policyRef,omitempty"`
}

EvidenceManifest is the bundle's own metadata (v0.1 §3 manifest).

type EvidenceOperation

type EvidenceOperation struct {
	OperationID     string  `json:"operationId,omitempty"`
	StartedAt       string  `json:"startedAt,omitempty"`
	FinishedAt      string  `json:"finishedAt,omitempty"`
	Resource        string  `json:"resource,omitempty"`
	Action          string  `json:"action,omitempty"`
	RequestedParams Summary `json:"requestedParams,omitempty"`
	Outcome         string  `json:"outcome,omitempty"` // permit | deny | error
}

EvidenceOperation identifies the audited operation and its outcome (v0.1 §3 operation).

type EvidenceOutcome added in v0.2.0

type EvidenceOutcome string

EvidenceOutcome says what happened to the request the record belongs to.

const (
	// EvidenceAdmitted means the request was admitted.
	EvidenceAdmitted EvidenceOutcome = "admitted"
	// EvidenceRefused means the request was refused (the record says why).
	EvidenceRefused EvidenceOutcome = "refused"
)

type EvidenceProfile added in v0.2.0

type EvidenceProfile struct {
	// Name is the profile identifier, e.g. "clc-decision+admission+outcome@1".
	Name string
	// Container is the envelope shape.
	Container EvidenceContainer
	// Decision emits CLC decision records (the language layer).
	Decision bool
	// Admission emits pipeline-level records for refusals that never reached the
	// language layer.
	Admission bool
	// Outcome allows an execution-boundary sink to report effect records.
	Outcome bool
	// Sources includes the authorization source chain in decision records.
	Sources bool
	// Freshness pins a RATS §10 explicit clock on each record for this duration
	// (0 = no context).
	Freshness time.Duration
	// Requirement binds the relying party's sufficiency bar into each record.
	Requirement *semantics.Requirement
}

EvidenceProfile declares which evidence a deployment emits.

func LookupEvidenceProfile added in v0.2.0

func LookupEvidenceProfile(name string) (EvidenceProfile, error)

LookupEvidenceProfile resolves a named profile. An unknown name is an error: a deployment that asks for a shape we do not produce must hear about it at configuration time, not discover it later from missing records.

func (EvidenceProfile) Apply added in v0.2.0

Apply fills an EvidenceConfig from the profile. The plumbing (sink, strict, error hook, recorder id) stays with the deployment; the shape comes from here.

func (EvidenceProfile) ID added in v0.2.0

ID is the profile's identity, content-addressed over everything that shapes the emitted bytes. Two deployments that emit the same shape share an ID.

func (EvidenceProfile) Validate added in v0.2.0

func (p EvidenceProfile) Validate() error

Validate checks the profile's own consistency.

type EvidenceQuery

type EvidenceQuery struct {
	// OperationID is the preferred correlation key (server-side request id).
	OperationID string
	// DaHash correlates by delegation authorization fingerprint.
	DaHash string
	// AgentID correlates by agent identifier.
	AgentID string
	// TimeRange bounds the export window.  Nil means no time bound.
	TimeRange *TimeRange
	// IncludeSupervision merges supervision events into the bundle (default
	// false keeps the bundle audit-only).
	IncludeSupervision bool
	// Record is the CLC decision record this bundle is about.  When set, it is
	// attached to the bundle's decision section as the authority, and the
	// summary fields (Decision, ReasonCodes) are derived from it rather than
	// from the audit text.  Callers obtain it from the evidence sink (see
	// LoadEvidenceRecord).
	Record *semantics.DecisionRecord
}

EvidenceQuery selects the evidence to export for post-operation attribution.

type EvidenceSink added in v0.2.0

type EvidenceSink interface {
	// Emit receives a language-layer decision record.
	Emit(ctx EvidenceContext, rec semantics.DecisionRecord, env semantics.Envelope) (RecordRef, error)
	// EmitAdmission receives a pipeline-level record: a refusal that happened
	// before the language layer (credential, chain, revocation, missing
	// capability).  It is a separate method because it is a separate payload
	// type — a sink that only understands decisions can still implement it as
	// "store the envelope and move on".
	EmitAdmission(ctx EvidenceContext, rec AdmissionRecord, env semantics.Envelope) (RecordRef, error)
}

EvidenceSink receives a frozen decision record and its transport envelope, and reports where it kept it. Implementations must be safe for concurrent use.

type EvidenceSubject

type EvidenceSubject struct {
	AgentID                string `json:"agentId,omitempty"`
	KeyBinding             string `json:"keyBinding,omitempty"`
	TransportIdentity      string `json:"transportIdentity,omitempty"`
	PresentedCredentialRef string `json:"presentedCredentialRef,omitempty"`
}

EvidenceSubject records who executed (v0.1 §3 subject).

type EvidenceSupervisionEvent

type EvidenceSupervisionEvent struct {
	Type        string `json:"type"` // consent | denied | stepUp | approval | breakGlass | override
	OccurredAt  string `json:"occurredAt"`
	Actor       string `json:"actor"`
	DecisionRef string `json:"decisionRef,omitempty"`
	EvidenceRef string `json:"evidenceRef,omitempty"`
}

EvidenceSupervisionEvent is one human-intervention event (v0.1 §3 supervision). decisionRef mirrors the shared operation_id correlation key.

type EvidenceVerifyReport added in v0.2.0

type EvidenceVerifyReport struct {
	// Total is the number of record envelopes scanned.
	Total int
	// Decision / Admission / Outcome are the per-kind record counts.
	Decision  int
	Admission int
	Outcome   int
	// Failures lists every file that did not verify: malformed JSON, an
	// envelope of an unknown predicate type, a decision that no longer
	// recomputes, or (when verify was supplied) a record with no valid
	// signature.  Empty means the directory verified clean.
	Failures []EvidenceFailure
}

EvidenceVerifyReport is the aggregated result of VerifyEvidenceDir.

func VerifyEvidenceDir added in v0.2.0

func VerifyEvidenceDir(dir string, verify func(keyID string, pae, sig []byte) error) (EvidenceVerifyReport, error)

VerifyEvidenceDir scans dir for evidence record envelopes and verifies each one, dispatching on its statement's predicate type: a CLC decision must re-compute, and an admission / outcome record must parse and be shape-invariant (see CheckEvidenceEnvelope). When verify is non-nil, every envelope must also carry at least one signature that verifies over its DSSE PAE (see VerifyEvidenceEnvelope). A deployment that already trusts the emission point's key passes VerifyFnFromPublicKey here and the key question is settled without writing crypto.

The scan is best-effort per file: a broken or unrecognized file is collected in Failures, not returned as an error — an evidence directory with one bad record must be auditable, not unreadable. Only a failure to read the directory itself surfaces as (Report{}, err). A directory without records verifies clean (all-zero report).

type ExtField

type ExtField = pki.ExtField

── Type aliases ──

type ExternalPolicyRef

type ExternalPolicyRef = pki.ExternalPolicyRef

── Type aliases ──

type FileEvidenceExporter

type FileEvidenceExporter struct {
	// AuditFile is the audit JSON Lines file (SignedAuditEntry stream).
	AuditFile string
	// SupervisionFile is the supervision event JSON Lines file.  Empty means
	// supervision is unavailable; querying with IncludeSupervision then yields
	// no supervision events.
	SupervisionFile string
	// EvidenceDir is the evidence record directory (the FileSink directory).
	// When set and no Record was passed in the query, the exporter resolves the
	// anchor from the audit chain (record_digest), loads the decision record
	// envelope by digest and attaches it to the bundle.  A digest that resolves
	// to nothing is a gap: the export fails instead of silently degrading to a
	// report-only bundle.
	EvidenceDir string
	// Generator is the bundle.generator label; empty defaults to the package
	// version.
	Generator string
	// Now overrides the export clock (tests).
	Now func() time.Time
}

FileEvidenceExporter is the built-in minimal exporter: it reads the audit chain (SignedAuditEntry JSONL) and optionally the supervision event store, filters them by EvidenceQuery, and builds an evidence-bundle v0.1 with a Merkle root over the exported line digests.

func NewFileEvidenceExporter

func NewFileEvidenceExporter(auditFile, supervisionFile string) *FileEvidenceExporter

NewFileEvidenceExporter builds the built-in exporter over the given audit and supervision JSONL files.

func (*FileEvidenceExporter) Export

Export implements EvidenceExporter.

type FileSink added in v0.2.0

type FileSink struct {
	Dir string
	// RecorderID is prefixed to the file name when set.
	RecorderID string
	// contains filtered or unexported fields
}

FileSink writes one DSSE envelope per record, named by its input digest so the same decision recorded twice overwrites itself instead of accumulating.

func (*FileSink) Emit added in v0.2.0

Emit implements EvidenceSink.

func (*FileSink) EmitAdmission added in v0.2.0

func (s *FileSink) EmitAdmission(ctx EvidenceContext, rec AdmissionRecord, env semantics.Envelope) (RecordRef, error)

EmitAdmission implements EvidenceSink for pipeline-level records; the file name carries an "admission-" marker so the two payload types never collide.

func (*FileSink) EmitOutcome added in v0.2.0

func (s *FileSink) EmitOutcome(ctx EvidenceContext, rec OutcomeRecord, env semantics.Envelope) (RecordRef, error)

EmitOutcome implements OutcomeSink for pipelines that want every artifact on disk; the file name carries an "outcome-" marker so the payload types never collide.

type GapCounter added in v0.2.0

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

GapCounter is an atomically counted evidence-gap meter: how often an emission that was supposed to leave a record failed to do so. Safe for concurrent use from any number of admission points.

func (*GapCounter) Count added in v0.2.0

func (g *GapCounter) Count() int64

Count returns how many emissions failed to leave a record since the counter was created.

func (*GapCounter) Inc added in v0.2.0

func (g *GapCounter) Inc()

Inc registers one failed emission.

type GeoResolver

type GeoResolver func(ip string) (string, error)

GeoResolver resolves a geographic region identifier (e.g. "CN-SHA") from a source IP. Registered with the geo-fence evaluator to make region resolution pluggable (built-in inline table; third-party databases like ip2region can self-register).

type HTTPFacts

type HTTPFacts = pki.HTTPFacts

HTTPFacts carries per-request HTTP facts for capability plugins.

type Hooks

type Hooks struct {
	// Authenticated runs on every admitted request, before the upstream handler
	// (middleware style) or backend proxy (proxy style) is invoked. Returning a
	// non-nil error denies the request with 403 and the error text is logged.
	Authenticated func(ctx *AuthContext, r *http.Request) error

	// Denied runs when a request is rejected by the admission pipeline. err is
	// the typed *AuthError carrying the error code and HTTP status.
	Denied func(r *http.Request, err *AuthError)

	// Forwarded runs after an admitted request returned from the backend (proxy
	// style only). resp is non-nil when the upstream responded; it is nil when
	// the reverse proxy failed before a response was produced.
	Forwarded func(r *http.Request, resp *http.Response)
}

Hooks are optional lifecycle callbacks a service operator can plug into the request pipeline. Every hook is invoked synchronously; nil hooks are skipped. This is the reserved extension point for callers that need to observe or veto AIC decisions without writing a full capability plugin.

type IdentityHeaderMode

type IdentityHeaderMode int

IdentityHeaderMode selects how much identity detail is forwarded to backends.

const (
	// IdentityMinimal strips every identity header (backend trusts the proxy only).
	IdentityMinimal IdentityHeaderMode = iota
	// IdentityForwardClientCert forwards the full client certificate (DER) plus
	// the X-AIC-* structured views.
	IdentityForwardClientCert
	// IdentityXForwarded sets only X-Forwarded-Client-* (CN/O/OU/serial).
	IdentityXForwarded
	// IdentityAIC only sets the X-AIC-* structured headers.
	IdentityAIC
)

type JWTVerifier

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

JWTVerifier verifies AIC-JWT bearer tokens against a trust root built from CA certificates (same kid convention as the X.509 carrier: base64url SHA-256 of the certificate SPKI). On success it returns a synthesized X.509 certificate carrying the token's AIC extension, so the existing pipeline (RunAccessPipeline / CheckAdmission) admits a bearer request exactly like a certificate-authenticated one.

A bare NewJWTVerifier only verifies the token signature/expiry. For production use the gateway must call SetBearerPolicy (issuer/audience binding + replay protection) and pass per-request proof-of-possession via VerifyBearer options; without those the bearer is replayable until exp by any holder (finding 5).

func LoadJWTVerifier

func LoadJWTVerifier(caFiles ...string) (*JWTVerifier, error)

LoadJWTVerifier reads one or more PEM CA certificate files (comma or space separated paths) and builds a JWT verifier from them. An empty spec returns a nil verifier (bearer auth disabled).

func NewJWTVerifier

func NewJWTVerifier(cas []*x509.Certificate) *JWTVerifier

NewJWTVerifier builds a verifier from CA certificates. kid for each CA is base64url(SHA-256(SubjectPublicKeyInfo)) — the same binding core publishes on /.well-known/jwks.json.

func (*JWTVerifier) SetBearerPolicy

func (v *JWTVerifier) SetBearerPolicy(expectedIssuer string, expectedAudience []string, nonces aicjwt.NonceStore)

SetBearerPolicy installs the static bearer-token policy applied to every verification: the expected issuer, acceptable audiences, and a replay nonce store. Configure all three for production; leaving issuer/audience empty or the nonce store nil keeps those checks off (finding 5).

func (*JWTVerifier) VerifyBearer

func (v *JWTVerifier) VerifyBearer(token string, now time.Time, opts ...JWTVerifyOptions) (*x509.Certificate, *aicjwt.OuterClaims, error)

VerifyBearer validates a Bearer AIC-JWT and returns a synthesized certificate carrying the AIC claims, plus the raw outer claims. opts carry per-request checks (proof-of-possession, revocation); the verifier's static policy (issuer/audience/replay) is always applied.

type JWTVerifyOptions

type JWTVerifyOptions struct {
	// ExpectedIssuer, when non-empty, requires outer.iss == ExpectedIssuer.
	ExpectedIssuer string
	// ExpectedAudience, when non-empty, requires the token aud to include one.
	ExpectedAudience []string
	// PresenterKey, when non-nil, enforces cnf proof-of-possession: the token
	// must be bound to this public key (e.g. the mTLS peer cert key).
	PresenterKey crypto.PublicKey
	// NonceStore, when non-nil, provides one-time-use replay protection on the
	// DA nonce (finding 5).
	NonceStore aicjwt.NonceStore
	// RequireJtiNonceMatch requires outer.jti == DA nonce.
	RequireJtiNonceMatch bool
	// StatusChecker, when non-nil, checks issuer/principal for revocation.
	StatusChecker aicjwt.StatusChecker
}

JWTVerifyOptions carries the runtime verification parameters for a single bearer token. These activate the checks Validate supports but that a bare call leaves unset (finding 5).

type Layer1Result

type Layer1Result struct {
	Verified bool
	Reason   string
	Roles    []string
}

Layer1Result is the Layer 1 identity verification result (Agent Cert + CA Chain + RBAC).

func VerifyLayer1

func VerifyLayer1(chain []*x509.Certificate, cfg *PipelineConfig) *Layer1Result

VerifyLayer1 performs Layer 1 identity verification: certificate chain validity (validity period) + RBAC roles (AllowRoles matching). Cryptographic certificate chain verification is done by the TLS layer (VerifyPeerCertificate); this layer performs application-level identity checks (validity period + roles). Returns roles for use by subsequent layers.

type Layer2Result

type Layer2Result struct {
	Verified               bool
	Reason                 string
	AIC                    *AIC
	PrincipalAuthorization *PrincipalAuthorization
	PrincipalUid           string
}

Layer2Result is the Layer 2 representation verification result (+ Principal Cert + PA).

type Layer3Result

type Layer3Result struct {
	Verified bool
	Reason   string
}

Layer3Result is the Layer 3 online authorization verification result (OCSP/CRL/Policy Server).

func VerifyLayer3

func VerifyLayer3(chain []*x509.Certificate, cfg *PipelineConfig) *Layer3Result

VerifyLayer3 performs Layer 3 online authorization verification: CRL/OCSP revocation freshness check + optional PolicyServer online policy check.

type MerkleTree

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

MerkleTree is a Merkle hash tree used for tamper-proof audit chains.

func NewMerkleTree

func NewMerkleTree(leaves [][]byte) *MerkleTree

NewMerkleTree creates a Merkle tree from leaf data.

func (*MerkleTree) Proof

func (m *MerkleTree) Proof(leafIndex int) ([]ProofStep, error)

Proof computes the audit proof path for a given leaf index.

func (*MerkleTree) Root

func (m *MerkleTree) Root() []byte

Root returns the Merkle tree root hash.

func (*MerkleTree) RootHex

func (m *MerkleTree) RootHex() string

RootHex returns the root hash as a hex-encoded string.

type MessageImprint

type MessageImprint struct {
	HashAlgorithm AlgorithmIdentifier
	HashedMessage []byte
}

MessageImprint is a message digest imprint.

type NonceCache

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

NonceCache provides a nonce replay protection cache (v1.4 §3.2). Thread-safe with automatic cleanup of expired entries.

func NewNonceCache

func NewNonceCache() *NonceCache

NewNonceCache creates a NonceCache and starts automatic cleanup (hourly, retaining entries within 24h).

func (*NonceCache) CheckAndAdd

func (nc *NonceCache) CheckAndAdd(scope string, nonce []byte) bool

CheckAndAdd checks whether a nonce has been replayed by a different certificate (DA replay attack detection). scope is the certificate identity (issuer/serial), used to distinguish "same cert replaying the same nonce" (normal) from "DA evidence copied into a different cert" (attack). Returns true to allow.

func (*NonceCache) Len

func (nc *NonceCache) Len() int

Len returns the current number of nonces in the cache (for testing and monitoring only).

func (*NonceCache) Stop

func (nc *NonceCache) Stop()

Stop stops the background cleanup goroutine.

type OCSPCache

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

OCSPCache is an OCSP response cache with request coalescing.

func NewOCSPCache

func NewOCSPCache(ttl time.Duration, fallback string, translator Translator, lang string) *OCSPCache

NewOCSPCache creates an OCSP cache instance.

func (*OCSPCache) Check

func (c *OCSPCache) Check(cert, issuer *x509.Certificate) error

Check queries the OCSP responder and caches the result.

func (*OCSPCache) Flush

func (c *OCSPCache) Flush()

Flush clears the OCSP cache.

func (*OCSPCache) SetCRLChecker

func (c *OCSPCache) SetCRLChecker(fn CRLRevokedFunc)

SetCRLChecker installs the CRL revocation lookup used by the "crl" OCSP fallback. It must be set for OCSPFallbackCRL to fail closed instead of silently allowing (finding 3).

func (*OCSPCache) Stats

func (c *OCSPCache) Stats() (int, int)

Stats returns the count of good/revoked entries in the OCSP cache.

type OfflineRBAC

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

OfflineRBAC provides offline RBAC decision capability for degraded scenarios where the core is unreachable. Injected with a role list extracted from certificates or configuration at construction time via NewOfflineRBAC.

func NewOfflineRBAC

func NewOfflineRBAC(roles []string) *OfflineRBAC

NewOfflineRBAC creates an offline RBAC instance storing the specified roles.

func NewOfflineRBACFromCert

func NewOfflineRBACFromCert(cert *x509.Certificate) *OfflineRBAC

NewOfflineRBACFromCert creates an offline RBAC instance by extracting roles from a certificate's OU.

func (*OfflineRBAC) CheckRole

func (r *OfflineRBAC) CheckRole(allowed []string) bool

CheckRole checks whether roles contains at least one role from allowed. If roles contains gateway:*, it passes immediately. Supports gateway:* wildcard in allowed.

type Operation

type Operation = semantics.Operation

Operation is a concrete action to authorize: a capability id plus the parameters the caller wants to use.

type OperationDecision

type OperationDecision struct {
	ID         string         `json:"id"`
	Params     map[string]any `json:"params,omitempty"`
	Verdict    string         `json:"verdict"` // semantics.VerdictAllow / VerdictDeny / VerdictAllowUR
	Reason     string         `json:"reason,omitempty"`
	Unresolved []string       `json:"unresolved,omitempty"`
	// Grants are the effective grant set the operation was decided over: the
	// AIC capability set on the delegated path, the principal's own grants on
	// the direct path, each already carrying the connection's authorization
	// constraints.  Surfacing them here means a downstream consumer can recompute
	// the verdict without parsing an evidence record file.
	Grants []semantics.Grant `json:"grants,omitempty"`
	// Released is true when an allow_unresolved operation was admitted because
	// the deployment's UnresolvedEvaluator confirmed the residual obligations.
	Released bool `json:"released,omitempty"`
}

OperationDecision records the CLC verdict for one requested operation, so admission results and the AuthContext can expose verdict / reason / unresolved (spec B3) instead of only the final allow/deny.

type OutcomeRecord added in v0.2.0

type OutcomeRecord struct {
	Ver     string    `json:"ver"`
	Outcome string    `json:"outcome"`
	At      time.Time `json:"at"`
	// DecisionDigest points back at the decision record this outcome followed
	// (hex of that record's input digest).  Empty means the linkage was not
	// established — which a consumer should treat as a gap, not as consent.
	DecisionDigest string `json:"decisionDigest,omitempty"`
	// OperationID is the action this outcome is about.
	OperationID string `json:"operationId,omitempty"`
	// RecorderID identifies the execution boundary that reported it.
	RecorderID string `json:"recorderId,omitempty"`
	// StatusCode is the transport-level result when there was one (HTTP).
	StatusCode int `json:"statusCode,omitempty"`
	// Note is a bounded, deployment-supplied description.
	Note string `json:"note,omitempty"`
	// Identity is who executed it, as the boundary established it.
	Identity AdmissionIdentity `json:"identity,omitempty"`
	// Facts are the digests of the material the outcome rests on.
	Facts []AdmissionFact `json:"facts,omitempty"`
}

OutcomeRecord says what the execution boundary observed, and which decision it followed. It is evidence about the effect, not about authority.

func ParseOutcomeEnvelope added in v0.2.0

func ParseOutcomeEnvelope(env semantics.Envelope) (OutcomeRecord, error)

ParseOutcomeEnvelope decodes and shape-checks an outcome record.

func (OutcomeRecord) Digest added in v0.2.0

func (r OutcomeRecord) Digest() (semantics.Digest, error)

recordDigest identifies the outcome record.

type OutcomeSink added in v0.2.0

type OutcomeSink interface {
	EmitOutcome(ctx EvidenceContext, rec OutcomeRecord, env semantics.Envelope) (RecordRef, error)
}

OutcomeSink receives execution-boundary records. A deployment that has no execution boundary simply does not configure one.

type OverrideRecorder

type OverrideRecorder interface {
	// Record persists a break-glass / override event.  It does not authorize
	// the operation by itself.
	Record(ctx context.Context, risk RiskAssessment, actor, reason string) (*SupervisionResult, error)
}

OverrideRecorder is the break-glass / override recording point. The SDK only provides the record point: whether break-glass is allowed is the operator's policy (SupervisionPolicy.AllowBreakGlass). When policy allows break-glass and the recorder is nil, startup validation fails, so an unlogged break-glass is never available.

type PKIStatusInfo

type PKIStatusInfo struct {
	Status int
}

PKIStatusInfo is PKI status information.

type ParameterValidator

type ParameterValidator interface {
	// Scheme returns the schemeId associated with this validator.
	Scheme() string
	// Validate checks whether declared (agent/certificate declaration) falls within
	// granted (principal authorization boundary). Returns a non-nil error if out of bounds
	// (includes the specific parameter name and boundary).
	Validate(granted, declared Capability) error
}

ParameterValidator validates whether a capability declaration's parameters fall within the authorized boundary. Implemented per schemeId (one Scheme → one validator).

var MaxRowsValidator ParameterValidator = maxRowsValidator{}

MaxRowsValidator is an exported instance of the max_rows parameter boundary validator (for registration: NewParameterValidatorRegistry().Register(MaxRowsValidator)).

type ParameterValidatorRegistry

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

ParameterValidatorRegistry manages registration and lookup of parameter boundary validators.

func BuiltinParameterValidators

func BuiltinParameterValidators() *ParameterValidatorRegistry

BuiltinParameterValidators returns the built-in parameter boundary validator registry.

func NewParameterValidatorRegistry

func NewParameterValidatorRegistry() *ParameterValidatorRegistry

NewParameterValidatorRegistry creates an empty parameter boundary validator registry.

func (*ParameterValidatorRegistry) Find

Find looks up the parameter boundary validator for the given schemeId.

func (*ParameterValidatorRegistry) Keys

func (r *ParameterValidatorRegistry) Keys() []string

Keys returns all registered scheme IDs.

func (*ParameterValidatorRegistry) Len

Len returns the number of registered validators.

func (*ParameterValidatorRegistry) Register

Register registers a parameter boundary validator.

func (*ParameterValidatorRegistry) Reset

func (r *ParameterValidatorRegistry) Reset()

Reset clears the registry.

func (*ParameterValidatorRegistry) ValidateCapability

func (r *ParameterValidatorRegistry) ValidateCapability(granted, declared Capability) error

ValidateCapability validates a capability declaration against the authorized boundary using the given registry. Unregistered schemes are allowed (no parameter boundary rules; capability-level intersection was already performed upstream).

type PermissionDef

type PermissionDef = pki.PermissionDef

── Type aliases ──

type PermissionLevel

type PermissionLevel = pki.PermissionLevel

── Type aliases ──

const (
	PermissionAuto             PermissionLevel = 0
	PermissionRequiresApproval PermissionLevel = 1
)

PermissionLevel constants.

type PipelineCheck

type PipelineCheck int

PipelineCheck is the certificate chain check scope type.

const (
	CheckFullChain PipelineCheck = iota
	CheckLeafOnly
)

CheckFullChain/CheckLeafOnly are certificate chain check scope constants.

type PipelineConfig

type PipelineConfig struct {
	// CRLCache is the CRL cache instance.
	CRLCache *CRLCache
	// OCSPCache is the OCSP cache instance.
	OCSPCache *OCSPCache
	// AllowRoles is the list of allowed RBAC roles.
	AllowRoles []string
	// CheckScope controls the CA scope check mode.
	CheckScope PipelineCheck
	// MaxConnsPerCert is the maximum connections per certificate.
	MaxConnsPerCert int
	// RequireAIC requires the client to hold an AIC certificate.
	RequireAIC bool
	// RequireSPIFFE requires the client certificate to carry a SPIFFE ID
	// SAN URI. When set, connections without a SPIFFE ID are rejected.
	RequireSPIFFE bool
	// AllowedSPIFFEIDs is an optional exact-match allowlist of SPIFFE IDs.
	// Empty means no allowlist restriction.
	AllowedSPIFFEIDs []string
	// SPIFFETrustDomain when non-empty requires the client SPIFFE ID to
	// belong to this trust domain (e.g. "varwof.com").
	SPIFFETrustDomain string
	// RequiredProtocol is the transport protocol required by the client.
	RequiredProtocol string
	// RequiredRuleId is the required matching route rule ID.
	RequiredRuleId string
	// RequiredCapabilities is the list of capabilities the client must possess.
	RequiredCapabilities []string
	// Operations are the concrete actions (capability id + parameters) this
	// request wants to perform.  When set, each one is decided with the CLC
	// core against the effective authority — the AIC capabilities intersected
	// with the PrincipalAuthorization grants — so parameter bounds take part in
	// the decision instead of matching capability ids alone.
	Operations []Operation
	// UnresolvedEvaluator is the §8.4 residual-obligation release hook for
	// allow_unresolved CLC decisions; forwarded to AdmissionConfig (see
	// AdmissionConfig.UnresolvedEvaluator).
	UnresolvedEvaluator func(op Operation, unresolved []string) bool
	// DischargeObligations / ObligationsUnderstood enable the strict
	// consumer-side obligation rule; forwarded to AdmissionConfig (see
	// AdmissionConfig.DischargeObligations).
	DischargeObligations  bool
	ObligationsUnderstood []string
	// RequireFreshDecisionContext / DecisionContext pin the RATS §10 freshness
	// input; forwarded to AdmissionConfig (see
	// AdmissionConfig.RequireFreshDecisionContext).
	RequireFreshDecisionContext bool
	DecisionContext             *semantics.DecisionContext
	// DisallowRepresentative disallows delegated representative mode.
	DisallowRepresentative bool
	// RequireUserPermission requires user authorization signature.
	RequireUserPermission bool
	// RejectOverflow rejects when connection limit is exceeded.
	RejectOverflow bool
	// RequireUserAuth requires user authentication.
	RequireUserAuth bool
	// EnforceCapSizeConstraints enforces capability size constraints.
	EnforceCapSizeConstraints bool
	// EnforceSize32 enforces the 32-byte size constraint.
	EnforceSize32 bool
	// CapabilityPluginRegistry is the capability plugin registry.
	CapabilityPluginRegistry *PluginRegistry
	// CapabilityRegistry is the capability registration validation (single source of truth).
	// When non-nil, phase one performs registration validation on AIC-declared capabilities:
	// unregistered → reject connection.
	// nil means disabled (backward compatible).
	CapabilityRegistry CapabilityRegistry
	// CapabilityPluginResolver selects a capability plugin registry by agent identifier
	// (task 5b: branch control/canary).
	// When non-nil, it takes precedence over CapabilityPluginRegistry: the returned registry
	// is used for phase one plugin evaluation, and the returned version number (if non-zero)
	// overrides PolicyVersion for audit binding. Agents matching a branch use the branch
	// version policy; others fall back to the currently active version.
	CapabilityPluginResolver func(agentID string) (version uint64, reg *PluginRegistry)
	// AuditLogger is the audit log recorder.
	AuditLogger *AuditLogger
	// NonceCache is the anti-replay nonce cache.
	NonceCache *NonceCache
	// ClientIP is the client IP address for authorization constraint checks.
	ClientIP string
	// UserCert is the authorized user's certificate, used for DelegationAuthorization
	// signature verification.
	UserCert *x509.Certificate
	// UserCertResolver resolves a user certificate by PrincipalUid.KeyHash
	// (fetched via varwof-core API). Automatically called when UserCert is nil.
	UserCertResolver func(keyHash []byte) (*x509.Certificate, error)
	// EnforceConstraints, when true, enforces authorizationConstraints.
	EnforceConstraints bool
	// StrictConstraints, when true, fails-closed on unknown constraint types.
	StrictConstraints bool
	// ParameterValidators is the parameter boundary validator registry.
	// When non-nil, after the P∩C intersection, parameters of AIC declarations and PA
	// authorizations are compared one by one against the boundary; out-of-bounds → reject.
	ParameterValidators *ParameterValidatorRegistry
	// PolicyServer is the Layer 3 online authorization policy server.
	// Called by VerifyLayer3/VerifyTrustLayers; not used by RunAccessPipeline.
	PolicyServer PolicyServer
	// CredentialBundle is the client-submitted credential bundle (agent, principal and CA chains).
	// When RequireUserAuth is enabled, the Principal certificate is preferentially extracted
	// from the credential bundle for DA signature verification.
	CredentialBundle *CredentialBundle
	// PolicyVersion is the policy version effective at decision time (task 5a: decision
	// records bound to policy version).
	// Filled by the gateway from PolicyManager.CurrentVersion() when constructing PipelineConfig;
	// when 0, the audit entry omits this field.
	PolicyVersion uint64
	// RiskMonitor is the high-risk behavior monitor (2026-08-15). When non-nil, the pipeline
	// automatically records violation signals at behavioral rejection points (parameter overflow /
	// plugin deny / CIDR out-of-bounds); once a rule threshold is reached, the gateway's
	// injected OnAction callback executes kick + revocation.
	RiskMonitor *RiskMonitor
	// OfflineMaxCertLifetime is the maximum remaining certificate validity enforced in offline
	// mode (G2(b)).
	// When >0 (e.g., 1h): revocation checks use fail-open (OCSP fallback=allow / CRL unreachable)
	// in offline scenarios; client certificates with remaining validity exceeding this value are
	// rejected — prevents offline fail-open windows from diluting "short-lived certificate"
	// semantics with long-lived certificates. 0 = not enforced.
	OfflineMaxCertLifetime time.Duration
	// HTTPFacts carries per-request HTTP facts (method/path/query/
	// headers) that are copied into the PluginContext for capability
	// plugins. nil means no HTTP facts (TCP/TLS admission path).
	HTTPFacts *HTTPFacts
}

PipelineConfig is the unified admission pipeline configuration.

type PipelineResult

type PipelineResult struct {
	Granted    bool
	DenyReason string
	Roles      []string
	Principal  string
	Serial     string
	AgentId    string
	// SPIFFEID is the SPIFFE ID extracted from the client certificate SAN
	// URI (empty when the certificate carries no SPIFFE URI).
	SPIFFEID string
	// AIC is the AIC extension carried by the admitted connection (parsed result). G3 long-lived
	// connection periodic review requires its AuthorizationConstraints.
	AIC *AIC
	// PrincipalAuthorization is the principal authorization extension carried by the connection
	// (source of the P∩C intersection).
	PrincipalAuthorization *PrincipalAuthorization
	// CLCVerdict / CLCReason / CLCUnresolved aggregate the per-operation CLC
	// decisions when cfg.Operations was set: "allow" when every requested
	// operation was authorized outright, "allow_unresolved" when one or more
	// carried §8.4 residual obligations released by the deployment's
	// UnresolvedEvaluator.  Empty when no operations were configured.
	CLCVerdict    string
	CLCReason     string
	CLCUnresolved []string
	// OperationDecisions is the per-operation CLC verdict detail (B3).
	OperationDecisions []OperationDecision
}

PipelineResult is the admission pipeline execution result.

func RunAccessPipeline

func RunAccessPipeline(chain []*x509.Certificate, cfg *PipelineConfig) *PipelineResult

RunAccessPipeline executes the unified admission pipeline (CRL→OCSP→RBAC→decision engine).

func VerifyTrustLayers

func VerifyTrustLayers(chain []*x509.Certificate, cfg *PipelineConfig) *PipelineResult

VerifyTrustLayers executes three-layer trust verification in combination (L1 → L2 → L3), equivalent to the explicit layered entry point of RunAccessPipeline. Returns denial on any layer failure.

type PluginAuditEntry

type PluginAuditEntry struct {
	Scheme       string `json:"scheme"`
	CapabilityID string `json:"capability_id"`
	Decision     string `json:"decision"`
	Reason       string `json:"reason"`
	ClientCN     string `json:"client_cn,omitempty"`
	Principal    string `json:"principal,omitempty"`
	// Level is the audit level (INFO/WARN). Allow→INFO, deny/execution error→WARN.
	// Empty value defaults to INFO.
	Level string `json:"level,omitempty"`
	// DaHash is the SHA-256 hash of the DelegationAuthorization signatureValue
	// (Task 4: plugin decision entries also bind authorization evidence fingerprints).
	DaHash string `json:"da_hash,omitempty"`
	// PolicyVersion is the policy version effective at decision time (Task 5a).
	PolicyVersion uint64 `json:"policy_version,omitempty"`
}

PluginAuditEntry records audit fields for plugin decision events.

type PluginContext

type PluginContext = pki.PluginContext

PluginContext is the context during plugin execution.

type PluginDecision

type PluginDecision = pki.PluginDecision

PluginDecision represents the decision result after plugin execution.

const (
	PluginAllow  PluginDecision = pki.PluginAllow
	PluginDeny   PluginDecision = pki.PluginDeny
	PluginBypass PluginDecision = pki.PluginBypass
)

PluginAllow/Deny/Bypass are plugin decision constants.

type PluginRegistry

type PluginRegistry = pki.PluginRegistry

PluginRegistry manages plugin registration and lookup.

func NewPluginRegistry

func NewPluginRegistry() *PluginRegistry

NewPluginRegistry creates a new empty registry.

type PluginResult

type PluginResult = pki.PluginResult

PluginResult is the return result after plugin execution.

func CheckOperationCapability

func CheckOperationCapability(reg *PluginRegistry, cap *Capability, ctx *PluginContext) (*PluginResult, error)

CheckOperationCapability executes phase two (operation layer) plugin decisions. Called by the gateway before processing a specific operation (e.g., HTTP route, TCP tunnel, UDP target), making decisions only for the capability corresponding to that operation:

  • Operation scheme has no plugin → fail-closed reject (gateway declares service but no decision rule configured; cannot allow uncontrolled operations);
  • Plugin deny → reject;
  • Plugin allow/bypass → allow.

Complementary to phase one (P∩C intersection + scheme alignment inside RunAccessPipeline): phase one ignores unrelated schemes to allow connections; phase two is fail-closed for operations that will actually be executed.

Returns (PluginResult, error): error indicates only internal execution errors (e.g., webhook call failure); Decision==PluginDeny means the operation is rejected, and the caller should block the operation and record an audit entry accordingly.

func ExecutePlugin

func ExecutePlugin(schemeID string, cap *pki.Capability, ctx *PluginContext) (*PluginResult, error)

ExecutePlugin is a convenience wrapper for findPlugin + Execute.

type PolicyNamespace

type PolicyNamespace struct {
	DisplayName string   `json:"display_name"`
	Prefix      string   `json:"prefix"`
	Grants      []string `json:"grants"`
}

PolicyNamespace defines the grants for a gateway namespace.

type PolicyRole

type PolicyRole struct {
	DisplayName string   `json:"display_name"`
	Profiles    []string `json:"profiles"`
	Grants      []string `json:"grants"`
	Scope       []string `json:"scope,omitempty"`
}

PolicyRole defines a single role's display name, profiles, grants, and scope.

type PolicyServer

type PolicyServer interface {
	// Name returns the policy server name (for auditing).
	Name() string
	// CheckOnline checks online authorization (leaf certificate + parsed AIC).
	// Returns nil for authorization granted; non-nil for denial (with reason).
	CheckOnline(leaf *x509.Certificate, aic *AIC) error
}

PolicyServer is the Layer 3 online authorization policy server interface (Layer 3). Online authorization verification includes revocation freshness (OCSP/CRL, handled by PipelineConfig's cache instances) and policy server policy checks (if configured).

type PolicySigningConfig

type PolicySigningConfig struct {
	// Enabled enables policy signature verification.
	Enabled bool `json:"enabled,omitempty"`
	// CAFile is the trusted CA chain PEM (defaults to tls_client_ca).
	CAFile string `json:"ca_file,omitempty"`
	// RequireAdminOU requires the signer to have admin OU (nil=defaults to true).
	RequireAdminOU *bool `json:"require_admin_ou,omitempty"`
	// Require: true=reject if signature is missing; false=degrade with warning if missing.
	Require bool `json:"require,omitempty"`
	// SigSuffix is the signature file suffix (default ".sig").
	SigSuffix string `json:"sig_suffix,omitempty"`
}

PolicySigningConfig configures PKCS#7 detached signature verification for the gateway authz.json policy file. Structurally identical to varwof-core's policy_signing configuration.

func (*PolicySigningConfig) BuildPolicyVerifyOptions

func (ps *PolicySigningConfig) BuildPolicyVerifyOptions(tlsClientCA string) (*PolicyVerifyOptions, error)

BuildPolicyVerifyOptions builds signature verification parameters from PolicySigningConfig. Returns nil if signature verification is not enabled (signing disabled). tlsClientCA is used as the default fallback when CAFile is empty.

type PolicySigningIdentity

type PolicySigningIdentity struct {
	Cert *x509.Certificate
	Key  crypto.Signer
}

PolicySigningIdentity describes the admin identity used to sign the policy file.

func LoadPolicySigningIdentity

func LoadPolicySigningIdentity(certPEM, keyPEM string) (*PolicySigningIdentity, error)

LoadPolicySigningIdentity loads the signer certificate and private key from PEM files.

type PolicyVerifyOptions

type PolicyVerifyOptions struct {
	// Roots is the trusted CA chain (Issuing CA + Root CA) for verifying the signer certificate.
	Roots *x509.CertPool
	// RequireAdminOU, when true, requires the signer certificate OU to contain the admin role.
	RequireAdminOU bool
}

PolicyVerifyOptions describes the external parameters needed for signature verification.

type PrincipalAuthorization

type PrincipalAuthorization = pki.PrincipalAuthorization

── Type aliases ──

func ParseUserPermissionExtension

func ParseUserPermissionExtension(cert *x509.Certificate) (*PrincipalAuthorization, error)

ParseUserPermissionExtension delegates to pki-types.

type PrincipalUid

type PrincipalUid = pki.PrincipalUid

── Type aliases ──

func MakePrincipalUidFromCert

func MakePrincipalUidFromCert(realm, identifier string, certDER []byte) PrincipalUid

MakePrincipalUidFromCert constructs a PrincipalUid from a certificate DER (KeyHash = SPKI SHA-256, per spec §PrincipalUid).

func ParsePrincipalUid

func ParsePrincipalUid(s string) (PrincipalUid, error)

ParsePrincipalUid delegates to pki-types.

type ProblemDetails added in v0.2.0

type ProblemDetails struct {
	Type      string               `json:"type"`
	Title     string               `json:"title,omitempty"`
	Status    int                  `json:"status"`
	Detail    string               `json:"detail,omitempty"`
	Instance  string               `json:"instance,omitempty"`
	Challenge *semantics.Challenge `json:"challenge,omitempty"`
}

ProblemDetails is an RFC 9457 problem document carrying a CLC challenge.

type ProofStep

type ProofStep struct {
	Sibling []byte `json:"sibling"`
	Left    bool   `json:"left"`
}

ProofStep is a single step in a Merkle audit proof, containing a sibling hash and direction.

type ProofStepJSON

type ProofStepJSON struct {
	Sibling string `json:"sibling"`
	Left    bool   `json:"left"`
}

ProofStepJSON is the JSON representation of an audit proof step.

type Reason

type Reason = pki.Reason

── Type aliases ──

type RecordRef added in v0.2.0

type RecordRef struct {
	Digest  string `json:"digest"`  // hex of the record's input digest
	Verdict string `json:"verdict"` // the CLC verdict recorded
	Path    string `json:"path,omitempty"`
}

RecordRef is where a record ended up, so a caller can point at it (audit entry, downstream log, response) without carrying the whole record.

func EmitDecisionRecords added in v0.2.0

func EmitDecisionRecords(cfg *EvidenceConfig, sinkCtx EvidenceContext, cert *x509.Certificate, aic *AIC, pa *PrincipalAuthorization, userCert *x509.Certificate, ops []OperationDecision) ([]RecordRef, error)

EmitDecisionRecords freezes one record per (source, operation) and hands it to the sink, returning where each record went. The verdict in each record is recomputed by RecordWith from the same grants and operation, so a record that does not reproduce is impossible by construction.

sinkCtx carries the correlation (which request, who, admitted or refused); the per-operation fields are filled in here.

func ReportOutcome added in v0.2.0

func ReportOutcome(sink OutcomeSink, cfg *EvidenceConfig, ctx EvidenceContext, rec OutcomeRecord) (RecordRef, error)

ReportOutcome sends an outcome record through the deployment's sink, filling in the recorder and timestamp when the caller left them out.

type RecorderDescriptor added in v0.2.0

type RecorderDescriptor struct {
	ID   string `json:"id"`
	Kind string `json:"kind,omitempty"`
	Note string `json:"note,omitempty"`
}

RecorderDescriptor identifies the admission point that emitted a record. It is published out of band (one descriptor per deployment); the envelope then carries its digest as a subject, so a consumer holding the descriptor can tell which recorder produced the record — and one that does not hold it can still tell that two records came from the same or different recorders.

func (RecorderDescriptor) Digest added in v0.2.0

func (r RecorderDescriptor) Digest() (semantics.Digest, error)

Digest is the recorder's content-addressed identity.

type ResourceScope

type ResourceScope = pki.ResourceScope

── Type aliases ──

type RiskAssessment

type RiskAssessment struct {
	// OperationID is the server-side per-request tracking id (correlation key).
	OperationID string `json:"operation_id"`
	// AgentID is the AIC agent identifier.
	AgentID string `json:"agent_id,omitempty"`
	// PrincipalUid is the verified principal UID.
	PrincipalUid string `json:"principal_uid,omitempty"`
	// DAHash is the sha256 hex hash of the signed DelegationAuthorization.
	DAHash string `json:"da_hash,omitempty"`
	// Capabilities are the admitted capability ids.
	Capabilities []string `json:"capabilities,omitempty"`
	// Operation is a short human-readable operation label (e.g. "POST /trade").
	Operation string `json:"operation"`
	// RequestedParams is the redacted summary of the request parameters.
	RequestedParams Summary `json:"requested_params,omitempty"`
	// Violations list deterministic-rule risks not yet covered semantically.
	Violations []string `json:"violations,omitempty"`
	// Semantic is the optional LLM semantic verdict for this request.
	Semantic *SemanticVerdict `json:"semantic,omitempty"`
	// PolicyVersion is the policy version effective at decision time.
	PolicyVersion uint64 `json:"policy_version,omitempty"`
}

RiskAssessment is the snapshot of a request that needs human supervision. It is passed to ApprovalRequester.Request, OverrideRecorder.Record and attached to supervision events. JSON is snake_case and defaults to omitempty so a minimal assessment stays small.

type RiskMonitor

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

RiskMonitor maintains per-agent violation counts and enforces rules automatically. Thread-safe; nil receiver methods are no-ops (safe to call when gateway is not configured).

func NewRiskMonitor

func NewRiskMonitor(cfg RiskMonitorConfig) *RiskMonitor

NewRiskMonitor creates a risk monitor.

func (*RiskMonitor) RecordViolation

func (m *RiskMonitor) RecordViolation(v RiskViolation) bool

RecordViolation records a behavioral violation and evaluates rules; triggers the enforcement callback when the threshold is reached. Returns whether enforcement was triggered. Returns false for nil receiver.

func (*RiskMonitor) Rules

func (m *RiskMonitor) Rules() []RiskRule

Rules returns a copy of the current rule list.

func (*RiskMonitor) SetRules

func (m *RiskMonitor) SetRules(rules []RiskRule)

SetRules hot-swaps the rule set (called during SIGHUP hot-reload).

func (*RiskMonitor) Violations

func (m *RiskMonitor) Violations(agentId string) int

Violations returns the cumulative violation count for an agent within the window (used for monitoring display when no enforcement has been triggered). Returns 0 for nil receiver.

type RiskMonitorConfig

type RiskMonitorConfig struct {
	// Rules is the list of risk rules.
	Rules []RiskRule `json:"rules"`
	// OnAction is the enforcement callback (gateway injects: execute disconnect + revoke).
	// When nil, only logging is performed.
	OnAction func(agentId, action, reason string)
	// Logger is the structured logger; uses slog.Default() when nil.
	Logger *slog.Logger
}

RiskMonitorConfig is the configuration for RiskMonitor.

type RiskRule

type RiskRule struct {
	// Name is the rule name.
	Name string `json:"name"`
	// Signals is the list of behavioral signal types that trigger the rule (any hit counts).
	Signals []string `json:"signals"`
	// Threshold is the violation count threshold within the window; reaching it triggers enforcement.
	Threshold int `json:"threshold"`
	// WindowSeconds is the counting window in seconds, default 60.
	WindowSeconds int `json:"window_seconds,omitempty"`
	// Action is the enforcement action: disconnect (kick) or revoke (kick + revoke).
	Action string `json:"action"`
	// Reason is the risk reason description for audit records.
	Reason string `json:"reason"`
}

RiskRule is a single risk rule.

type RiskViolation

type RiskViolation struct {
	// AgentId is the violating agent.
	AgentId string `json:"agent_id,omitempty"`
	// Signal is the risk signal type (e.g. cap_overflow / abnormal_rate / out_of_window).
	Signal string `json:"signal"`
	// CapabilityId is the associated capability identifier (optional).
	CapabilityId string `json:"capability_id,omitempty"`
	// Details provides supplementary description.
	Details string `json:"details,omitempty"`
	// At is the violation time (Unix seconds).
	At int64 `json:"at"`
}

RiskViolation describes a recorded behavioral violation (risk signal).

type RoleDef

type RoleDef = pki.RoleDef

── Type aliases ──

type RotatingFile

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

RotatingFile is an auto-rotating file that supports size-based rotation and backup count limits.

func NewRotatingFile

func NewRotatingFile(path string, maxSize int64, maxBak int) (*RotatingFile, error)

NewRotatingFile creates an auto-rotating file instance.

func (*RotatingFile) Close

func (r *RotatingFile) Close() error

Close closes the rotating file.

func (*RotatingFile) Write

func (r *RotatingFile) Write(p []byte) (int, error)

Write implements io.Writer with automatic rotation.

type Route

type Route struct {
	// Path is the URL path prefix to match (e.g. "/api/v1"). Requests under this
	// prefix are forwarded to Target with their path preserved.
	Path string
	// Target is the backend base URL (e.g. "http://127.0.0.1:8080").
	Target *url.URL
	// AllowMethods, when non-empty, restricts accepted HTTP methods.
	AllowMethods []string
	// RequiredCapabilities requires the admitted agent to hold these capability
	// ids before the request is forwarded.
	RequiredCapabilities []string
}

Route is a reverse-proxy routing rule.

type SPIFFEID

type SPIFFEID struct {
	TrustDomain string
	Path        string
}

SPIFFEID represents a parsed SPIFFE identity.

func ExtractSPIFFEID

func ExtractSPIFFEID(cert *x509.Certificate) *SPIFFEID

ExtractSPIFFEID extracts a parsed SPIFFE ID from a certificate's SAN URIs. Returns nil if no valid SPIFFE URI is found.

func ParseSPIFFEID

func ParseSPIFFEID(id string) (*SPIFFEID, error)

ParseSPIFFEID parses a SPIFFE ID string into its components (trust domain, path).

func (*SPIFFEID) Equal

func (s *SPIFFEID) Equal(other *SPIFFEID) bool

Equal checks whether two SPIFFEID values are identical.

func (*SPIFFEID) String

func (s *SPIFFEID) String() string

String returns the SPIFFE ID in URI format.

type SealedTree

type SealedTree struct {
	BatchNumber int    `json:"batch"`
	Timestamp   string `json:"timestamp"`
	Previous    string `json:"previous_root"`
	Root        string `json:"root"`
	Size        int    `json:"size"`
}

SealedTree is a sealed Merkle tree batch with root hash and predecessor link.

type SemanticVerdict

type SemanticVerdict struct {
	// Model is the model identifier that produced the verdict.
	Model string `json:"model"`
	// Version is the model/policy version.
	Version string `json:"version"`
	// PromptHash is a hex digest of the evaluation prompt (evidence binding).
	PromptHash string `json:"prompt_hash"`
	// InputDigest is a hex digest of the evaluated input (optional).
	InputDigest string `json:"input_digest,omitempty"`
	// OutputJSON is the raw model output (JSON document, base64 in JSON).
	OutputJSON []byte `json:"output_json,omitempty"`
	// Decision is the semantic decision: allow | deny | refer.
	Decision string `json:"decision"`
	// Confidence is the model-reported confidence (0..1), optional.
	Confidence float64 `json:"confidence,omitempty"`
	// Failed reports that the semantic evaluation itself errored (timeout,
	// transport failure).  A failed gate must never fail-open; it maps to a
	// deny/refer at the caller.
	Failed bool `json:"failed,omitempty"`
	// Err is the evaluation error text when Failed is set.
	Err string `json:"err,omitempty"`
}

SemanticVerdict is the output of an optional LLM semantic gate. The SDK does not call an LLM itself; integrations that do (or that enforce semantic policies) populate this and attach it to a RiskAssessment for the human approver.

type Server

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

Server is an AIC-protected HTTP reverse proxy. It terminates TLS (optionally mTLS), runs the admission pipeline on every request, and forwards admitted requests to the matching backend, injecting the verified client identity.

func NewServer

func NewServer(c *Config, routes []Route) (*Server, error)

NewServer builds an AIC-protected reverse proxy server.

func (*Server) Close

func (s *Server) Close(ctx context.Context) error

Close gracefully shuts down the server.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler exposes the server as a plain http.Handler (for embedding into an existing http.Server). Callers managing TLS themselves should use this.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(addr string) error

ListenAndServe starts the reverse-proxy server listening on addr. When TLSCertFile/TLSKeyFile are configured the listener terminates TLS (mTLS when CACertFile is also set).

type ServerOptions

type ServerOptions struct {
	ReadTimeout       time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration
	MaxHeaderBytes    int
}

ServerOptions tunes the embedded http.Server of the reverse proxy.

type SignedAuditEntry

type SignedAuditEntry struct {
	Entry AuditEntry `json:"entry"`
	TST   string     `json:"tst,omitempty"`
}

SignedAuditEntry is an audit entry with TSA timestamp signature.

type SignedSupervisionEvent

type SignedSupervisionEvent struct {
	Event pki.SupervisionEvent `json:"event"`
	TST   string               `json:"tst,omitempty"`
}

SignedSupervisionEvent is a supervision event with an optional RFC 3161 TSA timestamp attestation, mirroring SignedAuditEntry.

type SlogSink added in v0.2.0

type SlogSink struct {
	Logger *slog.Logger
}

SlogSink writes a compact summary of each record to a structured logger.

func (SlogSink) Emit added in v0.2.0

Emit implements EvidenceSink.

func (SlogSink) EmitAdmission added in v0.2.0

func (s SlogSink) EmitAdmission(ctx EvidenceContext, rec AdmissionRecord, env semantics.Envelope) (RecordRef, error)

EmitAdmission implements EvidenceSink for pipeline-level records.

func (SlogSink) EmitOutcome added in v0.2.0

func (s SlogSink) EmitOutcome(ctx EvidenceContext, rec OutcomeRecord, env semantics.Envelope) (RecordRef, error)

EmitOutcome implements OutcomeSink for the structured-logger fallback.

type Summary

type Summary string

Summary is a redacted summary of the requested operation parameters. It is derived from a digest form and never carries raw parameter values, reusing the audit mask concept (see mask.go). The empty Summary means nothing was included.

func NewSummaryFromBody

func NewSummaryFromBody(body []byte) Summary

NewSummaryFromBody produces a masked digest summary of a request body. An empty body yields an empty Summary.

type SupervisionPolicy

type SupervisionPolicy struct {
	// RequireRuntimeApproval mandates that requests flagged by RequireApproval
	// go through the ApprovalRequester.  When true and ApprovalRequester is
	// nil, startup validation fails.
	RequireRuntimeApproval bool
	// AllowBreakGlass enables break-glass overrides.  When true and
	// OverrideRecorder is nil, startup validation fails.
	AllowBreakGlass bool
	// RequireEvidenceExport mandates an EvidenceExporter.  When true and
	// EvidenceExporter is nil, startup validation fails.
	RequireEvidenceExport bool
}

SupervisionPolicy gates the supervision features. Pre-operation (DA) supervision is aic-agent side and intentionally absent here.

type SupervisionQuery

type SupervisionQuery struct {
	OperationID string
	DaHash      string
	AgentID     string
	Type        pki.SupervisionEventType
	TimeRange   *TimeRange
	Limit       int
}

SupervisionQuery filters supervision events read from the store. Empty fields are ignored. Type filters on a specific SupervisionEventType.

type SupervisionResult

type SupervisionResult struct {
	// Decision is approved | denied | pending.
	Decision string `json:"decision"`
	// Approver identifies the human that decided.
	Approver string `json:"approver,omitempty"`
	// Reason explains the decision.
	Reason string `json:"reason,omitempty"`
	// EvidenceRef references an external approval/review ticket.
	EvidenceRef string `json:"evidence_ref,omitempty"`
	// DecidedAt is when the decision was made.
	DecidedAt time.Time `json:"decided_at"`
}

SupervisionResult is the outcome of a human (or system) supervision step.

func (*SupervisionResult) Approved

func (r *SupervisionResult) Approved() bool

Approved reports whether the result is an explicit approval.

type SupervisionStore

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

SupervisionStore is the append-only supervision event store: JSON Lines with the same durability rules as the audit chain (audit.go). Critical events are written synchronously and can be TSA-attested. A nil store disables event persistence; the decision path stays fail-closed regardless.

func NewSupervisionStore

func NewSupervisionStore(file string, tsa *TSAClient, maxSize int64, maxBak int) (*SupervisionStore, error)

NewSupervisionStore creates an append-only event store at file. When file is empty it returns a nil store (no persistence). tsa, when non-nil, signs every recorded event with a timestamp token.

func (*SupervisionStore) Close

func (s *SupervisionStore) Close() error

Close closes the store file. Further Record calls fail.

func (*SupervisionStore) File

func (s *SupervisionStore) File() string

File returns the store file path (empty for a nil store).

func (*SupervisionStore) Query

Query reads supervision events from the store in append order and filters them. Events are returned newest-correlation-friendly in file order; an empty result is a non-nil empty slice. A nil store returns an error.

func (*SupervisionStore) Record

func (s *SupervisionStore) Record(ev *pki.SupervisionEvent) error

Record validates and synchronously appends a supervision event. TSA attestation is best-effort: a signing failure records the event unsigned and returns the underlying TSA error only if the write itself also failed.

type TSAClient

type TSAClient struct {
	// URL is the TSA service address.
	URL string
	// CACert is the CA certificate (for verifying TSA response signatures).
	CACert *x509.Certificate
	// HTTPClient is the HTTP client.
	HTTPClient *http.Client
	// SignFunc is a custom signing function (replaces HTTP calls).
	SignFunc func(data []byte) ([]byte, error)
	// contains filtered or unexported fields
}

TSAClient is an RFC 3161 timestamp client.

func NewTSAClient

func NewTSAClient(url string) *TSAClient

NewTSAClient creates a timestamp client.

func (*TSAClient) SetCACert

func (t *TSAClient) SetCACert(certFile string) error

SetCACert sets the TSA CA certificate.

func (*TSAClient) SetMaxTSTAge

func (t *TSAClient) SetMaxTSTAge(d time.Duration)

SetMaxTSTAge overrides the accepted TST age window. Defaults to 1h.

func (*TSAClient) Sign

func (t *TSAClient) Sign(data []byte) (tstDER []byte, err error)

Sign performs an RFC 3161 timestamp signature on data.

func (*TSAClient) Verify

func (t *TSAClient) Verify(entryJSON, tstDER []byte) error

Verify verifies a timestamp token.

type TSTInfo

type TSTInfo struct {
	Version        int
	Policy         asn1.ObjectIdentifier
	MessageImprint MessageImprint
	SerialNumber   int
	GenTime        time.Time
	Accuracy       asn1.RawValue `asn1:"optional"`
	Ordering       bool          `asn1:"optional,default:false"`
	Nonce          *int          `asn1:"optional"`
	TSA            asn1.RawValue `asn1:"optional,explicit,tag:0"`
}

TSTInfo is the timestamp token information.

func UnmarshalTimestampToken

func UnmarshalTimestampToken(data []byte) (*TSTInfo, error)

UnmarshalTimestampToken parses a timestamp token from DER.

type TimeRange

type TimeRange struct {
	Start time.Time `json:"start,omitempty"`
	End   time.Time `json:"end,omitempty"`
}

TimeRange bounds a query window. Zero values are unbounded on that end.

type TimeStampReq

type TimeStampReq struct {
	Version        int
	MessageImprint MessageImprint
	ReqPolicy      asn1.ObjectIdentifier `asn1:"optional"`
	Nonce          *int                  `asn1:"optional"`
	CertReq        bool                  `asn1:"optional,default:false"`
	Extensions     []asn1.RawValue       `asn1:"optional,set"`
}

TimeStampReq is an RFC 3161 timestamp request.

type TimeStampResp

type TimeStampResp struct {
	Status         PKIStatusInfo
	TimeStampToken asn1.RawValue `asn1:"optional"`
}

TimeStampResp is an RFC 3161 timestamp response.

type Translator

type Translator interface {
	T(lang, key string, args ...any) string
}

Translator is the internationalization translation interface.

type TrustLayer

type TrustLayer int

TrustLayer represents a layer in the three-layer trust model.

const (
	Layer1Identity TrustLayer = iota
	Layer2Representation
	Layer3OnlineAuthorization
)

Layer1/2/3 three-layer trust model layer constants.

func (TrustLayer) String

func (l TrustLayer) String() string

String returns the layer name.

type UserPermission

type UserPermission = pki.UserPermission

── Type aliases ──

type VerifyRequest

type VerifyRequest struct {
	Batch int             `json:"batch"`
	Leaf  string          `json:"leaf"`
	Proof []ProofStepJSON `json:"proof"`
}

VerifyRequest is an audit verification request.

type VerifyResponse

type VerifyResponse struct {
	Valid bool   `json:"valid"`
	Error string `json:"error,omitempty"`
}

VerifyResponse is an audit verification response.

Directories

Path Synopsis
examples
bearer-jwt-backend command
Command bearer-jwt-backend demonstrates the aic-verifier SDK protecting a real HTTP API:
Command bearer-jwt-backend demonstrates the aic-verifier SDK protecting a real HTTP API:
bearer-jwt-backend/gen-bearer command
Command gen-bearer generates a CA keypair and signs a sample AIC-JWT bearer token for the bearer-jwt-backend example.
Command gen-bearer generates a CA keypair and signs a sample AIC-JWT bearer token for the bearer-jwt-backend example.
inspect-record command
Command inspect-record reads a decision record written by the SDK and prints what was decided, why, and the digest a third party recomputes.
Command inspect-record reads a decision record written by the SDK and prints what was decided, why, and the digest a third party recomputes.
mcp-behind-proxy command
Command mcp-behind-proxy runs the AIC-gated MCP server in its canonical deployment topology: the aic-verifier reverse proxy terminates TLS, runs the bearer AIC-JWT admission pipeline and forwards each admitted request to a loopback-only MCP backend, which trusts the proxy's server-asserted X-AIC-* identity headers (aic-verifier/mcp TrustProxy mode) instead of re-verifying a credential.
Command mcp-behind-proxy runs the AIC-gated MCP server in its canonical deployment topology: the aic-verifier reverse proxy terminates TLS, runs the bearer AIC-JWT admission pipeline and forwards each admitted request to a loopback-only MCP backend, which trusts the proxy's server-asserted X-AIC-* identity headers (aic-verifier/mcp TrustProxy mode) instead of re-verifying a credential.
mcp-server command
Command mcp-server demonstrates an AIC-gated MCP (Model Context Protocol) server on top of aic-verifier.
Command mcp-server demonstrates an AIC-gated MCP (Model Context Protocol) server on top of aic-verifier.
mtls-backend command
Command mtls-backend demonstrates the aic-verifier SDK protecting a real HTTP API with mTLS client certificate + AIC authorization:
Command mtls-backend demonstrates the aic-verifier SDK protecting a real HTTP API with mTLS client certificate + AIC authorization:
mtls-backend/gen-cert command
Command gen-mtls generates a demo CA, a server certificate, and an mTLS client certificate carrying an AIC extension, for the mtls-backend example.
Command gen-mtls generates a demo CA, a server certificate, and an mTLS client certificate carrying an AIC extension, for the mtls-backend example.
smoke-verify command
Command smoke-verify runs a minimal aic-verifier-protected HTTP service against a real varwof PKI (no demo CA), for smoke testing the SDK.
Command smoke-verify runs a minimal aic-verifier-protected HTTP service against a real varwof PKI (no demo CA), for smoke testing the SDK.
supervision-demo
Package superv provides the shared supervision demonstration helpers used by the mtls-backend and bearer-jwt-backend examples: a DemoApprover that implements aicverifier.ApprovalRequester and a wire helper that turns the aic-verifier supervision config (policy + store + evidence export + trigger) into a runnable demo.
Package superv provides the shared supervision demonstration helpers used by the mtls-backend and bearer-jwt-backend examples: a DemoApprover that implements aicverifier.ApprovalRequester and a wire helper that turns the aic-verifier supervision config (policy + store + evidence export + trigger) into a runnable demo.
Package mcp implements an AIC-gated MCP (Model Context Protocol) server for aic-verifier: an HTTP/Streamable transport carrying an embedded MCP server (github.com/mark3labs/mcp-go), guarded by the aic-verifier admission pipeline.
Package mcp implements an AIC-gated MCP (Model Context Protocol) server for aic-verifier: an HTTP/Streamable transport carrying an embedded MCP server (github.com/mark3labs/mcp-go), guarded by the aic-verifier admission pipeline.

Jump to

Keyboard shortcuts

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