RFC: Hook + Plugin System for Guardian Gateway
Status: Foundation implemented; project-scoped bootstrap extension implemented by GAT-221
Last updated: 2026-07-10
Applies to: llm-gateway/service (runtime hooks) and control-plane/service (plugin management + snapshot activation)
1. Summary
Section titled “1. Summary”This RFC defines a production-grade lifecycle hook and marketplace plugin system for llm-gateway/service.
The system supports:
- first-party plugins (native Go)
- curated marketplace custom plugins (WASM, runtime-loaded without gateway rebuild)
- deterministic execution with strict safety defaults
- immutable, signed, control-plane-managed snapshots as the only runtime policy source
Canonical lifecycle hook names are:
request.startproject.bootstrapproxy.preproxy.postrequest.end
project.bootstrap is a typed host stage, not a general-purpose hook: for a bound project candidate it runs only the mandatory premade builtin.project_membership entry from that project’s pinned snapshot. Ordinary project plugins remain unavailable until Gateway validates the typed bootstrap result and establishes authoritative project context. The other hooks retain the generic event-envelope contract.
Plugins may also perform bounded, read-only, host-mediated CID resolution when cid.resolve is granted by snapshot policy. This capability reads from the gateway blob store, reports request-reference and host-verification status as metadata, and is not general network access.
Plugins may also perform host-managed outbound HTTP(S) requests to policy-allowed destinations and opt-in full request body reads when granted by snapshot policy. Direct/raw runtime network access remains disallowed.
The deny response envelope remains unchanged (code, message, correlation_id).
1.1 Foundation Status (GAT-66)
Section titled “1.1 Foundation Status (GAT-66)”The gateway/control-plane foundation covered by GAT-66 is now implemented:
- deterministic premade + WASM hook execution in the gateway
- control-plane-managed artifact, definition, snapshot, and activation APIs
- direct object-storage-backed artifact loading with proxied control-plane uploads
- first-party
builtin.authorizationas the baseline end-to-end authorization plugin - durable async
request.enddispatch with retry, drop accounting, and DLQ persistence - persisted last-known-good snapshot metadata plus local artifact cache for cold-start recovery
- queue and hook runtime metrics plus end-to-end WASM and representative plugin-chain test coverage
Remaining feature work for the hook/plugin platform now belongs to follow-on plugin tickets rather than the core foundation issue.
2. Goals
Section titled “2. Goals”- Enable deterministic, pluggable lifecycle policy around proxied LLM requests.
- Run first-party and custom plugins in one ordered pipeline.
- Allow custom plugin deployment without gateway recompilation.
- Keep deny envelope compatibility and preserve verification guarantees.
- Persist plugin decisions/events for audit and operational visibility.
- Keep plugin-induced overhead within
<=20ms p95under representative load. - Establish a generic event contract that can expand to non-proxy surfaces later.
- Allow bounded host-mediated CID resolution, outbound
HTTP(S)access, and opt-in request body access without granting raw plugin runtime network egress.
3. Non-Goals (V1)
Section titled “3. Non-Goals (V1)”- Hooking non-proxy endpoints (
/health, registration, blob/state/history) in V1 runtime. - Plugin mutation of request/response bodies or headers.
- Per-chunk streaming hook callbacks and chunk-time stream termination.
- Plugin-managed durable local state across requests.
- Request-time policy overlays outside active snapshot policy.
- Direct/raw plugin runtime network egress or arbitrary transport access.
- Multiple project policy scopes or switching project snapshots after bootstrap. Pre-verification
request.startstill uses the environment runtime because no safe candidate exists yet; after binding, the governed sequence pins exactly one project snapshot, or the environment default for projectless traffic. - Remote sidecar/plugin RPC execution in V1.
- Full response body access in V1.
4. Current State (Guardian Gateway)
Section titled “4. Current State (Guardian Gateway)”Current proxy path in llm-gateway/service is:
- Correlation ID assignment
- Route/provider recognition from the inbound gateway path
- Request body read/restore
request.start- Signature/profile/attestation/registry/replay verification
- Bind any project presentation to a non-authoritative candidate
- Pin the exact project snapshot and run
project.bootstrap, or pin the environment-default snapshot for an explicitly projectless request - Establish authoritative project context only from a validated typed bootstrap success
- Provider target resolution and finalized upstream metadata
proxy.pre- Upstream proxy dispatch
proxy.post- Audit capture plus async
request.endenqueue from the same pin
Primary extensibility is now the snapshot-driven hook/plugin runtime. The legacy Authorizer interface remains only as compatibility/fallback plumbing; in the shipped gateway binary it is wired as a noop allow authorizer.
5. Deliberate Design Direction
Section titled “5. Deliberate Design Direction”Compared to common gateway hook implementations, Guardian chooses:
- Runtime-doc contract parity as a hard requirement.
- Deterministic sequential execution with first-deny short-circuit.
- Snapshot-driven runtime plugin loading.
- Strict snapshot validation (
supported_hooks, config schema, timeout limits, artifact integrity). - No body/header mutation in V1.
- Signed control-plane snapshot as sole runtime policy source.
- Curated marketplace with signature-verified artifacts.
- Host-mediated privileged capabilities (
cid.resolve,request.body.read,http.request) instead of raw runtime privileges.
6. Scope and Phased Delivery
Section titled “6. Scope and Phased Delivery”In scope:
- proxy lifecycle hooks in
llm-gateway/service - first-party plugins in native Go
- custom plugins as in-process WASM modules (
wazero) - control-plane APIs/persistence for artifacts, definitions, snapshots, activation
- gateway runtime sync and atomic active snapshot swap
- shared host-managed capabilities for CID resolution, outbound
HTTP(S)calls, and opt-in full request body reads - plugin-aware audit/metrics/logs
V1 lifecycle coverage:
request.start(pre-verify)project.bootstrap(post-verify, pre-project-context, premade typed membership decision only)proxy.pre(post-verify, pre-proxy authorization)proxy.postonce per response (non-stream and stream headers stage only)request.endasync, durable dispatch
Deferred to V2+:
- per-chunk streaming callbacks
- chunk-stage stream termination policy
- side-effect sink dispatch beyond internal event persistence
- remote sidecar / RPC plugin execution model
7. Architecture
Section titled “7. Architecture”7.1 Gateway components
Section titled “7.1 Gateway components”PluginManager: environment snapshot lifecycle plus bounded concurrency-safe compilation and immutable pinning for exact project snapshots, executor lifecycle, sync loop, artifact verificationHookEngine: stage orchestration and deterministic plugin executionCapabilityBroker: host-mediated capability enforcement, outbound HTTP dispatch, secret injection, size/time budgets, and capability telemetryPremadeExecutor: native Go plugin executionWASMExecutor:wazeroruntime execution for custom pluginsEndHookWorker: durable async queue, retry, DLQ, and persistence forrequest.end
7.2 Control-plane components
Section titled “7.2 Control-plane components”- artifact registration and publishing metadata
- plugin definition CRUD
- immutable snapshot creation
- admin-only snapshot activation and current-snapshot reads
7.3 Policy scope
Section titled “7.3 Policy scope”- One environment-default active snapshot plus independently activated snapshots keyed by
environment + project_urn. - Snapshot activation is manual and atomic for new resolutions; an in-flight request retains its pinned identity across every stage.
7.4 Initial first-party plugins
Section titled “7.4 Initial first-party plugins”V1 foundation is anchored around builtin.authorization, which serves as the primary proxy authorization plugin and exercises the same snapshot, capability, audit, and activation contract as other plugins.
GAT-221 adds premade builtin.project_membership as the mandatory typed bootstrap policy for every project-scoped snapshot. It reuses the shared ProjectMembershipV1 validator and cannot be replaced by WASM or generic context writes.
Planned follow-on first-party plugins on the same shared model include:
- OPA/Rego evaluation plugin
- intent / VirusTotal plugin
Customer customization for these targets is configuration/policy driven, not customer-authored plugin code. The dedicated OPA/Rego builtin design is specified separately in rfc-hook-plugin-system-opa-rego-builtin.md.
8. Lifecycle Semantics (V1)
Section titled “8. Lifecycle Semantics (V1)”Hooks are scoped per proxied request.
Execution order:
- route/provider recognition from the inbound gateway path
request.start(pre-verify)- core verify
- core binds an optional project candidate and pins one immutable snapshot
project.bootstrapfor a project candidate; projectless requests skip it- Gateway validates the typed membership result and, on success, establishes protected project context
- provider target resolution and finalized upstream metadata
proxy.pre(includingbuiltin.authorizationwhen configured in the pinned snapshot)- proxy upstream
proxy.post- persist request + sync hook events
request.end(async durable, from the same pin)
Stage behavior:
request.start:- input is untrusted request metadata and bounded request preview
- for recognized LLM proxy routes, input also includes resolved
provider,operation,gateway_path,upstream_path, andupstream_urlwhen provider target resolution is already available - CID resolution, outbound
HTTP(S), and full request body read are available only when granted by snapshot policy - data derived from request-supplied headers/body or external lookups remains untrusted unless later verified by host logic
- may deny (enforcement mode)
project.bootstrap:- receives only the typed, core-bound candidate and locally available membership evidence
- executes exactly one premade
builtin.project_membershipentry in enforcement host mode; its own config chooses policyobserveorenforce - grants no host capabilities and performs no external network calls
- only a Gateway-validated typed success may establish authoritative project context; generic
context_writesare never membership authority
proxy.pre:- input includes verified identity (
AgentID,SignerKeyID) plus resolved route/provider metadata - host capabilities remain available when granted; verified identity/attestation metadata may be combined with external lookups for policy
- may deny
- input includes verified identity (
proxy.post:- may deny only before response commit
- V1 execution points: non-stream once, stream headers stage once
- input includes
stream_requestedandstream_observed; response preview is exposed only when streaming was not actually observed - no body/header mutation and no full response body capability in V1
- host-managed
HTTP(S)calls remain allowed when granted but must complete before response commit
request.end:- runs after response path completion
- is observability-only; deny decisions are recorded for audit and rollout analysis but never affect the already-sent response
- never blocks response completion
- host-managed
HTTP(S)calls remain allowed under durable async execution policy
Host capabilities are available in all lifecycle hooks only when granted by snapshot policy. ResolveCID is host-mediated, read-only, backed by the gateway blob store, and bounded by the cid.resolve grant. Request-reference and host-verification status are returned as metadata on CIDResolution; they are not the resolution authorization boundary. Outbound HTTP(S) and request body reads are separate capabilities. Plugins do not receive raw runtime network access.
9. Authorizer Migration
Section titled “9. Authorizer Migration”The primary proxy authorization seam is now the snapshot-configured builtin.authorization first-party plugin running in proxy.pre.
The legacy Authorizer interface remains in the runtime only for compatibility/fallback/shadow plumbing. In the shipped gateway binary it is wired as a noop allow authorizer, so policy enforcement is expected to come from hooks when plugin runtime is enabled and an active snapshot configures builtin.authorization.
10. Plugin Execution Model and Failure Policy
Section titled “10. Plugin Execution Model and Failure Policy”Execution model:
- Deterministic sequential execution by
order_index ASC, thenplugin_definition_id ASC. - First deny from an enforcement plugin short-circuits remaining plugins in that hook.
- No parallel execution within a hook in V1.
Plugin modes:
enforcement: deny is actionableobservability: advisory/shadow mode for rollout safety and signal collection; deny is ignored by host, outcome recorded. It is also the only valid mode forrequest.end.
The project.bootstrap entry is always stored as host enforcement; membership rollout mode belongs inside the strictly validated builtin.project_membership config. This prevents a generic observability wrapper from converting malformed or unavailable bootstrap execution into authorization.
Timeouts and limits:
- Default per-plugin timeout (no outbound
HTTP(S)):10ms. - Plugins granted
http.requestmust declare explicit timeout in snapshot policy. - Hard per-plugin timeout upper bound:
5s. - Per-hook total execution budget is enforced in addition to per-plugin timeout.
- Max enabled entries per hook: default
16, configurable with hard clamp64. - Capability-specific request/response size limits are enforced by host capability policy.
- Snapshot activation fails if limits/validation rules are violated.
Failure policy:
- Enforcement explicit deny: deny with plugin-provided fields or defaults.
- Enforcement timeout/runtime/engine/capability error: fail-closed deny with
code=plugin_execution_errorand default status503unless snapshot policy overrides, reflecting temporary policy-evaluation unavailability rather than an explicit plugin deny. - Observability timeout/runtime/engine/capability error: fail-open, request continues, event marked failed.
- Capability denials/policy violations are returned as host errors and follow the same mode-based failure semantics.
request.enderrors never block response completion.
Deny contract remains unchanged:
{ "code": "...", "message": "...", "correlation_id": "..." }Default deny values when omitted:
status:403code:plugin_deniedmessage:request denied by plugin policy
11. Policy Source and Snapshot Rules
Section titled “11. Policy Source and Snapshot Rules”Policy source:
- Active signed snapshot from control-plane is the only runtime policy source.
- No request-time policy overlay in V1.
- No local static fallback beyond last-known-good snapshot data.
Snapshot activation and bootstrap:
- Manual activation API is required.
- Activation is atomic for new requests.
- Gateway can start without an active snapshot, but operators should still activate a valid baseline signed snapshot before relying on hooks for enforcement.
- On update validation/load failure, gateway keeps last-known-good snapshot active.
Snapshot entry validation rules:
hookmust be in definitionsupported_hooks.config_jsonmust validate against definition schema.- Unknown config fields are rejected.
timeout_msmust be in allowed bounds.- Requested capabilities must be supported by plugin runtime and allowed by snapshot policy.
cid.resolvecapability permits bounded gateway blob-store reads when granted; request-reference status is returned as metadata.request.body.readcapability must declare max readable bytes.http.requestcapability must declare allowed destination policy, method policy, secret refs, timeout, and request/response size limits.- Entry filter operators must be supported (
exact,prefix). - Disabled entries are skipped at compile/activation time.
request.endentries must useobservabilitymode;enforcementis invalid for that async stage.- A project-scoped snapshot must contain exactly one enabled premade
builtin.project_membershipentry atproject.bootstrap, in host enforcement mode and with no host capabilities. Missing, disabled, duplicate, custom, or unmaterializable bootstrap entries invalidate creation, activation, and rollback.
Versioning and pinning:
- Snapshot entries pin exact plugin version.
- Snapshot entries pin artifact digest.
- Floating channels/ranges are not allowed in V1.
- One snapshot ID/version is pinned from project bootstrap through
proxy.pre,proxy.post, audit attribution, andrequest.end. A bound project candidate resolves only its exact project activation with no environment fallback; projectless traffic pins the environment default and skips bootstrap.
12. Plugin Contract
Section titled “12. Plugin Contract”12.1 Canonical names
Section titled “12.1 Canonical names”type HookName string
const ( HookRequestStart HookName = "request.start" HookProxyPre HookName = "proxy.pre" HookProxyPost HookName = "proxy.post" HookRequestEnd HookName = "request.end")12.2 Generic typed event envelope
Section titled “12.2 Generic typed event envelope”type HookEventEnvelope struct { EventName HookName CorrelationID string TimestampUnixMs int64
// Core HTTP metadata (always present where available) Method string GatewayPath string UpstreamPath string Query string GatewayURI string UpstreamURL string
// Route metadata (proxy lifecycle) Provider string Operation string
// Identity metadata (when verified) AgentID string SignerKeyID string // verified RFC 9421 keyid; typically a did:key in V1
// Bounded previews and headers RequestHeaders map[string][]string ResponseHeaders map[string][]string RequestPreview []byte // default max 16KB ResponsePreview []byte // default max 16KB
ResponseStatus int StreamRequested bool StreamObserved bool
// Host-populated immutable context visible to later hooks Context map[string]string}12.3 Decision and event output
Section titled “12.3 Decision and event output”type PluginMode string
const ( ModeEnforcement PluginMode = "enforcement" ModeObservability PluginMode = "observability")
type EmittedEvent struct { Name string Tags map[string]string Body map[string]any}
type HookDecision struct { Allow bool StatusCode int Code string Message string Tags map[string]string ContextWrites map[string]string
// V1 side effects are internal events only. EmittedEvents []EmittedEvent
// Reserved for V2 streaming chunk semantics. TerminateStream bool}12.4 Cross-hook context model
Section titled “12.4 Cross-hook context model”- Host/plugin can append namespaced context keys.
- Context is immutable after write for the remainder of request lifecycle.
- Later hooks can read context; they cannot mutate existing keys.
12.5 Host capabilities
Section titled “12.5 Host capabilities”Plugins may call bounded host capabilities in addition to consuming HookEventEnvelope.
V1 host capabilities:
type ResolveCIDOptions struct { MaxBytes int}
type CIDResolution struct { CID string Found bool Bytes []byte SizeBytes int64 // underlying blob size when known Truncated bool Source string // blob_store RequestReferenced bool VerifiedByHost bool}
type ReadRequestBodyOptions struct { MaxBytes int}
type RequestBodyRead struct { Bytes []byte Truncated bool}
type OutboundHTTPRequest struct { URL string Method string Headers map[string][]string Body []byte}
type OutboundHTTPResponse struct { StatusCode int Headers map[string][]string Body []byte Truncated bool}
type HostCapabilities interface { ResolveCID(ctx context.Context, cid string, opts ResolveCIDOptions) (CIDResolution, error) ReadRequestBody(ctx context.Context, opts ReadRequestBodyOptions) (RequestBodyRead, error) DoHTTPRequest(ctx context.Context, req OutboundHTTPRequest) (OutboundHTTPResponse, error)}Capability rules:
- All capabilities are host-mediated and explicitly granted per snapshot entry.
ResolveCIDis read-only, host-mediated, and backed by the gateway blob store.request_referencedmeans the CID was present in request headers or otherwise added by host verification/attestation handling for the current request context. It is metadata, not an allowlist.- Resolved blob contents may identify additional CIDs; plugins may resolve those CIDs when they have the
cid.resolvegrant and the blobs are available in the gateway blob store. request.startCID resolutions are still untrusted request-derived data unless later markedVerifiedByHost.ReadRequestBodyis opt-in full request-body access bounded byrequest.body.readpolicy; full response body access is not available in V1.- A plugin may send request body data outbound only if both
request.body.readandhttp.requestare granted. DoHTTPRequestsupportsHTTP(S)only and is gated byhttp.requestcapability policy.- Host validates destination and method policy, injects referenced auth/secrets at dispatch time, and enforces timeout, request-size, response-size, and concurrency limits.
DoHTTPRequestis not raw runtime network access; native and WASM plugins use the same capability policy model.- Capability denials or policy violations are returned as host errors and use normal mode-based failure semantics.
12.6 Filtering contract
Section titled “12.6 Filtering contract”Each snapshot entry can filter by:
provideroperationmethodgateway_pathupstream_pathagent_id
The contract intentionally does not overload a single generic path; inbound gateway routing and provider-relative upstream routing are exposed separately.
Matching rules:
- All provided filters are ANDed.
- Omitted filter is wildcard.
- Supported operators in V1:
exact,prefix. - Regex operators are not supported in V1.
- Non-match silently skips entry.
13. Custom Plugin Runtime (WASM)
Section titled “13. Custom Plugin Runtime (WASM)”Runtime model:
- engine:
wazero - target: WASI-compatible
wasm32 - SDK baseline: Rust-first
- ABI strategy in V1: JSON ABI
Guest module must export:
guardian_plugin_handle_v1
Contract:
- input: UTF-8 JSON bytes of
HookEventEnvelope - output: UTF-8 JSON bytes of
HookDecision
Host capability imports use module guardian_host_v1 with V1 functions:
guardian_host_resolve_cid_v1guardian_host_read_request_body_v1guardian_host_http_request_v1
Each import accepts (request_ptr uint32, request_len uint32) and returns a
packed (response_ptr,response_len) uint64. Requests and responses are UTF-8
JSON bytes. Responses use { "result": ..., "error": "..." }.
Sandbox policy by default:
- no filesystem access
- no direct outbound network access
- no process spawning
- no host env access
- privileged capabilities exposed only through host imports/ABI calls
- host-mediated capabilities only when explicitly granted by runtime policy (for example
cid.resolve,request.body.read,http.request)
14. Marketplace and Trust Model
Section titled “14. Marketplace and Trust Model”Marketplace model in V1:
- curated signed marketplace
- first-party and approved curated plugin entries only
- custom plugins may use the same host-managed capability model as first-party plugins when granted by snapshot policy
Artifact trust policy:
- Plugin artifacts must be signed.
- Trust anchor is platform release signing keys only.
- Gateway verifies digest + signature before loading.
- Artifact objects are immutable and content-addressed by SHA-256-backed object keys.
Artifact storage model:
- dedicated artifact storage plus metadata table
- object storage bytes are separate from Postgres metadata
- control-plane proxies uploads and writes immutable object references into metadata
- gateway reads objects directly from storage with service credentials and re-verifies digest/signature before activation/load
- not reused from
gateway_blobsrequest-state storage path
Secrets model:
- Snapshot config uses secret references only.
- Raw secret values are not stored in plugin config JSON.
- Outbound
HTTP(S)auth is injected by host from secret references at dispatch time. - Plugins do not self-fetch secrets or CID data over network in V1.
15. Control-plane API Surface and RBAC (V1)
Section titled “15. Control-plane API Surface and RBAC (V1)”Artifact APIs:
POST /v1/plugins/artifacts/upload-urlPOST /v1/plugins/artifactsGET /v1/plugins/artifacts/{id}
POST /v1/plugins/artifacts/upload-url is a compatibility helper that returns control-plane upload metadata. In V1 implementation, the actual artifact bytes are streamed through control-plane ingress rather than uploaded via provider-specific signed URLs from the client.
Definition APIs:
POST /v1/plugins/definitionsPATCH /v1/plugins/definitions/{id}GET /v1/plugins/definitionsGET /v1/plugins/definitions/{id}
Snapshot APIs:
POST /v1/plugin-snapshotsGET /v1/plugin-snapshotsGET /v1/plugin-snapshots/{id}POST /v1/plugin-snapshots/{id}/activateGET /v1/plugin-snapshots/current
RBAC policy:
- create/update/activate snapshot and plugin definitions: platform admins only
- read status/events: broader read-only roles allowed
16. Data Model Draft
Section titled “16. Data Model Draft”Core entities:
plugin_definitionsplugin_artifactsplugin_snapshotsplugin_snapshot_entriesgateway_plugin_hook_eventsgateway_plugin_async_eventsgateway_plugin_end_dlq
Key fields (selected):
plugin_definitions:
plugin_definition_idplugin_idruntime(premadeorwasm)supported_hooks_jsondefault_modeconfig_schema_jsonrequired_capabilities_jsonartifact_id(nullable for premade)statuscreated_at
plugin_artifacts:
artifact_idversionsha256signaturesigner_key_idabi_versiondownload_url_templatesize_bytesstatuscreated_at
plugin_snapshots:
snapshot_idenvironmentversionmanifest_jsonmanifest_signaturestatuscreated_bycreated_at
plugin_snapshot_entries:
snapshot_idhookorder_indexplugin_definition_idplugin_versionartifact_sha256plugin_modetimeout_msgranted_capabilities_jsonmatch_jsonconfig_jsonenabled
granted_capabilities_json may encode per-entry capability policy, for example:
cid.resolve: max returned bytes for gateway blob-store CID readsrequest.body.read: max readable byteshttp.request: allowed URL prefixes or destination aliases, allowed methods, timeout_ms, max_request_bytes, max_response_bytes, max_inflight
gateway_plugin_hook_events:
event_idcorrelation_idsnapshot_versionhookplugin_idmodeoutcomelatency_mserror_codedecision_codecreated_at
gateway_plugin_async_events:
event_idcorrelation_idsnapshot_versionplugin_idhook(request.end)outcomeattemptlatency_mscreated_at
gateway_plugin_end_dlq:
dlq_idcorrelation_idsnapshot_versionplugin_idhookfailure_reasonattemptsfirst_failed_atlast_failed_at
Audit linkage (gateway_audit_exchanges additions):
plugin_snapshot_versionplugin_denied_byplugin_decision_codeplugin_summary_json
Retention policy:
- detailed plugin event tables follow audit retention window by default
17. Runtime Sync and Activation Behavior
Section titled “17. Runtime Sync and Activation Behavior”- Default poll interval:
15s. - Poll current snapshot metadata with version/ETag checks.
- Resolve artifact refs from snapshot.
- Open artifact bytes directly from configured object storage using immutable provider/bucket/key refs.
- Verify digest and signature against platform release keys.
- Compile/load executors.
- Persist verified last-known-good snapshot metadata to disk and retain local artifact cache.
- Atomically swap active snapshot pointer for new requests.
- Keep previous snapshot active on failure.
- Continue on last-known-good snapshot during control-plane/data outages, including cold start when control-plane is unavailable.
18. Security Model
Section titled “18. Security Model”- Strict custom-plugin sandbox.
- Artifact digest and signature verification is mandatory.
- Platform release signing keys are the only trusted signing authority in V1.
- Immutable snapshots prevent in-place policy drift.
- Strict schema validation with unknown-field rejection prevents silent config drift.
- Secret references only in plugin config (no plaintext secrets in snapshot config).
- Host-mediated CID resolution is read-only, grant-gated, bounded by max bytes, and backed by the gateway blob store.
- Host-managed outbound
HTTP(S)is policy-gated to snapshot-approved destinations/methods and uses secret references for auth injection. - Full request body access is opt-in and bounded by capability policy; full response body access is not available in V1.
- Bounded previews (
16KB), CID byte limits, outbound size limits, and strict timeouts reduce abuse surface. - Snapshot signature verification is required before activation.
- No direct/raw plugin runtime network egress in V1.
- Native first-party plugins are expected to use host capability APIs rather than ad hoc direct HTTP clients.
19. Observability, Audit, and Side Effects
Section titled “19. Observability, Audit, and Side Effects”Metrics:
- invocation count by hook/plugin/outcome
- timeout/error/deny counters by mode
- hook and plugin latency histograms
- CID resolve count, latency, failure, truncation, not-found, request-referenced, and verified-by-host counters
- outbound
HTTP(S)call count, latency, status, destination alias, timeout, and capability-denied counters - request body read count and truncation counters
- active snapshot version gauge
- snapshot load/activation failure counters
request.endqueue depth, drops, retries, DLQ count
Logs:
- structured logs with correlation ID, snapshot version, hook, plugin, outcome, latency
- structured CID resolution logs with correlation ID, hook, plugin, CID, result, and verification/truncation metadata
- structured outbound
HTTP(S)logs with correlation ID, hook, plugin, destination, method, status, latency, and policy outcome - warning logs for fail-open observability failures
- error logs for fail-closed enforcement failures and snapshot load failures
Audit:
- sync hook events persisted and linked to request correlation ID
- async
request.endevents persisted and linked to correlation ID - request audit row includes plugin summary attribution and capability usage summary
Side effects:
- V1 plugins may emit structured events
- emitted events are persisted/observable internally only
- host-managed outbound
HTTP(S)for plugin evaluation is allowed separately from emitted-event sink dispatch - no outbound webhook/queue/SIEM dispatch for emitted events in V1
20. Reliability and Async End Policy
Section titled “20. Reliability and Async End Policy”- Enforcement plugin timeout/runtime/engine errors fail closed.
- Observability timeout/runtime errors fail open.
- Invalid snapshot/artifact cannot be activated.
- Atomic swap ensures per-request snapshot consistency.
request.enduses durable queueing with bounded retries and DLQ.- Queue overflow uses drop-and-count and never blocks response completion.
request.endfailures never block response completion.
21. Performance Targets
Section titled “21. Performance Targets”- Core in-process plugin overhead target, excluding external service latency:
<=20ms p95. - Plugins using
http.requestmust fit within declared timeout budget. - Default per-plugin timeout without outbound
HTTP(S):10ms. - Per-hook total execution budget enforced.
- Default preview cap:
16KB. - Deterministic sequential execution retained for debuggability and correctness.
22. Testing Plan
Section titled “22. Testing Plan”Core correctness:
- deterministic ordering and filter matching (
exact,prefix) - first-deny short-circuit
- enforcement fail-closed
- observability fail-open
- deny envelope compatibility
Contract and validation:
- canonical hook name validation
- strict config schema enforcement with unknown-field rejection
- timeout bounds and hook-budget validation
- capability policy validation (
cid.resolve,request.body.read,http.request) - ABI compatibility checks
- digest/signature verification pass/fail
- blocked raw runtime access checks (
fs/network/env/process/direct network) - CID resolution grant enforcement, blob-store lookup behavior, and metadata reporting
- CID resolution bounds (
max bytes, plugin timeout budget, truncation) - request body grant/truncation enforcement
- outbound
HTTP(S)destination/method/auth-ref/timeout/size enforcement
Lifecycle coverage (V1):
request.startpre-verify denyproject.bootstraptyped success, policy deny, invalid result, missing/unmaterializable plugin, and no-network guarantees- project snapshot selection without environment downgrade plus projectless environment-default behavior
- one project pin across
project.bootstrap,proxy.pre,proxy.post, audit, andrequest.end, including concurrent activation proxy.prepost-verify deny- non-stream
proxy.postdeny pre-commit - stream headers-stage
proxy.postdeny pre-commit - async
request.enddispatch, retry, DLQ, and non-blocking guarantees - CID resolution succeeds for granted plugins across supported hooks when blobs exist in the gateway blob store
request.startCID resolution remains untrusted until markedVerifiedByHost- outbound
HTTP(S)succeeds to allowed destinations in sync and async hooks - full request body access is available only to explicitly granted plugins
Authorizer migration:
- shadow comparison parity checks
- mismatch telemetry correctness
- controlled cutover validation
Sync and resilience:
- baseline snapshot startup gating
- snapshot immutability and activation semantics
- atomic snapshot swap under load
- invalid update rejection with last-known-good fallback
- cold start from persisted cache
Performance:
- p95 overhead validation under representative plugin chains
- long-running stream behavior with headers-stage
proxy.post - async queue backpressure/drop/retry accounting
- machine-readable performance report output suitable for dedicated CI/perf jobs
23. Acceptance Criteria
Section titled “23. Acceptance Criteria”- Applicable hooks execute for every proxied request in defined order with canonical names;
project.bootstrapruns only for a bound project candidate. - Premade and custom plugins run in one deterministic sequential pipeline.
- Current
Authorizerlogic runs as first-party plugin after migration cutover. - OPA/Rego, intent/VirusTotal, and authorized registry plugins can all be implemented as first-party plugins on the shared hook/capability model.
- Custom plugins deploy without gateway rebuild.
- Existing deny envelope remains unchanged.
- V1 streaming behavior supports headers-stage
proxy.postpolicy decisions only. - Snapshot updates are signature-verified and atomically activated.
- Baseline snapshot requirement and last-known-good fallback both work.
- Host-managed outbound
HTTP(S)and full request body access are policy-gated and observable. - Plugin execution details are visible in metrics/logs/audit data.
- Async
request.enddurability, retry, DLQ, and drop accounting are observable. - P95 core in-process plugin overhead meets target under acceptance load.
24. Future Work (V2+)
Section titled “24. Future Work (V2+)”- Per-chunk streaming callbacks and chunk-stage termination semantics.
- Optional plugin SDK expansion beyond Rust baseline.
- Extended hook coverage outside proxy lifecycle.
- First planned expansion surface: gateway operational endpoint lifecycle hooks (
/v1/agents/registrations, blobs, state/history endpoints). - Optional outbound side-effect sink integrations (webhook/queue/SIEM).
- WIT/component-model ABI exploration.
- Remote sidecar / RPC plugin execution model exploration.
25. Assumptions and Defaults
Section titled “25. Assumptions and Defaults”- Proxy lifecycle only in V1 runtime scope.
- One environment-default activation plus at most one active snapshot per
environment + project_urn; after project binding, each request pins exactly one resolved snapshot for the remaining governed lifecycle. - Curated signed marketplace.
- Platform release signing keys as artifact trust anchors.
wazeroruntime and JSON ABI.- Stateless per-request plugin execution.
- Proxied control-plane artifact uploads plus direct gateway object-store reads.
- No request/response body or header mutation in V1.
- No direct/raw plugin runtime network egress in V1.
- Outbound plugin network access is host-managed and
HTTP(S)-only in V1. - Full request body access is opt-in per plugin entry.
- Full response body access is deferred.
- Secret references only for sensitive config.
- Strict schema validation with unknown-field rejection.
- Side effects are events-only in V1.
- Detailed plugin event retention follows audit retention by default.
- CID resolution is host-mediated, read-only, grant-gated, and backed by the gateway blob store in V1.