External Processor

Send HTTP request/response phases to an external service that can inspect, mutate, or reject them. The most powerful middleware — your service sees every header and every body byte, with full control over what happens next.

Configuration

{
  "name": "waf",
  "type": "extProc",
  "extProc": {
    "destinationId": "<waf-service>",
    "mode": "grpc",
    "phaseTimeout": "200ms",
    "allowOnError": false,
    "statusOnError": 500,
    "disableReject": false,
    "phases": {
      "requestHeaders": "send",
      "responseHeaders": "send",
      "requestBody": "buffered",
      "responseBody": "none",
      "maxBodyBytes": 1048576
    },
    "allowedMutations": {
      "allowHeaders": ["x-custom-*"],
      "denyHeaders": ["authorization"]
    },
    "forwardRules": {
      "allowHeaders": ["content-type", "x-request-id"],
      "denyHeaders": ["cookie"]
    },
    "observeMode": {
      "enabled": false,
      "workers": 64,
      "queueSize": 4096
    }
  }
}

All fields

FieldTypeDefaultDescription
destinationIdstringrequiredDestination hosting the processor
modestringgrpcgrpc or http
phaseTimeoutstring200msMax time per phase
allowOnErrorboolfalseAllow requests if processor fails
statusOnErrornumber500HTTP status when processor errors (if allowOnError: false)
disableRejectboolfalsePrevent processor from rejecting requests
phasesobjectWhich phases to send and how
phases.requestHeadersstringskipsend or skip
phases.responseHeadersstringskipsend or skip
phases.requestBodystringnonenone, buffered, bufferedPartial, streamed
phases.responseBodystringnonenone, buffered, bufferedPartial, streamed
phases.maxBodyBytesnumberMax bytes to buffer for bufferedPartial
allowedMutationsobjectRestrict which headers the processor can modify
forwardRulesobjectRestrict which request headers are sent to the processor
observeModeobjectFire-and-forget async processing
metricsPrefixstringCustom prefix for this processor’s metrics

Body modes

ModeDescription
noneBody not sent to processor
bufferedEntire body buffered in memory, sent as one message
bufferedPartialBuffer up to maxBodyBytes, send whatever was buffered (may be truncated)
streamedBody chunks sent as they arrive, processor responds per-chunk

Examples

WAF (request headers + body inspection)

{
  "name": "waf",
  "type": "extProc",
  "extProc": {
    "destinationId": "<waf-service>",
    "mode": "grpc",
    "phaseTimeout": "100ms",
    "phases": {
      "requestHeaders": "send",
      "requestBody": "buffered",
      "responseHeaders": "skip",
      "responseBody": "none"
    }
  }
}

Inspects request headers and body for malicious payloads. Skips response phases (WAF only cares about incoming traffic).

Response header injection

{
  "name": "response-enricher",
  "type": "extProc",
  "extProc": {
    "destinationId": "<enricher-service>",
    "phaseTimeout": "50ms",
    "phases": {
      "requestHeaders": "skip",
      "responseHeaders": "send",
      "requestBody": "none",
      "responseBody": "none"
    },
    "allowedMutations": {
      "allowHeaders": ["x-custom-*", "cache-control"]
    }
  }
}

Only processes response headers. The processor can add or modify x-custom-* and cache-control headers but nothing else.

Audit logger (observe mode)

{
  "name": "audit-log",
  "type": "extProc",
  "extProc": {
    "destinationId": "<audit-service>",
    "phases": {
      "requestHeaders": "send",
      "responseHeaders": "send",
      "requestBody": "bufferedPartial",
      "responseBody": "none",
      "maxBodyBytes": 65536
    },
    "observeMode": {
      "enabled": true,
      "workers": 64,
      "queueSize": 4096
    }
  }
}

Fire-and-forget: phases are queued and processed by a background worker pool. The request is never blocked. Useful for audit logging, analytics, or compliance recording.

Request body transformation

{
  "name": "body-transform",
  "type": "extProc",
  "extProc": {
    "destinationId": "<transform-service>",
    "mode": "grpc",
    "phaseTimeout": "500ms",
    "phases": {
      "requestHeaders": "send",
      "requestBody": "buffered",
      "responseHeaders": "skip",
      "responseBody": "none"
    }
  }
}

The processor receives the full request body and can replace it before forwarding to upstream.

HTTP mode (simpler, less powerful)

{
  "name": "simple-proc",
  "type": "extProc",
  "extProc": {
    "destinationId": "<processor>",
    "mode": "http",
    "phaseTimeout": "200ms",
    "phases": {
      "requestHeaders": "send",
      "responseHeaders": "skip"
    }
  }
}

In HTTP mode, Vrata sends one POST per phase to <destination>/process. Simpler to implement than gRPC but limited to one message per phase (no streaming).

Security: restrict mutations

{
  "allowedMutations": {
    "allowHeaders": ["x-enriched-*"],
    "denyHeaders": ["authorization", "cookie", "host"]
  },
  "forwardRules": {
    "allowHeaders": ["content-type", "x-request-id", "accept"],
    "denyHeaders": ["cookie", "authorization"]
  }
}

Prevent rejection (logging-only processor)

{
  "disableReject": true,
  "allowOnError": true
}

The processor can inspect and mutate, but cannot reject requests. If the processor fails, the request continues. Useful for non-critical enrichment services.

Processor responses

The processor can return one of:

Monitoring

vrata_middleware_duration_seconds{type="extProc"} tracks per-phase processing latency. A spike means your processor is slowing down.