Skip to content

Approval webhook endpoint should use HMAC signature auth instead of API key #237

@AbirAbbas

Description

@AbirAbbas

Problem

The approval webhook endpoint (POST /api/v1/webhooks/approval-response) currently relies on the global API key authentication middleware. This creates two issues:

  1. 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.

  2. 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

  1. Add /api/v1/webhooks/approval-response to the global auth middleware's SkipPaths so it bypasses API key auth
  2. Require the approval.webhook_secret config to be set when the webhook endpoint is active
  3. The webhook handler already verifies signatures — just need to make it mandatory (currently it's optional: skipped when webhookSecret == "")
  4. 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.goApproval.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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions