Skip to content

Webhook Middleware Phase 2: Validating webhook middleware #3397

@JAORMX

Description

@JAORMX

Overview

Implement validating webhook middleware that calls external HTTP services to approve or deny MCP requests. This middleware allows organizations to plug in external policy engines, approval workflows, or rate limiters.

RFC: https://github.com/stacklok/toolhive-rfcs/blob/main/rfcs/THV-0017-dynamic-webhook-middleware.md

Depends on: Phase 1 (Core webhook package)

Files to Create

File Purpose
pkg/webhook/validating/middleware.go Validating webhook middleware implementation
pkg/webhook/validating/config.go Configuration types and validation

Files to Modify

File Changes
pkg/runner/middleware.go Register validating-webhook in GetSupportedMiddlewareFactories()
pkg/runner/config.go Add ValidatingWebhooks []webhook.WebhookConfig field to RunConfig

Middleware Implementation

// pkg/webhook/validating/middleware.go
const MiddlewareType = "validating-webhook"

type MiddlewareParams struct {
    Webhooks []webhook.WebhookConfig `json:"webhooks"`
}

type Middleware struct {
    client     *webhook.Client
    webhooks   []webhook.WebhookConfig
    middleware types.MiddlewareFunction
}

func (m *Middleware) Handler() types.MiddlewareFunction {
    return m.middleware
}

func (m *Middleware) Close() error {
    return nil
}

func CreateMiddleware(config *types.MiddlewareConfig, runner types.MiddlewareRunner) error {
    // Implementation
}

Request/Response Format

Request to webhook (POST):

{
  "version": "v0.1.0",
  "uid": "unique-request-id",
  "timestamp": "2025-01-22T10:30:00Z",
  "principal": {
    "sub": "user123",
    "email": "user@example.com",
    "groups": ["engineering"]
  },
  "mcp_request": {
    "method": "tools/call",
    "params": { "name": "database_query", "arguments": {...} }
  },
  "context": {
    "server_name": "my-mcp-server",
    "transport": "sse",
    "source_ip": "192.0.2.1"
  }
}

Response (allowed):

{
  "version": "v0.1.0",
  "uid": "unique-request-id",
  "allowed": true
}

Response (denied):

{
  "version": "v0.1.0",
  "uid": "unique-request-id",
  "allowed": false,
  "code": 403,
  "message": "Production writes require approval",
  "reason": "RequiresApproval",
  "details": {
    "ticket_url": "https://tickets.example.com/PROD-1234"
  }
}

Middleware Chain Position

Validating webhooks should be placed after MCP Parser and before Authorization:

Auth -> Token Exchange -> Tool Filter -> MCP Parser ->
[Mutating Webhooks] -> [Validating Webhooks] -> Telemetry -> Authorization -> Audit -> Recovery

Failure Policies

Policy Behavior on webhook error
fail (fail-closed) Deny request with 403
ignore (fail-open) Allow request to continue

Error conditions:

  • Network errors
  • Timeout
  • HTTP 5xx from webhook
  • Invalid JSON response
  • Missing required fields

Multiple Webhooks

When multiple validating webhooks are configured:

  1. Execute in configuration order
  2. If ANY webhook returns allowed: false, deny the request
  3. If ALL webhooks return allowed: true, allow the request
  4. Failure policy applies per-webhook

Tests

  • Unit tests with mock webhook servers (use httptest)
  • Tests for allowed=true and allowed=false responses
  • Tests for failure policies (fail-closed, fail-open)
  • Tests for multiple webhooks in chain
  • Tests for timeout handling
  • Tests for invalid response handling
  • Integration tests with middleware chain

Acceptance Criteria

  • Middleware registered in GetSupportedMiddlewareFactories()
  • Correctly calls webhooks with RFC-compliant request format
  • Handles allowed: true/false responses correctly
  • Implements both failure policies
  • Supports multiple webhooks in chain
  • Comprehensive unit tests with >80% coverage
  • Integration test with full middleware chain
  • Code passes task lint and task test

Metadata

Metadata

Assignees

Labels

apiItems related to the APIauthenticationenhancementNew feature or requestgoPull requests that update go code

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions