envelope

package
v0.0.0-...-0879ac6 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package envelope provides authenticated trusted control envelopes so a worker can accept a legitimate coordinator control message (for example a scope correction) without treating it as prompt injection.

Provider/repository text is untrusted free-form data and can never forge an envelope. Only a MAC-valid envelope bound to the active task, lease generation, and worker session is trusted. Receivers fail closed: spoofed, replayed, cross-task, or stale-generation envelopes are rejected or force an observable BLOCKED state rather than silently continuing the old scope.

FAC-133 / live incident: a FAC-146 worker rejected a valid orchestrator scope correction as "prompt injection" because content heuristics ran before provenance. Trust is the signature and binding fields, never body text that happens to mention injection, secrets, or shell commands.

Index

Constants

View Source
const (
	RoleOrchestrator = "orchestrator"
	RoleCoordinator  = "coordinator"
	RoleAuditor      = "auditor"
)

Known issuer roles that may mint control envelopes under policy.

View Source
const DefaultMaxClockSkew = 2 * time.Minute

DefaultMaxClockSkew is how far IssuedAt/ExpiresAt may drift from local now.

View Source
const DefaultPolicyAuthority = "herd.control.v1"

DefaultPolicyAuthority is the control-plane policy that may issue trusted instructions. Receivers reject any other authority.

View Source
const DefaultTTL = 15 * time.Minute

DefaultTTL is how long a freshly issued envelope remains acceptable.

Variables

View Source
var (
	ErrMissingSecret      = errors.New("envelope: secret is required (fail-closed)")
	ErrMissingBinding     = errors.New("envelope: session binding incomplete (fail-closed)")
	ErrInvalidSignature   = errors.New("envelope: invalid signature")
	ErrUnknownKind        = errors.New("envelope: unknown control kind")
	ErrUnauthorizedIssuer = errors.New("envelope: issuer role not authorized")
	ErrAuthorityMismatch  = errors.New("envelope: policy authority mismatch")
	ErrTaskMismatch       = errors.New("envelope: target task mismatch")
	ErrWorkerMismatch     = errors.New("envelope: target worker session mismatch")
	ErrStaleGeneration    = errors.New("envelope: stale lease generation")
	ErrReplay             = errors.New("envelope: replay or non-monotonic sequence")
	ErrDuplicateID        = errors.New("envelope: envelope id already seen")
	ErrDuplicateNonce     = errors.New("envelope: nonce already seen")
	ErrExpired            = errors.New("envelope: envelope expired or outside clock skew")
	ErrMissingFields      = errors.New("envelope: required fields missing")
	ErrSessionBlocked     = errors.New("envelope: session is BLOCKED; rebind required")
	ErrConflict           = errors.New("envelope: conflicting control instruction")
	ErrNotControl         = errors.New("envelope: input is not a trusted control envelope")
	ErrEmptyScope         = errors.New("envelope: scope correction requires scope or body")
)

Sentinel errors for typed fail-closed paths.

Functions

func DefaultAllowedIssuerRoles

func DefaultAllowedIssuerRoles() map[string]struct{}

DefaultAllowedIssuerRoles is the policy allowlist for control issuers.

func EqualScope

func EqualScope(a, b *Scope) bool

EqualScope reports whether a and b describe the same scope.

func FormatSig

func FormatSig(sum []byte) string

FormatSig builds a "sha256=<hex>" signature string (tests / tooling).

func LoadDurableSession

func LoadDurableSession(path string, cfg SessionConfig) (*Session, *DurableSessionState, error)

LoadDurableSession loads session state. Missing file → fresh session. Corrupt JSON or identity mismatch against cfg is fail-closed.

func ParseUntrusted

func ParseUntrusted(raw []byte) (*Envelope, TrustClass, error)

ParseUntrusted attempts to decode provider/repository text that claims to be a control envelope. It never returns TrustControl: even well-formed JSON is untrusted until Session.Receive verifies the MAC under the worker secret. This is the explicit API for "card text that looks like a control message".

func SaveDurableSession

func SaveDurableSession(path string, sess *Session) error

SaveDurableSession persists session after apply/block with flock + fsync. When the caller already holds WithSessionFileLock, use SaveDurableSessionLocked.

func SaveDurableSessionLocked

func SaveDurableSessionLocked(path string, sess *Session) error

SaveDurableSessionLocked writes session state; caller must hold the session lock.

func SessionStatePath

func SessionStatePath(root, workerSession, task string) string

SessionStatePath returns durable path for a worker control session.

func Sign

func Sign(secret []byte, e *Envelope) error

Sign attaches Signature using HMAC-SHA256 over Canonical(). Fail-closed on empty secret or incomplete envelope.

func VerifyMAC

func VerifyMAC(secret []byte, e *Envelope) bool

VerifyMAC reports whether e.Signature is a valid HMAC-SHA256 of Canonical() under secret. Empty secret, empty/malformed signature, or MAC mismatch all return false (fail-closed). Comparison is constant-time via hmac.Equal.

func WithSessionFileLock

func WithSessionFileLock(path string, fn func() error) error

WithSessionFileLock holds an exclusive cross-process lock on the session state file for the duration of fn (load/apply/save transaction).

Types

type Decision

type Decision struct {
	Status       Status
	Reason       string
	Trust        TrustClass
	EnvelopeID   string
	Sequence     uint64
	AppliedScope *Scope
	SessionState SessionState
}

Decision is the structured result of receiving a candidate control message. Callers gate on Status; Reason is diagnostic only.

type DurableIssuerStore

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

DurableIssuerStore persists monotonic lastSeq per (worker,task) with a cross-process file lock, unique temp, and fsync (FAC-133 re-admission).

func NewDurableIssuerStore

func NewDurableIssuerStore(path string) (*DurableIssuerStore, error)

NewDurableIssuerStore opens/creates a JSON state file under path.

func (*DurableIssuerStore) NextSeq

func (s *DurableIssuerStore) NextSeq(worker, task string) (uint64, error)

NextSeq returns the next monotonic sequence for worker+task and persists it.

type DurableSessionState

type DurableSessionState struct {
	WorkerSession   string          `json:"worker_session"`
	Task            string          `json:"task"`
	LeaseGeneration int64           `json:"lease_generation"`
	LastSeq         uint64          `json:"last_seq"`
	LastAppliedID   string          `json:"last_applied_id"`
	State           SessionState    `json:"state"`
	BlockReason     string          `json:"block_reason,omitempty"`
	SeenIDs         map[string]bool `json:"seen_ids,omitempty"`
	SeenNonces      map[string]bool `json:"seen_nonces,omitempty"`
	Scope           *Scope          `json:"scope,omitempty"`
	ExpectedIssuer  string          `json:"expected_issuer_session,omitempty"`
}

DurableSessionState is persisted worker control state.

type Envelope

type Envelope struct {
	Version             string `json:"v"`
	ID                  string `json:"id"`
	Kind                Kind   `json:"kind"`
	Sequence            uint64 `json:"seq"`
	Nonce               string `json:"nonce"`
	IssuerRole          string `json:"issuer_role"`
	IssuerSession       string `json:"issuer_session"`
	PolicyAuthority     string `json:"policy_authority"`
	TargetTask          string `json:"target_task"`
	LeaseGeneration     int64  `json:"lease_generation"`
	TargetWorkerSession string `json:"target_worker_session"`
	IssuedAtUnix        int64  `json:"issued_at"`
	ExpiresAtUnix       int64  `json:"expires_at"`
	// Body may contain text that resembles prompt injection. Authenticity is
	// the MAC + binding fields, never a content heuristic over Body.
	Body      string `json:"body"`
	Scope     *Scope `json:"scope,omitempty"`
	Signature string `json:"sig"`
}

Envelope is the authenticated control message. Signature is computed over Canonical() and is never part of the signed material itself.

func (*Envelope) Canonical

func (e *Envelope) Canonical() []byte

Canonical returns the deterministic byte sequence that is MAC'd. Signature is excluded. Field order is fixed so neither JSON key reordering nor body splicing can preserve a captured MAC.

Format (newline-separated, no trailing newline after last field):

v1
id=...
kind=...
seq=...
nonce=...
issuer_role=...
issuer_session=...
policy_authority=...
target_task=...
lease_generation=...
target_worker_session=...
issued_at=...
expires_at=...
body_sha256=...
scope=...

func (*Envelope) ValidateUnsigned

func (e *Envelope) ValidateUnsigned() error

ValidateUnsigned checks structural completeness before signing or verifying. It does not check the MAC.

type IssueOpts

type IssueOpts struct {
	Kind                Kind
	TargetTask          string
	LeaseGeneration     int64
	TargetWorkerSession string
	Body                string
	Scope               *Scope
	// Sequence, when non-zero, overrides the issuer's auto-increment (tests).
	Sequence uint64
	// TTL bounds ExpiresAt; zero uses DefaultTTL.
	TTL time.Duration
	// ID and Nonce, when empty, are random 128-bit hex strings.
	ID    string
	Nonce string
}

IssueOpts are the per-message fields a coordinator supplies when minting a control envelope. Sequence is assigned monotonically by the Issuer per (target worker session, target task) unless Sequence is non-zero (tests).

type Issuer

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

Issuer mints MAC-signed control envelopes. It is the coordinator-side producer; workers never issue.

func NewIssuer

func NewIssuer(secret, role, session string) (*Issuer, error)

NewIssuer builds a fail-closed Issuer. Empty secret/role/session is refused.

func (*Issuer) Issue

func (i *Issuer) Issue(opts IssueOpts) (*Envelope, error)

Issue creates, sequences, and signs a control envelope bound to opts.

type Kind

type Kind string

Kind identifies the control instruction family.

const (
	// KindScopeCorrection narrows or reasserts the worker's exclusive package
	// allowlist / scope note without elevating merge or credential authority.
	KindScopeCorrection Kind = "scope.correction"
)

type Scope

type Scope struct {
	PackageAllowlist []string `json:"package_allowlist,omitempty"`
	Exclusive        bool     `json:"exclusive"`
	Note             string   `json:"note,omitempty"`
}

Scope is the structured payload of a scope correction. Exclusive means the worker must not expand beyond PackageAllowlist (when non-empty).

func CloneScope

func CloneScope(s *Scope) *Scope

CloneScope returns a deep copy of s (nil-safe).

type Session

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

Session is the production consumer of control envelopes. A worker holds one Session for its active task/lease and feeds every candidate control message through Receive / ReceiveJSON. Free-form provider text is never elevated: only MAC-valid, binding-matched envelopes change scope.

On stale generation or conflicting control, Session transitions to StateBlocked and freezes the last applied scope until Rebind advances the lease generation (or an explicit ClearBlock after operator reconcile).

func NewSession

func NewSession(cfg SessionConfig) (*Session, error)

NewSession constructs a fail-closed worker control session.

func (*Session) ClearBlock

func (s *Session) ClearBlock()

ClearBlock is an operator reconcile path that unblocks without advancing generation. Prefer Rebind when the lease generation actually changed.

func (*Session) CurrentScope

func (s *Session) CurrentScope() *Scope

CurrentScope returns a copy of the last applied scope (nil if none).

func (*Session) LastSequence

func (s *Session) LastSequence() uint64

LastSequence returns the highest applied sequence.

func (*Session) Rebind

func (s *Session) Rebind(leaseGeneration int64) error

Rebind advances the active lease generation (e.g. after reclaim) and clears BLOCKED so the worker can accept control for the new generation. Generation must strictly increase.

func (*Session) Receive

func (s *Session) Receive(e *Envelope) (*Decision, error)

Receive is the production consumer entry point: verify provenance and bindings, then apply a scope correction or fail closed. Body text that resembles prompt injection does NOT cause rejection when the MAC and bindings are valid — that is the FAC-133 incident fix.

func (*Session) ReceiveJSON

func (s *Session) ReceiveJSON(raw []byte) (*Decision, error)

ReceiveJSON unmarshals raw JSON then Receive. Malformed JSON is untrusted rejection, never control.

func (*Session) State

func (s *Session) State() (SessionState, string)

State returns the current session state and block reason (if any).

type SessionConfig

type SessionConfig struct {
	Secret             string
	WorkerSession      string
	Task               string
	LeaseGeneration    int64
	PolicyAuthority    string // empty → DefaultPolicyAuthority
	MaxClockSkew       time.Duration
	AllowedIssuerRoles map[string]struct{}
	// ExpectedIssuerSession, when set, must match envelope.IssuerSession.
	ExpectedIssuerSession string
	// Now overrides the clock (tests).
	Now func() time.Time
}

SessionConfig binds a worker process to the control plane. All identity fields are required; construction fails closed without them.

type SessionState

type SessionState string

SessionState is the durable control state a worker holds.

const (
	// StateActive: worker may apply new verified control.
	StateActive SessionState = "active"
	// StateBlocked: control plane failed closed; scope is frozen until rebind.
	StateBlocked SessionState = "blocked"
)

type Status

type Status string

Status is the structured outcome of Session.Receive.

const (
	// StatusApplied: envelope verified and applied (scope updated).
	StatusApplied Status = "applied"
	// StatusRejected: fail-closed refuse; session stays Active (or remains Blocked).
	StatusRejected Status = "rejected"
	// StatusBlocked: stale/conflicting control forces observable BLOCKED.
	StatusBlocked Status = "blocked"
	// StatusDuplicate: exact-id redelivery of an already-applied envelope.
	StatusDuplicate Status = "duplicate"
)

type TrustClass

type TrustClass string

TrustClass is how a receiver classifies input before (or after) verification.

const (
	// TrustUntrusted is provider/repo/free-form text. It is never control.
	TrustUntrusted TrustClass = "untrusted"
	// TrustControl is a MAC-valid envelope bound to the active session.
	TrustControl TrustClass = "control"
)

func Classify

func Classify(_ string) TrustClass

Classify reports the trust class of free-form input without elevating it. Any non-empty provider/repo text is TrustUntrusted. Only a Session.Receive path that verifies a MAC can produce TrustControl.

Jump to

Keyboard shortcuts

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