Problem
The approval webhook endpoint (POST /api/v1/webhooks/approval-response) currently relies on the global API key authentication middleware. This creates two issues:
-
API key exposure — External approval services must embed the control plane's API key in the webhook callback URL (e.g., as a query parameter), which gets logged in access logs, stored in databases, and visible in HTTP server logs.
-
Requires internet-reachable control plane — The webhook callback is a direct HTTP POST from the external approval service to the control plane. If the CP is behind a firewall or VPN, the callback can't reach it. This undermines the connector architecture which is specifically designed for isolated control planes.
Current State
- The webhook handler already has full HMAC-SHA256 signature verification implemented (
verifySignature in webhook_approval.go)
- It supports multiple signature formats:
t=timestamp,v1=signature, raw hex, and sha256= prefixed
- The
ApprovalWebhookHandler already accepts a webhookSecret parameter from config (config.AgentField.Approval.WebhookSecret)
- The global auth middleware already supports
SkipPaths for bypassing API key auth on specific endpoints
Proposed Solution
Phase 1: HMAC-only auth for webhook endpoint
- Add
/api/v1/webhooks/approval-response to the global auth middleware's SkipPaths so it bypasses API key auth
- Require the
approval.webhook_secret config to be set when the webhook endpoint is active
- The webhook handler already verifies signatures — just need to make it mandatory (currently it's optional: skipped when
webhookSecret == "")
- Agents pass the
webhook_secret to the external approval service when creating approval requests, so it can sign callbacks
This way:
- No API key in URLs or stored externally
- Webhook is authenticated via cryptographic signature
- Each approval service can have its own webhook secret
Phase 2: Connector-routed approval callbacks (future)
For fully isolated control planes that aren't internet-reachable:
- Route approval callbacks through the existing connector WebSocket channel (SaaS → connector → CP)
- Add an
approval_response command type to the connector protocol
- The external approval service sends the callback to the SaaS platform, which forwards it through the connector to the CP
- No public CP URL needed at all
Files
control-plane/internal/handlers/webhook_approval.go — webhook handler with existing HMAC verification
control-plane/internal/server/middleware/auth.go — global API key auth with SkipPaths support
control-plane/internal/server/server.go:1243 — webhook route registration
control-plane/internal/config/config.go — Approval.WebhookSecret config field
Context
Discovered during deployment where the control plane had API key auth enabled. The approval service couldn't call the webhook endpoint without the API key, leading to the workaround of embedding the API key as a query parameter in the webhook URL.
Problem
The approval webhook endpoint (
POST /api/v1/webhooks/approval-response) currently relies on the global API key authentication middleware. This creates two issues:API key exposure — External approval services must embed the control plane's API key in the webhook callback URL (e.g., as a query parameter), which gets logged in access logs, stored in databases, and visible in HTTP server logs.
Requires internet-reachable control plane — The webhook callback is a direct HTTP POST from the external approval service to the control plane. If the CP is behind a firewall or VPN, the callback can't reach it. This undermines the connector architecture which is specifically designed for isolated control planes.
Current State
verifySignatureinwebhook_approval.go)t=timestamp,v1=signature, raw hex, andsha256=prefixedApprovalWebhookHandleralready accepts awebhookSecretparameter from config (config.AgentField.Approval.WebhookSecret)SkipPathsfor bypassing API key auth on specific endpointsProposed Solution
Phase 1: HMAC-only auth for webhook endpoint
/api/v1/webhooks/approval-responseto the global auth middleware'sSkipPathsso it bypasses API key authapproval.webhook_secretconfig to be set when the webhook endpoint is activewebhookSecret == "")webhook_secretto the external approval service when creating approval requests, so it can sign callbacksThis way:
Phase 2: Connector-routed approval callbacks (future)
For fully isolated control planes that aren't internet-reachable:
approval_responsecommand type to the connector protocolFiles
control-plane/internal/handlers/webhook_approval.go— webhook handler with existing HMAC verificationcontrol-plane/internal/server/middleware/auth.go— global API key auth withSkipPathssupportcontrol-plane/internal/server/server.go:1243— webhook route registrationcontrol-plane/internal/config/config.go—Approval.WebhookSecretconfig fieldContext
Discovered during deployment where the control plane had API key auth enabled. The approval service couldn't call the webhook endpoint without the API key, leading to the workaround of embedding the API key as a query parameter in the webhook URL.