Skip to content

Commit 851ccd0

Browse files
committed
docs(rfc): refine middleware evaluation shape
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
1 parent 1b1098c commit 851ccd0

1 file changed

Lines changed: 33 additions & 23 deletions

File tree

rfc/0009-supervisor-middleware/README.md

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ Configuration-time:
151151

152152
Request-time:
153153

154-
- `EvaluateHttpRequest` carries the operation phase (`pre_credentials`) plus context: request identity, endpoint and header context, optional originating process data (the binary, pid, and ancestor chain OpenShell resolves when available for network policy and audit), the bounded body, and the middleware's configuration from policy.
155-
- `HttpRequestResult` is a response OpenShell can apply directly: `allow` or `deny`, optional replacement content and allowed header mutations, findings (labels, counts, confidence, never raw matched values), and namespaced metadata.
154+
- `EvaluateHttpRequest` carries the selected binding ID and operation phase (`pre_credentials`) plus the request context, middleware configuration from policy, HTTP request target, safe header subset, and bounded body.
155+
- `HttpRequestResult` is a response OpenShell can apply directly: `allow` or `deny`, a safe reason, optional replacement content, allowed header mutations, findings (labels, counts, confidence, never raw matched values), and namespaced metadata.
156156

157157
A simplified sketch of the gRPC contract:
158158

@@ -179,23 +179,35 @@ message MiddlewareBinding {
179179
string phase = 3; // e.g. "pre_credentials"
180180
}
181181
182-
// Context plus body as two top-level fields, so the body is cleanly separable.
183182
message HttpRequestEvaluation {
184-
Operation operation = 1;
185-
RequestContext context = 2;
186-
bytes body = 3; // bounded
183+
string api_version = 1;
184+
string binding_id = 2; // selected manifest binding
185+
string phase = 3; // e.g. "pre_credentials"
186+
187+
RequestContext context = 4;
188+
google.protobuf.Struct config = 5; // service-specific, from policy
189+
190+
HttpRequestTarget target = 6;
191+
map<string, string> headers = 7; // safe subset
192+
bytes body = 8; // bounded
187193
}
188194
189195
message RequestContext {
190196
string request_id = 1;
191197
string sandbox_id = 2;
192-
Endpoint endpoint = 3; // scheme, host, port, method, path
193-
map<string, string> headers = 4; // safe subset
194-
google.protobuf.Struct config = 5; // service-specific, from policy
195-
Process actor = 6; // optional originating process (per-connection)
198+
Process originating_process = 3; // optional, per-connection
196199
}
197200
198-
// Mirrors the actor process OpenShell already resolves for network policy and OCSF audit.
201+
message HttpRequestTarget {
202+
string scheme = 1;
203+
string host = 2;
204+
uint32 port = 3;
205+
string method = 4;
206+
string path = 5;
207+
string query = 6; // raw request query string; never log
208+
}
209+
210+
// Mirrors the originating process OpenShell already resolves for network policy and OCSF audit.
199211
message Process {
200212
string binary = 1; // resolved binary path
201213
uint32 pid = 2;
@@ -210,28 +222,26 @@ message Finding {
210222
string severity = 5; // service-defined severity marker
211223
}
212224
213-
// Outcome plus optional replacement body.
214225
message HttpRequestResult {
215-
Outcome outcome = 1;
216-
bytes body = 2; // replacement content when transformed
217-
}
218-
219-
message Outcome {
220226
Decision decision = 1; // ALLOW or DENY
221-
string deny_reason = 2; // safe, machine-readable
222-
map<string, string> add_headers = 3; // append-only, subject to a v1 safe-header allow-list
223-
map<string, string> metadata = 4; // namespaced, no raw values
224-
repeated Finding findings = 5; // labels, counts, confidence
227+
string reason = 2; // safe, machine-readable
228+
229+
bytes body = 3; // replacement content when transformed
230+
bool has_body = 4; // distinguishes no replacement from an empty replacement
231+
232+
map<string, string> add_headers = 5; // append-only, subject to a v1 safe-header allow-list
233+
repeated Finding findings = 6; // labels, counts, confidence
234+
map<string, string> metadata = 7; // namespaced, no raw values
225235
}
226236
```
227237

228-
The evaluation and result are shaped so middleware composes cleanly in a chain. The transformed content a middleware returns (`HttpRequestResult.body`) is the same request body the next middleware receives as `HttpRequestEvaluation.body`, and the allow/deny decision travels as a separate signal in `Outcome` rather than being mixed into the content. The supervisor feeds one stage's `body` (and allowed header mutations) as the input to the next stage, so a chain is effectively a fold over a single request representation; a `deny` from any stage short-circuits the rest. See [Middleware ordering](#middleware-ordering) for how chains are assembled and ordered.
238+
The evaluation and result are shaped so middleware composes cleanly in a chain. The allow/deny decision is a first-class result field rather than being mixed into content. If `has_body` is true, the transformed content a middleware returns (`HttpRequestResult.body`) becomes the request body the next middleware receives as `HttpRequestEvaluation.body`; if `has_body` is false, the supervisor keeps the previous body. The supervisor also feeds allowed header mutations into the next stage, so a chain is effectively a fold over a single request representation; a `deny` from any stage short-circuits the rest. See [Middleware ordering](#middleware-ordering) for how chains are assembled and ordered.
229239

230240
Header mutation is deliberately narrow in v1. Middleware may only append safe, non-credential request headers approved by OpenShell. It cannot remove headers, rewrite existing headers, or set credential-bearing and request-routing headers such as `Authorization`, `Cookie`, `Host`, `X-Amz-*`, or OpenShell credential placeholders. This keeps request-body transformation useful while preserving the credential boundary and avoiding a second, middleware-owned routing surface.
231241

232242
The interface is gRPC. The hot-path v1 RPC is unary: the supervisor buffers the bounded body, sends one `HttpRequestEvaluation`, and receives one `HttpRequestResult`. Streaming is deliberately not baked into `EvaluateHttpRequest`; if OpenShell later needs chunked or incremental processing, it should add a separate operation-specific method rather than changing the v1 method cardinality. Possible extensions (streaming operations, additional operation phases, semantic context) are collected in the [protocol-extensions appendix](appendices/protocol-extensions.md). The baseline middleware ships in the supervisor and is served in-process over the same gRPC contract, with no network hop. The exact field set is settled during implementation; the sketch above is the contract shape this RFC asks reviewers to evaluate.
233243

234-
The `actor` process is the same identity OpenShell already resolves on the egress path - the binary, pid, and ancestor chain it uses for binary-scoped network policy and OCSF audit. It is resolved when the connection is established, so it is per-connection rather than strictly per-request: over a reused or pooled connection it identifies the process that opened the connection, which a middleware should not over-trust for per-request attribution. The field is optional because proxy-only or future shared-supervisor modes may not have reliable actor data; middleware must treat missing actor data as "unavailable" rather than as an authorization failure.
244+
The `originating_process` is the same identity OpenShell already resolves on the egress path - the binary, pid, and ancestor chain it uses for binary-scoped network policy and OCSF audit. It is resolved when the connection is established, so it is per-connection rather than strictly per-request: over a reused or pooled connection it identifies the process that opened the connection, which a middleware should not over-trust for per-request attribution. The field is optional because proxy-only or future shared-supervisor modes may not have reliable process data; middleware must treat missing process data as "unavailable" rather than as an authorization failure.
235245

236246
### Relationship to RFC 0010
237247

0 commit comments

Comments
 (0)