Documentation
¶
Overview ¶
Package delegation implements the github-repository-delegation-v1 control-plane contract described by github/gh-aw-firewall ADR 0001 ("Agent enclave repository admission"). It lets AWF create/confirm and revoke short-lived, invocation-scoped mcpg identities for dynamically admitted agent enclaves without adding, removing, or mutating any configured MCP backend, route, or tool.
Index ¶
- Constants
- func DelegatedTools() []string
- func IsCanonicalRepositorySelector(selector string) bool
- func IsDelegatedTool(tool string) bool
- type AuditEvent
- type ControlCapability
- type CreateOrConfirmRequest
- type Envelope
- type Identity
- type IdentityResult
- type Store
- func (s *Store) Authorize(executorBearer, runID, enclaveBackend, repository, tool string) error
- func (s *Store) AuthorizeExecutor(executorBearer, repository, tool string) error
- func (s *Store) CreateOrConfirm(req CreateOrConfirmRequest) (*IdentityResult, error)
- func (s *Store) IsRecoveryIncomplete() bool
- func (s *Store) MarkReconciled()
- func (s *Store) Revoke(handle string) error
- func (s *Store) RevokeByLabels(runID, enclaveEntryID string) int
- func (s *Store) SaveState(path string) error
- func (s *Store) Snapshot() map[string]Identity
Constants ¶
const EnvControlCapabilityKey = "MCP_GATEWAY_DELEGATION_CONTROL_KEY"
EnvControlCapabilityKey is the environment variable holding the AWF-only delegation-control capability value minted by the compiler at startup. Primary and enclave agents must never receive this value: it is only ever read by the process hosting the controller and compared against inbound requests on the private awf-enclave-mcp-control channel.
const EnvControlListenAddr = "MCP_GATEWAY_DELEGATION_CONTROL_LISTEN"
EnvControlListenAddr is the private listener address for the AWF control channel. It must not be shared with the executor-facing proxy listener.
const ToolPolicyGitHubRepositoryReadV1 = "github-repository-read-v1"
ToolPolicyGitHubRepositoryReadV1 is the only delegated tool policy this package understands: a closed allowlist of repository-scoped read-only GitHub tools (list_issues and issue_read).
Variables ¶
This section is empty.
Functions ¶
func DelegatedTools ¶
func DelegatedTools() []string
DelegatedTools returns the exact, closed set of tools permitted by github-repository-read-v1. Each call allocates a fresh slice; callers on a hot path (such as per-call authorization checks) should use IsDelegatedTool instead.
func IsCanonicalRepositorySelector ¶
IsCanonicalRepositorySelector reports whether selector is already the exact canonical ASCII byte sequence required by the ADR:
^[a-z0-9](?:[a-z0-9-]{0,38})/(?!\.\.?$)(?!.*\.\.)[a-z0-9._-]{1,100}$
There is no trimming, case folding, Unicode normalization, URL decoding, or alternate syntax: callers must reject any selector for which this returns false rather than attempt to normalize it.
func IsDelegatedTool ¶
IsDelegatedTool reports whether tool is a member of the closed github-repository-read-v1 tool set, without allocating.
Types ¶
type AuditEvent ¶
type AuditEvent struct {
Operation string // "create_or_confirm", "confirm", "revoke", "revoke_by_labels", "expire", "reconcile"
RunIDHash string
EnclaveEntryID string
InvocationID string
RepositoryHash string
HandleHash string
PolicyGen uint64
Outcome string // "admitted", "confirmed", "denied", "revoked", "mismatch", "expired"
Reason string // coarse, non-identifying reason code only
}
AuditEvent is a redacted lifecycle record for one delegation-control operation. Per the ADR, EnclaveEntryID and InvocationID are structural correlation labels the compiler already assigns and are retained in the clear so operators can reconcile a run's dynamic state; they never carry a repository name, credential, or header. Every value that could disclose an identity, a repository selector, or an idempotency key (RunIDHash, RepositoryHash, HandleHash) is hashed before it reaches this struct, so AuditEvent as a whole never carries a bearer value, header, or unredacted private repository name.
type ControlCapability ¶
type ControlCapability struct {
// contains filtered or unexported fields
}
ControlCapability authenticates inbound control-plane requests against the single AWF-only capability value installed for this run. It intentionally has no relationship to per-invocation Claims verified by internal/enclavegithub: that verifier authenticates enclave executors to the GitHub read proxy, while ControlCapability authenticates only AWF to the delegation controller itself.
func NewControlCapability ¶
func NewControlCapability(secret string) (*ControlCapability, error)
NewControlCapability creates a capability verifier from a non-empty secret value. The secret is never retained in comparable form: only its SHA-256 digest is stored, so a leaked process memory dump of this struct cannot be used to recover or forge the capability value.
func (*ControlCapability) Authenticate ¶
func (c *ControlCapability) Authenticate(authorizationHeader string) error
Authenticate verifies an Authorization header value against the capability. It accepts the header value with a leading Bearer-scheme prefix (stripped before comparison) or as a bare value, per MCP spec 7.1. Comparison is constant-time over fixed-size digests to avoid leaking the capability's length or contents through timing.
type CreateOrConfirmRequest ¶
type CreateOrConfirmRequest struct {
// RunID must equal the envelope's bound workflow run.
RunID string `json:"run_id"`
// EnclaveBackend must equal the envelope's single AWF enclave backend.
EnclaveBackend string `json:"enclave_backend"`
// EnclaveEntryID identifies the enclave entry (frontmatter block) this
// invocation belongs to.
EnclaveEntryID string `json:"enclave_entry_id"`
// InvocationID identifies one bounded enclave invocation.
InvocationID string `json:"invocation_id"`
// Repository is the canonical, exact-byte owner/repo selector chosen
// for this invocation. It must already be canonical: the Store performs
// no trimming, case folding, Unicode normalization, or URL decoding.
Repository string `json:"repository"`
// ToolPolicy must equal ToolPolicyGitHubRepositoryReadV1.
ToolPolicy string `json:"tool_policy"`
// SchemaHash is the finite response schema hash approved for this
// invocation; it must be a member of the envelope's allowed set.
SchemaHash string `json:"schema_hash"`
// AdmittedDefaultBranchSHA is the default-branch SHA AWF resolved
// during live-read admission, when known at request time.
AdmittedDefaultBranchSHA string `json:"admitted_default_branch_sha,omitempty"`
// RequestedTTL bounds how long the identity should live; it is capped
// by (and must not exceed) the envelope's MaxIdentityTTL.
RequestedTTL time.Duration `json:"requested_ttl"`
// InvocationExpiresAt is the absolute deadline of the invocation, when
// one is supplied by AWF. An identity can never outlive this deadline.
InvocationExpiresAt time.Time `json:"invocation_expires_at,omitempty"`
// IdempotencyKey deduplicates retried create/confirm calls for the same
// (RunID, EnclaveEntryID, InvocationID, Repository) tuple.
IdempotencyKey string `json:"idempotency_key"`
}
CreateOrConfirmRequest is an AWF-authenticated request to create or confirm exactly one delegated identity. Every field must be a strict subset of the compiler-installed Envelope; the Store rejects anything wider than the envelope allows.
type Envelope ¶
type Envelope struct {
// RunID is the workflow run this envelope, and every identity minted
// from it, is bound to.
RunID string `json:"run_id"`
// EnclaveBackend is the single AWF enclave backend identities may be
// bound to.
EnclaveBackend string `json:"enclave_backend"`
// AllowedRepositories is the closed set of canonical owner/repo
// selectors the compiler admitted for this run. Selectors are compared
// as exact ASCII byte sequences; no normalization is performed.
AllowedRepositories []string `json:"allowed_repositories"`
// ToolPolicy is the single delegated tool policy this envelope allows.
// Only ToolPolicyGitHubRepositoryReadV1 is currently supported.
ToolPolicy string `json:"tool_policy"`
// AllowedSchemaHashes is the closed set of finite response schema
// hashes the compiler approved for this run.
AllowedSchemaHashes []string `json:"allowed_schema_hashes"`
// MaxIdentityTTL bounds how long any single delegated identity may
// live, and therefore how long an executor bearer remains valid.
MaxIdentityTTL time.Duration `json:"max_identity_ttl"`
// ExpiresAt is the envelope's own absolute expiry, no later than the
// workflow job lifetime. No identity may be created once the envelope
// itself has expired.
ExpiresAt time.Time `json:"expires_at"`
}
Envelope is the compiler-installed, compiler-bounded policy envelope bootstrapped into the controller at gateway startup. Every delegated identity must be a strict subset of this envelope; the controller rejects any request for a server, tool, repository, credential, backend URL, or guard policy outside it. The compiler is not a runtime service: once installed the envelope is immutable for the lifetime of the process.
func (*Envelope) AllowsRepository ¶
AllowsRepository reports whether repo is an exact-byte member of the envelope's admitted repository set.
func (*Envelope) AllowsSchemaHash ¶
AllowsSchemaHash reports whether schemaHash is an exact-byte member of the envelope's admitted schema hash set.
type Identity ¶
type Identity struct {
Handle string `json:"handle"`
ExecutorBearer string `json:"executor_bearer"`
RunID string `json:"run_id"`
EnclaveBackend string `json:"enclave_backend"`
EnclaveEntryID string `json:"enclave_entry_id"`
InvocationID string `json:"invocation_id"`
Repository string `json:"repository"`
ToolPolicy string `json:"tool_policy"`
SchemaHash string `json:"schema_hash"`
AdmittedDefaultBranchSHA string `json:"admitted_default_branch_sha,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
InvocationExpiresAt time.Time `json:"invocation_expires_at,omitempty"`
PolicyGeneration uint64 `json:"policy_generation"`
IdempotencyKey string `json:"idempotency_key"`
CreatedAt time.Time `json:"created_at"`
Revoked bool `json:"revoked"`
}
Identity is one invocation-scoped delegated identity bound to a single canonical repository under github-repository-read-v1.
type IdentityResult ¶
type IdentityResult struct {
Handle string `json:"handle"`
ExecutorBearer string `json:"executor_bearer"`
Repository string `json:"repository"`
ToolPolicy string `json:"tool_policy"`
Tools []string `json:"tools"`
AdmittedDefaultBranchSHA string `json:"admitted_default_branch_sha,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
}
IdentityResult is returned to AWF on a successful create or confirm call. It intentionally excludes any field not required by the executor: no credentials, headers, or repository contents are ever included.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store atomically creates/confirms and revokes delegated identities for one compiler-installed Envelope. It is safe for concurrent use: every mutating operation is serialized so that concurrent create/confirm/revoke calls for the same idempotency key are atomic and generation-safe.
func LoadStore ¶
LoadStore reconstructs a Store from a prior SaveState file. If path does not exist, this is a fresh start (no prior state to reconcile) and an empty, fully-reconciled Store is returned. If path exists but is corrupt, truncated, or fails checksum verification, an empty Store is returned with recoveryIncomplete set: callers must fail closed for new dynamic admissions until MarkReconciled is called after an operator confirms outstanding identities are safe to disregard.
Identities that already expired at load time are dropped silently: their absence is ordinary lifecycle behavior, not incomplete reconstruction.
func NewStore ¶
NewStore creates an empty Store bound to envelope. generation is the compiler-supplied policy generation for this envelope installation; it is stamped onto every identity minted from this Store so a later envelope replacement can be distinguished from stale identities.
func (*Store) Authorize ¶
Authorize enforces that executorBearer is a live identity bound to exactly the given run, enclave backend, repository, and tool. It rejects replayed, expired, revoked, wrong-repository, wrong-tool, and wrong-run identities; none of those can establish or continue a session.
func (*Store) AuthorizeExecutor ¶
AuthorizeExecutor authorizes an executor bearer against the repository and tool derived by the data-plane route. The run and backend are read from the authenticated identity rather than supplied by the executor.
func (*Store) CreateOrConfirm ¶
func (s *Store) CreateOrConfirm(req CreateOrConfirmRequest) (*IdentityResult, error)
CreateOrConfirm atomically creates a new delegated identity, or confirms an existing one for the same idempotency scope. A repeated call for the same (run, enclave entry, invocation, idempotency key) tuple returns the exact same binding, expiry, admitted default-branch SHA, opaque handle, and executor bearer. A request whose binding differs from the stored identity is terminal: the stored identity is revoked and an error is returned.
func (*Store) IsRecoveryIncomplete ¶
IsRecoveryIncomplete reports whether restart reconstruction left this Store unable to vouch for prior state. Callers must fail closed for new dynamic admissions while this is true.
func (*Store) MarkReconciled ¶
func (s *Store) MarkReconciled()
MarkReconciled clears the recovery-incomplete flag once an operator or automated process has confirmed outstanding state is safe to resume from.
func (*Store) Revoke ¶
Revoke idempotently revokes the identity bound to handle. Revoking an unknown or already-revoked handle is not an error.
func (*Store) RevokeByLabels ¶
RevokeByLabels idempotently revokes every identity bound to (runID, enclaveEntryID) and returns how many identities were revoked. It is used both for explicit label-scoped revocation and for shutdown/reconciliation cleanup.
func (*Store) SaveState ¶
SaveState persists every currently live identity to path so the controller can reconstruct labelled live delegations after a restart. The file is written with 0600 permissions because it contains executor bearer secrets. A trailing SHA-256 checksum lets LoadStore detect truncation or corruption and fail closed instead of silently reconstructing partial state.