A pipeline is an ordered list of filters. Every
request through /proxy is evaluated against it, and the first filter to deny
short-circuits: the remaining filters do not run, the upstream is never called,
and the credential is never used.
That ordering is not an implementation detail. It is the contract.
What happens on a request
- Resolve the token. The
lat_…bearer token identifies the latch. No token, or an unknown one, is a401. - Resolve the parent chain. If the latch was extended from another, its ancestors are resolved live, at request time. A change to a parent's policy takes effect on the child's next request. See Extending a latch.
- Match a mount, if the latch has a mount table. Longest prefix wins. No match
is a
404, and the response lists the mounts that do exist. - Evaluate the request filters, in order. First deny wins.
- Inject the credential and forward. Only now does the real key get used.
- Evaluate the response filters on the way back. A
responsefilter can block or redact before you ever see the body. - Write the audit entry, allowed or denied, with the full filter trace.
The order filters run in
Filters run in the order they appear in the pipeline, and on a multi-mount or extended latch they compose like this:
grandparent latch-level pipeline
-> parent latch-level pipeline
-> child latch-level pipeline
-> the matched mount's own pipeline
Ordering follows the stage, not parent-versus-child: a child's latch-level filters run before the matched mount's filters, even when that mount belongs to the parent.
Request filters and response filters
Most filters decide before the upstream is called. Exactly one, response, runs
after it replies. That distinction matters:
- A request filter that denies means the upstream never saw the request, and the credential was never used.
- A response filter that denies means the upstream did run. The call happened, it may have cost money, and it may have had side effects. What is prevented is you seeing the result.
Use request filters to control what an agent can do. Use the response filter to
control what it can learn.
What a denial looks like
{
"error": "Request denied by policy",
"requestId": "req_4a1f2c",
"latchId": "lnk_9f3c1a2b",
"deniedBy": "Completions only",
"reason": "/v1/embeddings not in allowlist"
}
403. deniedBy names the filter that stopped it, so the caller can tell what it
did wrong without you having to explain your policy to it.
Conditional filters
Any filter can carry a condition, so it only applies to some requests. A filter
whose condition does not match is skipped, not denied.
{ "type": "body_size", "name": "Cap uploads", "maxBytes": 102400,
"condition": { "methods": ["POST", "PUT"], "pathPrefix": "/v1/files" } }
Try it before you ship it
Simulate runs a request through the real pipeline and shows you each filter's decision, without calling the upstream.