Latch / Docs
filters

The pipeline

How a request is actually decided. First deny wins.

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

  1. Resolve the token. The lat_… bearer token identifies the latch. No token, or an unknown one, is a 401.
  2. 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.
  3. 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.
  4. Evaluate the request filters, in order. First deny wins.
  5. Inject the credential and forward. Only now does the real key get used.
  6. Evaluate the response filters on the way back. A response filter can block or redact before you ever see the body.
  7. 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.