Hook/Plugin Runtime Bootstrap
This guide covers the operational steps needed to enable the GAT-66 hook/plugin
foundation in a cluster deployed with guardian-stack.
What must exist before enabling plugins
Section titled “What must exist before enabling plugins”llm-gatewayandcontrol-planemust share the plugin trust roots for the signer keys that will sign plugin artifacts and snapshot manifests.- A baseline signed snapshot must be created and activated for the target
environment before enabling
llmGateway.plugins.enabled=true. - The active baseline snapshot should include
builtin.authorizationinproxy.preso the first-party authorization policy is enforced before upstream proxying. llm-gatewayandcontrol-planemust be able to read/write the shared plugin artifact bucket, typically via workload identity-bound Kubernetes service accounts.
Helm values
Section titled “Helm values”Gateway values:
llmGateway: deploymentEnvironment: production serviceAccount: annotations: iam.gke.io/gcp-service-account: guardian-llm-gateway@project-id.iam.gserviceaccount.com plugins: enabled: true pollInterval: 15s authorizerShadowEnabled: true endQueueDir: /var/lib/guardian/plugin-end-queue endQueueMaxBytes: 134217728 artifactCacheDir: /var/lib/guardian/plugin-artifacts artifactStorage: provider: gcs bucket: guardian-plugin-artifacts prefix: production gcs: endpoint: "" trustedSignerKeys: signer-prod: did:key:z6Mk... secrets: resolutionOrder: ["store"] encryptionKey: existingSecret: guardian-plugin-secret-key secretKey: encryption-keyControl-plane values:
controlPlane: serviceAccount: annotations: iam.gke.io/gcp-service-account: guardian-control-plane@project-id.iam.gserviceaccount.com plugins: artifactStorage: provider: gcs bucket: guardian-plugin-artifacts prefix: production maxUploadBytes: 536870912 gcs: endpoint: "" kmsKeyName: "" trustedSignerKeys: signer-prod: did:key:z6Mk... secrets: encryptionKey: existingSecret: guardian-plugin-secret-key secretKey: encryption-keyNotes:
trustedSignerKeysare public trust roots, so inline values are acceptable.llmGateway.deploymentEnvironmentmust match the snapshot environment that will be activated, for exampledevelopment,staging, orproduction.control-planeproxies artifact uploads, computes the SHA-256 digest, verifies the signer signature, and stores only metadata in Postgres. Artifact bytes live in object storage under immutable digest-based keys.llm-gatewayreads artifact objects directly from the configured bucket using its own workload identity or ADC credentials, then verifies digest and signature again before caching locally.- Store-backed plugin secrets are encrypted with the generic
guardian-plugin-secret-keyvalue and scoped by environment, plugin ID, and secret name. The control-plane API accepts raw values only on write and returns metadata/fingerprints on reads. - The chart mounts
emptyDirvolumes for the asyncrequest.endqueue and the verified artifact cache. Move those paths to persistent storage if you need cache reuse across restarts.
Built-in plugin secret names:
builtin.vtscanshould use{"kind":"secret_ref","name":"virustotal_api_key"}forvt_api_key.builtin.intent_monitorshould use{"kind":"secret_ref","name":"openai_api_key"}forllm_api_key.
Set or rotate store-backed values through the control-plane:
curl -X POST "$CONTROL_PLANE_URL/v1/plugins/secrets" \ -H 'Content-Type: application/json' \ -d '{"environment":"production","plugin_id":"builtin.intent_monitor","name":"openai_api_key","value":"replace-with-openai-key"}'Local/dev fallback can still use Kubernetes env injection. Example Secret data
when using the default plugins.secrets.env.prefix = "GUARDIAN_SECRET_":
GUARDIAN_SECRET__BUILTIN__VTSCAN__VIRUSTOTAL_API_KEY=replace-with-virustotal-keyGUARDIAN_SECRET__BUILTIN__INTENT_MONITOR__OPENAI_API_KEY=replace-with-openai-keyBaseline snapshot checklist
Section titled “Baseline snapshot checklist”- Register the premade plugin definition for
builtin.authorization. - Create a draft snapshot for the deployment environment.
- Add a
proxy.preenforcement entry forbuiltin.authorization. - Add any observability plugins needed for rollout validation.
- Sign the snapshot manifest with a trusted signer key.
- Activate the snapshot through
POST /v1/plugin-snapshots/{id}/activate. - Confirm
GET /v1/plugin-snapshots/currentreturns the expected version. - Enable
llmGateway.plugins.enabled=trueand roll the gateway deployment.
The gateway now starts cleanly when plugin runtime is enabled but no active snapshot exists yet. Even so, you should still bootstrap a signed baseline snapshot before rollout so the environment comes up enforcing an intentional policy instead of an empty plugin set.
Example builtin.authorization snapshot entry config:
{ "default_effect": "deny", "default_message": "agent is not authorized for this provider route", "rules": [ { "id": "allow-agent-openai-chat", "effect": "allow", "match": { "agent_id": { "operator": "exact", "value": "agent:test" }, "provider": { "operator": "exact", "value": "openai" }, "operation": { "operator": "exact", "value": "chat_completions" }, "upstream_path": { "operator": "prefix", "value": "/v1/chat/completions" } } } ]}Recommended first rollout:
- set
llmGateway.plugins.authorizerShadowEnabled=true - compare hook decisions against the legacy authorizer in telemetry
- keep the legacy authorizer enabled only for shadow comparison until
builtin.authorizationhas acceptable mismatch rates
Signer rotation
Section titled “Signer rotation”Use additive rotation:
- Add the new signer public key to both
controlPlane.plugins.trustedSignerKeysandllmGateway.plugins.trustedSignerKeys. - Roll both deployments so the new trust root is active everywhere.
- Start signing new artifacts and snapshots with the new signer key.
- Activate at least one snapshot signed by the new key in each environment.
- After the old signer is no longer needed for rollback, remove its trust root from both services and roll again.
Do not remove the old signer key before every environment has a last-known-good snapshot signed by the replacement key.
Operational checks
Section titled “Operational checks”llm-gatewaystartup should logplugin runtime enabled.- Audit rows should populate
plugin_snapshot_version,plugin_denied_by, andplugin_decision_codewhen a hook participates in the request. - When
authorizerShadowEnabled=true, shadow mismatches appear asgateway.proxy.policy.shadow_mismatchlog events andgateway.policy.shadow_comparespan events. - The async end-hook queue path should stay below
endQueueMaxBytes; investigate DLQ growth before increasing the limit.