Request Evaluation
Request evaluation is Layer 2 of the governed path. OpenShell’s sandbox supervisor sends each egress request that the sandbox policy routes to APF’s middleware, after its own Layer 1 checks and before it injects credentials. APF decides in process and returns a decision. OpenShell enforces it.
From Request to Evaluation Input
The middleware accepts only the HTTP_REQUEST operation at the PRE_CREDENTIALS phase. Before it builds an evaluation input, it checks the request and denies it with a stable reason code if a check fails.
The evaluation input has these fields:
APF identifies an MCP tool call from the request body, not from the destination. Any JSON body whose method is tools/call is evaluated as a tool call.
Cedar Request Mapping
Each request is mapped to a Cedar principal, action, and resource.
The Cedar context is a record with these attributes:
APF evaluates Cedar with an empty entity store. Conditions read from the context, not from entity attributes or hierarchies.
A provider domain can never be args, annotations, data, body_text, has_tool_name, target_host, target_method, target_path, or target_scheme. The gateway rejects such a registration, and the evaluator refuses an input whose namespace would shadow one of those roots.
The Four Stages
Without registered providers the Enrichment stage does nothing, and the pipeline is the annotator, Cedar, and effect stages alone.
Stage 1, Enrichment
Before the evaluator runs, the gateway consults the policy information providers registered with --pip-config and writes their facts into the context. Refer to Policy Information Providers for the registration file and the provider contract.
- A provider is consulted when it applies to the request.
applies_tolists tool names or action ids, and an entry matches when it equals the request’s tool name or its action id. On the OpenShell path both are the bare MCP tool name. On theEvaluationServicepath the tool name isresource.classand the action id is<resource.class>::<verb>. One entry covers both surfaces only when callers use the same tool name on both; otherwise list both forms. A registration withoutapplies_tois consulted on every evaluation. - The gateway sends each applicable provider only what its type allows, the identity and action fields of the subject to a
grpcprovider and the request fields its lookup keys read to anhttpprovider. It looks the facts up within the provider’stimeout_ms, validates the answer, and writes it tocontext.<domain>together with a_metaprovenance record. The whole stage runs under--pip-stage-budget-ms, 250 ms by default. - When a provider cannot answer, its registered failure mode decides. With
fail_closedthe request is denied before any rule runs, with the reasonpip_<status>:<provider>, for examplepip_timeout:change-management. Withdegradethe domain’s record holds only_meta, withdegradedset totrueandstatusnaming the failure, and evaluation continues. - A failure the caller caused, such as a projected subject over the message size cap or a correlation id over 256 bytes, is
pip_invalid_inputand denies regardless of the failure mode. - Any
context_attributesa caller supplies are discarded before the stage runs. Only the gateway writes provider facts.
The statuses are found, no_data, not_applicable, timeout, unavailable, capacity, invalid_output, invalid_input, unauthorized, misconfigured, and internal. The first three are not failures.
Write permits over provider facts with a has guard, so a missing fact leaves the grant off. A forbid that reads a missing provider fact fails to evaluate and denies the request, as described under Stage 3.
Stage 2, Annotators
Every annotation in every PolicyAnnotator runs against the raw input. Annotators cannot see each other’s output, and an annotation name may be defined only once in a bundle.
- A
boolannotation is always present incontext.annotations. It isfalsewhen its rules do not match. - A
stringorintegerannotation is present only when a rule produced a value. Guard reads withcontext.annotations has <name>.
Stage 3, Cedar Rules
Each PolicyRule is its own Cedar policy set, evaluated in bundle manifest order with its own data in the context.
- An explicit
forbidthat fires stops evaluation. The request is denied with the reason<rule>/<@id>, or the@iditself when it contains/, or<rule>/policyNwhen the statement has no@id. The findings referenced by that forbid’s@apf_findingare attached. - A rule where no statement matches is skipped. Cedar’s default deny does not deny the request.
- A
forbidthat fails to evaluate, for example because it reads an attribute the request does not carry, denies the request with the reason<rule>/<@id>: evaluation error. The engine’s error text is written to the gateway log, not returned to OpenShell. If anotherforbidin the same rule fires on the request, thatforbiddecides and the erroring one is logged. This includes a forbid that reads a provider fact the Enrichment stage did not supply. Apermitthat fails to evaluate grants nothing. Guard optional attributes withhaswhen a statement should simply not apply. - A
permitthat fires contributes its@apf_effectand@apf_findingreferences. - A rule’s
roledecides whether its permits count as a grant. Every MCP tool call needs at least one permit from acapabilityrule. Acontrolrule’s permit does not count. A request that is not a tool call needs no grant.
When an MCP tool call gets no grant, the deny reason is <rule>: conditions not met for <action> if a capability rule whose name contains the full tool name was skipped, and no grant for <action> otherwise. On the OpenShell path the tool name is the whole MCP tool name, such as github_repo_write, so most denials read no grant for <action>.
Stage 4, Effects
On an allow, the effects referenced by every fired permit are aggregated in firing order.
- Body patches accumulate and apply in order with RFC 6902 semantics.
- A body replacement is last-wins and takes precedence over patches.
- Header mutations accumulate in order.
- The verdict is the strongest one that fired:
allow, thenwarn, thenescalate. - The
reasonandmessagecome from the first fired effect that carries the final verdict. - Findings from effects come first, in firing order, followed by the findings from fired permits’
@apf_findingreferences.
A source or label_source that names an annotation the request did not produce fails the whole evaluation closed, and the request is denied.
Applying the Result
The middleware turns an allow into concrete changes before it answers OpenShell. Any failure is a deny, because forwarding an unmodified request would drop a mandated change.
An evaluation error returns evaluation_failed. The engine’s error text is not returned to OpenShell.
The Response to OpenShell
OpenShell blocks a denied request or applies the changes, and only then injects provider credentials and forwards the request. Every decision writes one authorization record to the middleware audit file. When providers are registered, the record carries an extensions.x_apf_pip object listing what each provider contributed. Refer to Understand Decisions and Findings.
Next Steps
- To write rules for this pipeline, refer to Policy Model.
- To see the middleware contract, refer to Supervisor Middleware.
- To register providers for the Enrichment stage, refer to Policy Information Providers.