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 configuration manifests.- A baseline signed configuration must be created and activated before
enabling
llmGateway.plugins.enabled=true. - The active baseline configuration 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.deploymentEnvironmentlabels telemetry and dev-mode checks only; plugin configurations are no longer scoped by environment.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 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 '{"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 configuration checklist
Section titled “Baseline configuration checklist”- Register the premade plugin definition for
builtin.authorization. - Create a draft default-scope configuration.
- Add a
proxy.preenforcement entry forbuiltin.authorization. - Add any observability plugins needed for rollout validation.
- Sign the configuration manifest with a trusted signer key.
- Activate the configuration through
POST /v1/plugin-configurations/{id}/activate(empty request body). - Confirm
GET /v1/plugin-configurations/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 configuration exists yet. Even so, you should still bootstrap a signed baseline configuration before rollout so the deployment comes up enforcing an intentional policy instead of an empty plugin set.
Example builtin.authorization configuration 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 configurations with the new signer key.
- Activate at least one configuration signed by the new key in every deployment.
- 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 deployment has a last-known-good configuration signed by the replacement key.
Operational checks
Section titled “Operational checks”llm-gatewaystartup should logplugin runtime enabled.- Audit rows should populate
plugin_configuration_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.