Last modified: 2026-08-28
Every request sbproxy accepts runs through one pipeline, implemented as a
sequence of Pingora ProxyHttp callbacks: request_filter,
upstream_peer, upstream_request_filter, response_filter,
response_body_filter, and logging, in that order. A rejection at any
stage short-circuits the rest and writes the error response immediately.
Each stage below names the config and category docs that cover it in
depth, and calls out every point where custom logic (a hook, an
extension bundle, a script, a plugin trait) can attach.
For the authoritative stage list this page is derived from, see
architecture.md's request pipeline section;
for field-by-field config, see configuration.md.
If you want the "what exists" view instead of the "what happens, in order" view, start at core-concepts.md or docs/README.md instead.
flowchart TD
A[Connection accepted] --> B["request_filter\nhostname match, auth, policy,\ncache lookup, forward rules"]
B -->|denied at any step| X[Error response, pipeline short-circuits]
B --> C[upstream_peer\nrouting / load balancer / ai_routing]
C --> D{Action dispatch}
D -->|proxy / load_balancer| E1[Plain HTTP origin]
D -->|ai_proxy| E2[AI provider or local model]
D -->|mcp / a2a| E3[MCP tool call or A2A envelope]
D -->|payment-gated| E4[402 challenge, then origin]
E1 --> F[response_filter\nsecurity headers, anomaly hook]
E2 --> F
E3 --> F
E4 --> F
F --> G[response_body_filter\ntransforms, cache write]
G --> H[logging\nmetrics, access log, event bus]
H --> I[Response returned]
The diagram above is the shape; the listing below is the detail, with every hook attachment point named against its exact step number.
request_filter
1. Trace context extract (W3C / B3)
2. ACME HTTP-01 challenge interception
3. /health and /metrics short-circuit
4. Hostname extraction and origin resolution
5. Force-SSL redirect
6. Allowed methods check
7. CORS preflight handling
8. Bot detection <- identity hooks attach here
9. Threat protection (JSON body checks)
10. Authentication <- auth hook attaches here
11. Policy enforcement <- policy + CEL/Rego hooks attach here
12. Response cache lookup
13. on_request callbacks <- webhook callback attaches here
14. Forward rule matching
15. Action dispatch <- branches by traffic type below
upstream_peer <- RoutingStrategy / ai_routing hook attaches here
upstream_request_filter <- request rewrite
[ the origin call: proxy / ai_proxy / MCP / A2A / payment-gated ]
response_filter <- AnomalyDetectorHook runs here, on_response callback attaches here
response_body_filter <- transform pipeline attaches here, response cache write
logging <- metrics, access log, typed event bus
A client connects and sends a request with a Host header. sbproxy
extracts W3C/B3 trace context (see observability.md
for how that becomes a span), handles ACME HTTP-01 challenges and the
built-in /health and /metrics short-circuits, then resolves the
hostname against origins: using a bloom filter plus hash map lookup.
This is the stage routing.md covers for how an origin is
matched and api-gateway.md covers for the traditional
reverse-proxy framing of the same step.
Force-SSL redirect, an allowed-methods check, and CORS preflight handling
run first. Then bot detection runs, and this is where the agent-identity
resolver chain attaches: Web Bot Auth verification (resolver step 1), then
the IdentityResolverHook trait ("KYA step 1.5", sbproxy-plugin's
identity.rs) sits between that and forward-confirmed reverse DNS
(resolver step 2). Registered hooks run in registration order; the first
to return a verdict wins, and returning None falls through to the next
resolver step. OSS builds register none; a plugin installs one via
sbproxy_plugin::register_identity_hook. See plugins.md
for the extension surfaces and mcp-and-agents.md for
how the resolved agent_id/agent_class gets used downstream.
The MlClassifierHook trait exists in the same sbproxy-plugin crate and
is registrable via register_ml_classifier_hook, but as of this writing
has no call site anywhere in the OSS pipeline (ml_classifier_hooks() is
referenced only in its own definition and in tests) - it is a defined
extension point, not one that currently fires in a request. Do not
depend on it running until an embedder wires it in.
Threat protection (JSON body checks) closes out this group. See security.md and api-security.md.
Built-in providers (API key, JWT, basic, bearer, digest, forward-auth,
mTLS, OIDC, Web Bot Auth, cap) run here; see
api-gateway.md and auth-oidc.md. An
extension bundle can also supply an auth hook: it attaches through
the origin's auth: block, runs before the origin action, and always
fails closed (a hook that throws denies the request regardless of
failure_posture). A bundle auth hook is JavaScript-only in this
release; see extension-bundles.md's Auth hooks section
and plugins.md.
Rate limiting, IP filtering, WAF, CSRF, DDoS protection, request
validation, object authorization, and every other policy run here,
including a CEL, Rego, or extension-bundle policy hook. See
policy.md and security.md for the full
policy catalog, ai-policy-cel.md for the unified CEL
plane, and plugins.md for authoring a custom policy hook.
A policy_denied event fires from here (see the typed event bus in
step 9 below).
The response cache is checked (see cache-reserve.md);
a hit can short-circuit everything that follows. on_request webhook
callbacks fire next - a config-level callbacks: mechanism (documented
inline in configuration.md, on_request:/
on_response: fields) distinct from both the typed event bus
(events.md) and extension-bundle hooks. Forward rule matching runs,
then the action dispatches.
Action dispatch is where the traffic-type branch happens; see
section 6 below for
what differs by branch. Built-in action types are enum variants matched
here: proxy, load_balancer, ai_proxy, static, mock, redirect,
echo, beacon, noop, websocket, grpc, graphql, storage,
a2a, mcp, abtest, and https_proxy; the complete catalog with a paragraph on each is
features.md's action reference. A third-party
action plugin (Plugin(Box<dyn ActionHandler>)) pays one indirect call
here instead of hitting the branch-predicted match; see
plugins.md.
For proxy and load_balancer actions, upstream_peer resolves the
concrete upstream. This is where the RoutingStrategy trait
attaches for a custom selection algorithm beyond the built-in strategies
(see routing.md and routing-strategies.md),
and where an ai_routing hook (an envelope-WASM-only extension
bundle hook, attached by name from an origin's ai_routing_policy)
picks the provider and model for an ai_proxy action on every request
through that origin - see ai-gateway.md and
extension-bundles.md's Routing hooks section.
upstream_request_filter then applies URL rewrite, query injection,
method override, body replacement, request header modifiers, and
distributed-tracing headers.
The actual origin call branches by traffic type:
- Plain HTTP (
proxy,load_balancer) - an ordinary reverse-proxy call. See api-gateway.md and routing.md. - AI (
ai_proxy) - a request to a hosted provider or local model. Guardrail mesh hooks (ai_guardrail_input,ai_guardrail_output) andai_tool_callcan each returnrelease,flag,block, or (where the manifest declaresexecution.mutates: true)mutateto rewrite the content in place before the next hook runs. An inspect-only input hook may setexecution.mode: parallelto run alongside the upstream call and cancel it on reject. Output and tool-call hooks run on both buffered and streamed completions. A streamedmutatethat cannot be written back as the client's wire shape is refused rather than shipping the original. See ai-gateway.md, ai-guardrail-mesh.md, and extension-bundles.md's AI stream hooks section. - MCP / A2A - a JSON-RPC tool call or an agent-to-agent envelope. See mcp-and-agents.md.
- Payment-gated origin - an HTTP 402 challenge/settlement round trip gates the call to the origin. See payments.md.
CORS response headers, HSTS, security headers (from SecHeaders
policies), response modifiers, forward-rule echo, rate-limit headers,
Alt-Svc, CSRF and session cookies, on_response callbacks, and
traceparent echo all run here. This is also where the
AnomalyDetectorHook trait dispatches, not at request time as
its name implies. It runs "now that all
signals have been populated" (TLS fingerprint, ML classification,
headless detection, request rate), against every registered hook, with
verdicts forwarded to whatever sink the hook implementation wires (audit
log, tracing, reputation updater); the OSS pipeline does not act on the
verdicts itself. OSS builds register none. The signal set now includes
GeoIP country/ASN and a UA-parsed headless-library label when the
geoip / user_agent_parser policies ran earlier in the request
phase. See headless-detection.md,
request-enrichment.md, and
plugins.md.
Transforms modify the response body before it reaches the client - they
are response-side only, run in the order declared under transforms:,
and this is their one attachment point in the pipeline. Four of the
twenty-eight transform types are themselves a scripting hook
(cel_script, lua_json, javascript/js_json, wasm), so this stage
is both a fixed set of built-in reshaping operations and its own
extension point. See transforms.md for the full catalog
and plugins.md for the scripting/WASM surfaces. A response
cache write on miss and a fallback body swap also happen at this stage;
see cache-reserve.md and degradation.md.
Metrics emission, the structured access log, and event publication close
out the pipeline. EventType has twenty-three variants; twenty-one of them
ship a production emitter in the OSS binary today: request_started,
request_completed, request_error (the request_events: lane),
auth_denied, policy_denied, config_reloaded, egress_refused
(the events: lane, bridged from the security, config, and egress
audit records), mcp_governance_decision (every MCP tools/call
decision, allowed or refused), key_minted, key_revoked,
key_rotated, key_blocked (the admin key plane, bridged from the
key_audit channel), credential_resolved (once per actual upstream
credential resolution, never per request),
credential_fallback (once per AI provider-key fallback decision),
and provider_selected,
budget_exceeded, guardrail_triggered (verdict-level: a provider
fallback, a budget cap denial, or a guardrail block, never a
per-request or per-chunk line), ai_workflow_operation (one terminal
governed workflow execution), ai_evaluation_operation (one terminal
offline evaluation run), ai_prompt_rollout_selected (an admin dry-run
or live AI request selected a weighted prompt version), and
agent_registration_decided (an agent entered the owner-approval queue, or
an operator approved, rejected, or revoked one). cache_hit and
cache_miss are the two enum variants left unwired on purpose: firing on every cacheable request would put an
NDJSON line on every configured events: sink per cache lookup. Cache
admission already reports through DecisionEvent::CacheAdmit/CacheKey
and the access log's cache_status column; naming either in
events.types: gets a boot warning rather than a silent no-op. See
events.md and observability.md.
| You want to... | Attach at | Mechanism | Depth |
|---|---|---|---|
| Resolve a custom agent identity | Bot detection (step 8) | IdentityResolverHook (Rust trait) |
plugins.md |
| Authenticate with custom logic | Authentication (step 10) | Extension-bundle auth hook (JS only) |
extension-bundles.md |
| Add a custom policy | Policy enforcement (step 11) | CEL, Rego, or extension-bundle policy hook | policy.md, plugins.md |
| Fetch external data mid-request | on_request/on_response |
Webhook callback | configuration.md |
| Pick a custom upstream | upstream_peer |
RoutingStrategy trait |
routing-strategies.md |
| Pick an AI provider/model dynamically | upstream_peer (AI branch) |
ai_routing hook (WASM only) |
extension-bundles.md |
| Inspect/mutate an AI guardrail or tool call | AI origin call | ai_guardrail_*/ai_tool_call hooks |
ai-guardrail-mesh.md |
| Reshape a response | response_body_filter |
A transform, or a scripting transform (CEL/Lua/JS/WASM) | transforms.md |
| Detect anomalous behavior after the fact | response_filter |
AnomalyDetectorHook (Rust trait) |
plugins.md |
| React to a lifecycle event | logging (mostly) |
Typed event bus (21 of 23 event types emitted) | events.md |
A new user wants stage 1-5 to understand what a request touches before
it leaves the gateway. An advanced user wants the exact stage names and
ordering above to reason about interaction effects (for example, why a
cache hit at step 12 skips the action dispatch that would otherwise run
an AI guardrail). An SRE lead cares about stage 9 (what gets
logged and emitted) and the AnomalyDetectorHook's real placement in
response_filter. An AI user cares about the AI branch in
section 6. A
developer extending the gateway wants the summary table above as a map
of every attachment point before opening plugins.md.