// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package openshell.middleware.v1; import "google/protobuf/struct.proto"; import "google/protobuf/duration.proto"; import "extension.proto"; // SupervisorMiddleware discovers and configures one operator-run middleware. // It evaluates HTTP requests and WebSocket messages before credentials. // Phase-specific services share the same registration. service SupervisorMiddleware { // Describe returns the service manifest and declared bindings. rpc Describe(MiddlewareDescribeRequest) returns (MiddlewareManifest); // ValidateConfig checks service-specific configuration for one binding. rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse); // EvaluateHttpRequest returns an allow, deny, or mutation decision for one // buffered HTTP request. rpc EvaluateHttpRequest(HttpRequestEvaluation) returns (HttpRequestResult); // EvaluateWebSocketSession opens one ordered, phase-specific stream for a // single middleware stage and WebSocket upgrade attempt. The current // implementation supports client-to-upstream text messages at // PRE_CREDENTIALS; PRE_RETURN is reserved for upstream-to-client messages. // A request may go unanswered when the session terminates. For every opened // stage stream, OpenShell attempts at most one session_end before closing the // stream when its transport is still writable. rpc EvaluateWebSocketSession(stream WebSocketSessionEvent) returns (stream WebSocketSessionEventResult); } message MiddlewareDescribeRequest { // Gateway or supervisor protocol metadata. Middleware must reject unmet // requirements before accepting traffic. openshell.extension.v1.PeerMetadata gateway = 1; } // HttpResponsePreReturn evaluates one response for one middleware stage before // OpenShell returns it to the sandbox. service HttpResponsePreReturn { // Evaluate starts with preflight and may continue with selected body units // and trailers. A body unit marked end_of_stream ends body inspection, not // the event stream. Trailers and one best-effort session_end may follow. rpc Evaluate(stream HttpResponseEvent) returns (stream HttpResponseEventResult); } // MiddlewareManifest describes one middleware service and its bindings. message MiddlewareManifest { // Human-readable middleware service name used only for diagnostics. This is // not required to match an operator-owned registration name. string name = 1; // Deprecated diagnostic compatibility field. Use // extension.implementation_version. string service_version = 2; // Bindings exposed by this middleware service. repeated MiddlewareBinding bindings = 3; // Exact JWT audience this service verifies on inbound OpenShell calls. // After authenticated Describe succeeds, OpenShell rejects the registration // unless this matches the operator-configured audience. A strict verifier may // reject an incorrect audience before returning this manifest. Empty skips // this post-authentication consistency check. string expected_audience = 4; // Supervisor-middleware protocol metadata. Required for negotiation. // Declare protocol 1.0 and list "openshell.supervisor-middleware.contract" // in supported_capabilities. openshell.extension.v1.PeerMetadata extension = 5; } // MiddlewareBinding declares one operation and phase supported by a service. message MiddlewareBinding { reserved 4; reserved "timeout"; // Supported operation. SupervisorMiddlewareOperation operation = 1; // Supported phase. SupervisorMiddlewarePhase phase = 2; // Maximum request body, WebSocket message, or response body unit/replacement. // Required for payload-bearing operations. uint64 max_payload_bytes = 3; // Optional binding-specific RPC timeout. Empty uses the operator-configured // service timeout, or the 500ms platform default when that is also omitted. // A non-empty value may shorten but cannot extend the operator timeout. // Values must be between 10ms and 30s. google.protobuf.Duration request_timeout = 104; } // ValidateConfigRequest contains one policy configuration to validate. message ValidateConfigRequest { // Service-specific policy configuration. google.protobuf.Struct config = 1; // Built-in middleware name or operator-owned registration name. string middleware_name = 2; } // ValidateConfigResponse reports whether a policy configuration is accepted. message ValidateConfigResponse { // True when the service accepts the configuration. bool valid = 1; // Human-readable validation failure reason. Empty when valid is true. string reason = 2; } // HttpRequestEvaluation contains one buffered HTTP request to evaluate. message HttpRequestEvaluation { // Evaluation phase selected for this request. SupervisorMiddlewarePhase phase = 1; // Sandbox and request identity available to the supervisor. // The encoded context is limited to 4 KiB. RequestContext context = 2; // Validated service-specific policy configuration. // The encoded configuration is limited to 64 KiB. google.protobuf.Struct config = 3; // Destination and HTTP request target. // The encoded target is limited to 32 KiB. HttpRequestTarget target = 4; // HTTP request headers before OpenShell injects credentials, in wire // order. Repeated header names are preserved as separate entries. Protected // credential, routing, framing, and hop-by-hop headers are omitted. // At most 128 lines and 64 KiB of encoded headers are included. repeated HttpHeader headers = 5; // Buffered request body, limited to 4 MiB. Empty for a bodyless request. bytes body = 6; // Built-in middleware name or operator-owned registration name. string middleware_name = 7; } // HttpHeader is one HTTP header line. message HttpHeader { // Lowercased header name. string name = 1; // Header value with surrounding whitespace trimmed. string value = 2; } // One ordered response event. A stream starts with preflight, may continue with // body units ending in end_of_stream, may then include trailers, and may end // with one best-effort session_end. message HttpResponseEvent { oneof event { // Initial response head and request context. HttpResponsePreflight preflight = 1; // Next normalized body unit. HttpResponseBodyUnit body = 2; // Normalized trailers after the final body result. HttpResponseTrailers trailers = 4; // Optional terminal notification. MiddlewareSessionEnd session_end = 3; } } // Each preflight, body, and trailers event requires one ordered result. // session_end has no result. message HttpResponseEventResult { oneof result { // Result for preflight. HttpResponsePreflightResult preflight_result = 1; // Result for the next body unit. HttpResponseBodyResult body_result = 2; // Result for response trailers. HttpResponseTrailersResult trailers_result = 3; } } // HttpResponsePreflight exposes the current final response head to one stage. message HttpResponsePreflight { // Request identity. request_id links request and response evaluations. // Limited to 4 KiB encoded. RequestContext context = 1; // Admitted request target with a redacted query. Limited to 32 KiB encoded. HttpRequestTarget target = 2; // Final non-informational upstream status. Upgrades are not evaluated. uint32 status_code = 3; // Response headers after prior stages, in wire order. Repeated names remain // separate. Credential, routing, and hop-by-hop headers are omitted. // Content-Length, Content-Encoding, and Content-Range retain their read-only // upstream values. OpenShell may recompute or remove Content-Length later. // Limited to 128 lines and 64 KiB encoded. repeated HttpHeader headers = 4; // Built-in middleware name or operator-owned registration name. string middleware_name = 5; // Validated service configuration. Limited to 64 KiB encoded. google.protobuf.Struct config = 6; // Effective minimum of platform, registration, and binding limits. Applies to // whole-body input/replacement and each stream input/replacement. Stream // inputs use at most min(64 KiB, max_payload_bytes). uint64 max_payload_bytes = 7; // Modes derived independently for this stage. OpenShell first determines // response-shape eligibility from the original final response head, then // applies this stage's effective max_payload_bytes. Different stages may // receive different lists. HEADERS_ONLY is always present and is the only // mode for bodyless, partial, encoded, or no-transform responses. For an // otherwise eligible response, a known body larger than this stage's limit // omits WHOLE_BODY_BYTES. An eligible unknown-length response may select // WHOLE_BODY_BYTES and later fail with whole_body_over_capacity according to // this stage's on_error. STREAM_BYTES is omitted when // max_payload_bytes is zero. Selecting an unlisted mode fails according to // on_error. repeated HttpResponseBodyMode permitted_body_modes = 8; } // Selects skip, inspect, or block. Diagnostic fields apply to every action. // Invalid diagnostics make the entire result a middleware failure handled // according to on_error. message HttpResponsePreflightResult { oneof action { // Deliver unchanged without invoking on_error. HttpResponsePreflightSkip skip = 1; // Inspect with the selected body mode and mutations. HttpResponsePreflightInspect inspect = 2; // Prevent delivery to the sandbox. HttpResponseBlockDelivery block_delivery = 7; } // Service diagnostic, never sent to the sandbox or security logs. Maximum // 4 KiB. string reason = 3; // Optional audit code using the HttpRequestResult.reason_code format and // 64-byte maximum. Returned to the sandbox only for block_delivery. string reason_code = 4; // Up to 32 audit-safe findings, each limited to 4 KiB encoded. repeated Finding findings = 5; // Non-secret diagnostic metadata, limited to 64 entries and 32 KiB. map metadata = 6; } // Ends this stage successfully without body inspection. message HttpResponsePreflightSkip {} // Blocks delivery as a successful decision regardless of on_error. OpenShell // evaluates results in policy order. Once it accepts a valid block, it stops // later middleware evaluation and ends every still-writable opened stage with // MIDDLEWARE_DENIAL. A failure handled earlier may already have stopped // evaluation, so a later block does not override it. An invalid block result // is a middleware failure handled according to on_error. The upstream request // has already run; blocking its response does not reject or roll back that // request. // // Before response commitment, including at preflight and during // WHOLE_BODY_BYTES, OpenShell replaces the upstream response with the canonical // 403 Forbidden middleware-denial response. Its JSON body has // error = "middleware_denied" and includes a validated reason_code when the // result supplies one. OpenShell never returns the free-form reason or writes it // to security logs. For HEAD, OpenShell sends the canonical response headers // and Content-Length but no body. It closes the downstream connection after the // denial response. // // After response commitment, including during STREAM_BYTES, OpenShell aborts // downstream delivery. It does not inject an error body, a terminating chunk, // or an error trailer. OpenShell does not reuse the upstream connection. message HttpResponseBlockDelivery {} // Selects body inspection and response-header mutations. message HttpResponsePreflightInspect { // Required mode from permitted_body_modes. Invalid values fail according to // on_error. HttpResponseBodyMode body_mode = 1; // Ordered mutations applied atomically before the next stage. Only visible // end-to-end headers may change. Routing, credential, framing, coding, range, // and hop-by-hop headers are protected; integrity headers may only be removed. // Limited to 64 operations, 32 KiB of name/value data, and 64 KiB encoded. repeated HeaderMutation header_mutations = 2; } // Controls which response-body units a stage receives. enum HttpResponseBodyMode { // Invalid value handled according to on_error. HTTP_RESPONSE_BODY_MODE_UNSPECIFIED = 0; // Inspect only the response head. HTTP_RESPONSE_BODY_MODE_HEADERS_ONLY = 1; // Buffer the normalized body as one final unit before committing the head. // Input and replacement must fit max_payload_bytes. Capacity failures use // whole_body_over_capacity and follow this stage's on_error. HTTP_RESPONSE_BODY_MODE_WHOLE_BODY_BYTES = 2; // Receive normalized units ending with end_of_stream. Each input is at most // min(64 KiB, max_payload_bytes), and each replacement must fit // max_payload_bytes. Each result fully accounts for its input unit; V1 does // not permit retaining input across units. The full body may exceed the // limit. STREAM_BYTES has no total response-lifetime deadline. HTTP_RESPONSE_BODY_MODE_STREAM_BYTES = 3; } // One normalized body unit. Boundaries have no transport or application // meaning. message HttpResponseBodyUnit { // Contiguous and stage-local, starting at 1. uint64 sequence = 1; oneof payload { // Bytes without transfer framing. A body-capable response with no body bytes // has present empty data in sequence 1. STREAM_BYTES input size is at most // min(64 KiB, max_payload_bytes). A unit may be shorter to preserve // flushing. bytes data = 2; } // Marks the final body unit. Every normally completed body inspection receives // exactly one. For a body-capable response with no body bytes, this is the // empty sequence-1 unit. OpenShell does not read ahead, so it may send an empty // final unit after the last nonempty unit. Trailers and session_end may // follow. A stage ended by skip_remaining, block, or failure receives no // later final unit. bool end_of_stream = 3; } // Result for one body unit. Units are processed in lockstep; V1 does not // support ownership transfer or cross-unit retention. Diagnostic fields apply // to every action. Invalid diagnostics make the entire result a middleware // failure handled according to on_error. OpenShell retains the current input // until it validates the result, so fail-open can continue from the last input // OpenShell still owns. message HttpResponseBodyResult { // Must match the next unit. Zero, gaps, duplicates, and regressions fail. uint64 sequence = 1; // Exactly one explicit action is required. oneof action { // Forward the input unit unchanged. HttpResponseBodyPassThrough pass_through = 2; // Replace the complete input unit. HttpResponseBodyTransform transform = 3; // Stop delivery. See HttpResponseBlockDelivery. HttpResponseBlockDelivery block_delivery = 8; // Finalize this unit and stop inspecting. HttpResponseBodySkipRemaining skip_remaining = 9; } // Service diagnostic, never sent to the sandbox or security logs. Maximum // 4 KiB. string reason = 4; // Optional audit code using the HttpRequestResult.reason_code format and // 64-byte maximum. When OpenShell accepts block_delivery before response // commitment, it includes this code in the canonical denial response. It is // never returned after commitment. string reason_code = 5; // Up to 32 audit-safe findings, each limited to 4 KiB encoded. repeated Finding findings = 6; // Non-secret diagnostic metadata, limited to 64 entries and 32 KiB. map metadata = 7; } // Preserves the input unit. message HttpResponseBodyPassThrough {} // Finalizes this unit and ends the stage. This stage receives no later body or // trailer events. The current and later units continue through other stages. // For WHOLE_BODY_BYTES, this equals its nested action. message HttpResponseBodySkipRemaining { // Exactly one action for the current unit. oneof current { // Forward the current unit unchanged. HttpResponseBodyPassThrough pass_through = 1; // Replace the current unit. HttpResponseBodyTransform transform = 2; } } // Replaces the complete input unit. message HttpResponseBodyTransform { // Required replacement, limited to max_payload_bytes. Present empty data // deletes the input unit. The replacement fully accounts for this input unit; // middleware must not retain input bytes for a later unit in V1. oneof replacement { // Normalized replacement bytes. bytes data = 1; } } // The current normalized response trailers in wire order. Repeated names stay // as separate fields. A stage that completes WHOLE_BODY_BYTES or STREAM_BYTES // receives exactly one trailers event after its final body result, including // when this set is empty. SKIP, HEADERS_ONLY, semantically bodyless responses, // and stages ended by block, failure, or skip_remaining receive no trailers. message HttpResponseTrailers { repeated HttpHeader headers = 1; } // Applies ordered trailer mutations atomically. An empty mutation list // preserves the current trailers. A write may target only a case-insensitive // name present in the trailers event; V1 cannot create a trailer name. Removal // of an absent name is a no-op. Credential, routing, framing, coding, range, // hop-by-hop, and connection-nominated fields are protected. Diagnostic fields // apply whether mutations are empty or nonempty. Invalid diagnostics or // mutations make the entire result a middleware failure handled according to // on_error. message HttpResponseTrailersResult { // At most 64 operations, 32 KiB of validated name/value data, and 64 KiB // encoded are accepted. repeated HeaderMutation trailer_mutations = 1; // Service diagnostic, never sent to the sandbox or security logs. Maximum // 4 KiB. string reason = 2; // Optional audit code using the HttpRequestResult.reason_code format and // 64-byte maximum. Never sent to the sandbox. string reason_code = 3; // Up to 32 audit-safe findings, each limited to 4 KiB encoded. repeated Finding findings = 4; // Non-secret diagnostic metadata, limited to 64 entries and 32 KiB. map metadata = 5; } // Stable reason OpenShell ended a middleware stage stream. enum MiddlewareSessionEndReason { // Invalid reason. MIDDLEWARE_SESSION_END_REASON_UNSPECIFIED = 0; // Evaluation completed. MIDDLEWARE_SESSION_END_REASON_NORMAL = 1; // The sandbox peer disconnected. MIDDLEWARE_SESSION_END_REASON_DOWNSTREAM_DISCONNECT = 2; // A policy reload replaced the active middleware chain. MIDDLEWARE_SESSION_END_REASON_POLICY_RELOAD = 3; // A stage denied the operation or blocked the response. MIDDLEWARE_SESSION_END_REASON_MIDDLEWARE_DENIAL = 4; // A selected stage failed. MIDDLEWARE_SESSION_END_REASON_MIDDLEWARE_FAILURE = 5; // A proxied or middleware protocol was violated. MIDDLEWARE_SESSION_END_REASON_PROTOCOL_ERROR = 6; // Evaluation was canceled for another reason. MIDDLEWARE_SESSION_END_REASON_CANCELLATION = 7; // Upstream rejected or failed before a valid response or upgrade. MIDDLEWARE_SESSION_END_REASON_UPSTREAM_FAILURE = 8; // Network policy denied the operation. MIDDLEWARE_SESSION_END_REASON_POLICY_DENIAL = 9; // The stage successfully declined inspection during preflight. MIDDLEWARE_SESSION_END_REASON_STAGE_SKIPPED = 10; // Upstream disconnected after a valid response or upgrade. MIDDLEWARE_SESSION_END_REASON_UPSTREAM_DISCONNECT = 11; } // Best-effort terminal notification. A stage receives at most one and sends no // result. message MiddlewareSessionEnd { // Terminal reason. Producers never send UNSPECIFIED. MiddlewareSessionEndReason reason = 1; // Set only for PROTOCOL_ERROR. Missing or unknown details mean a generic // protocol error. MiddlewareSessionProtocolError protocol_error = 2; } // Details for a protocol-error session end. message MiddlewareSessionProtocolError { oneof domain { // WebSocket protocol violation. WebSocketProtocolError web_socket = 1; // Middleware event/result protocol violation. MiddlewareExchangeProtocolError middleware_exchange = 2; } } // WebSocket protocol error details, reserved for future categories. message WebSocketProtocolError {} // Middleware exchange error details, reserved for future categories. message MiddlewareExchangeProtocolError {} // Supervisor operation selected for middleware evaluation. enum SupervisorMiddlewareOperation { SUPERVISOR_MIDDLEWARE_OPERATION_UNSPECIFIED = 0; SUPERVISOR_MIDDLEWARE_OPERATION_HTTP_REQUEST = 1; SUPERVISOR_MIDDLEWARE_OPERATION_WEBSOCKET_MESSAGE = 2; SUPERVISOR_MIDDLEWARE_OPERATION_HTTP_RESPONSE = 3; } // Ordered phase within a supervisor operation. enum SupervisorMiddlewarePhase { SUPERVISOR_MIDDLEWARE_PHASE_UNSPECIFIED = 0; SUPERVISOR_MIDDLEWARE_PHASE_PRE_CREDENTIALS = 1; SUPERVISOR_MIDDLEWARE_PHASE_PRE_RETURN = 2; } // WebSocketSessionEvent is one ordered event in a stage-local stream. // Message sequence numbers identify logical messages session-wide. A stage // receives a strictly increasing subset of those numbers; gaps are valid when // session messages are not delivered to that stage. message WebSocketSessionEvent { oneof event { WebSocketPreflight preflight = 1; WebSocketSessionStart session_start = 2; WebSocketMessage message = 3; MiddlewareSessionEnd session_end = 4; } } // WebSocketPreflight lets a service decline this upgrade before OpenShell // contacts upstream. It deliberately excludes query data, arbitrary request // headers, and message payloads. message WebSocketPreflight { string session_id = 1; SupervisorMiddlewarePhase phase = 2; RequestContext context = 3; // Admitted HTTP WebSocket-upgrade target. The method is GET, query is always // empty, and path never includes a query string. HttpRequestTarget target = 4; repeated string requested_subprotocols = 5; // Built-in middleware name or operator-owned registration name. string middleware_name = 6; google.protobuf.Struct config = 7; } // WebSocketSessionStart reports bounded metadata known only after the // upstream 101 response validates. Empty selected_subprotocol means none. message WebSocketSessionStart { string selected_subprotocol = 1; } // WebSocketMessage contains one complete reconstructed logical message. message WebSocketMessage { // Session-global sequence starting at 1. Values delivered to one stage must // strictly increase but need not be contiguous. Reject zero, duplicates, and // regressions; accept gaps. uint64 sequence = 1; // One complete logical payload. Protobuf string decoding enforces UTF-8 for // text messages. Raw frame mechanics are never exposed. Limited to 4 MiB by // the platform and the binding-specific cap. oneof payload { string text = 2; bytes binary = 3; } } // WebSocketPreflightAction is the service's one-time scoping decision. enum WebSocketPreflightAction { // Invalid response value handled according to the policy failure mode. WEB_SOCKET_PREFLIGHT_ACTION_UNSPECIFIED = 0; // Inspect this session after the upstream accepts the upgrade. WEB_SOCKET_PREFLIGHT_ACTION_INSPECT = 1; // Voluntarily decline inspection without denying the upgrade. This is a // successful decision and does not engage on_error. WEB_SOCKET_PREFLIGHT_ACTION_SKIP = 2; // Authoritatively deny the upgrade before upstream contact. This is a // successful decision and is enforced regardless of on_error. WEB_SOCKET_PREFLIGHT_ACTION_DENY = 3; } message WebSocketPreflightDecision { WebSocketPreflightAction action = 1; // Free-form service diagnostic. OpenShell never exposes this to the // workload or security logs. Limited to 4 KiB before discarding. string reason = 2; // Optional stable machine-readable code for a deny decision. Because // preflight runs before the HTTP upgrade completes, OpenShell may return // this code to the requester. Codes follow the same format and 64-byte // maximum as HttpRequestResult.reason_code. string reason_code = 3; // Audit-safe findings produced during preflight. At most 32 findings of at // most 4 KiB encoded each are accepted. repeated Finding findings = 4; // Non-secret service-defined metadata included in diagnostics. At most 64 // entries and 32 KiB of combined key/value data are accepted. map metadata = 5; } // WebSocketMessageResult contains the decision and optional replacement for // one message. A replacement must use the same variant as the input payload. message WebSocketMessageResult { // Must exactly match the sequence of the corresponding WebSocketMessage. uint64 sequence = 1; Decision decision = 2; // Absence preserves the input unchanged. Oneof presence distinguishes an // empty replacement from no replacement, and string decoding enforces UTF-8. oneof replacement { string text = 3; bytes binary = 4; } // Free-form service diagnostic. OpenShell never exposes this to the // workload or security logs. Limited to 4 KiB before discarding. string reason = 5; // Optional stable machine-readable code for OCSF only. Unlike the HTTP // reason_code, this value is never put in a WebSocket close frame. string reason_code = 6; repeated Finding findings = 7; map metadata = 8; } // WebSocketSessionEventResult is an evaluation result for a preflight or message // event. Session start and end events do not produce results. message WebSocketSessionEventResult { oneof result { WebSocketPreflightDecision preflight_decision = 1; WebSocketMessageResult message_result = 2; } } // RequestContext identifies the sandbox request being evaluated. message RequestContext { // Request id used to correlate middleware and supervisor logs. string request_id = 1; // Sandbox id that originated the request. string sandbox_id = 2; // Workload process that originated the request, when available. Process originating_process = 3; // Sandbox name that originated the request. For display and logging only. // Names are workspace-scoped and may be reused for different sandbox // instances, so consumers must use sandbox_id for authorization, persistence, // durable correlation, and identity. string sandbox = 4; // Workspace the sandbox belongs to. For display and logging only; see the // sandbox guidance above. string workspace = 5; } // HttpRequestTarget describes the admitted HTTP destination and request target. message HttpRequestTarget { // Request scheme, such as "http", "https", "ws", or "wss". string scheme = 1; // Destination hostname selected by network policy. string host = 2; // Destination TCP port. uint32 port = 3; // HTTP request method. string method = 4; // Request path without the query string. string path = 5; // Raw request query string without the leading question mark. string query = 6; } // Process identifies a workload process and its executable ancestry. message Process { // Executable path for the originating process. string binary = 1; // Process id within the sandbox. uint32 pid = 2; // Executable paths for ancestor processes, nearest parent first. repeated string ancestors = 3; } // Decision controls whether OpenShell continues processing the current // evaluation unit. enum Decision { // Invalid response value handled according to the policy failure mode. DECISION_UNSPECIFIED = 0; // Continue processing the current request or message and apply any returned // mutations. DECISION_ALLOW = 1; // Reject the current request or message. The operation-specific result // defines the enclosing protocol behavior. DECISION_DENY = 2; } // Finding is an audit-safe observation produced during evaluation. message Finding { // Stable, service-defined finding type. string type = 1; // Human-readable finding label that does not contain request content. string label = 2; // Number of matching observations represented by this finding. uint32 count = 3; // Service-defined confidence level. string confidence = 4; // Service-defined severity level. string severity = 5; } // ExistingHeaderAction controls how a header write behaves when the // case-insensitive header name is already present. Every action writes the // value when the header is absent. enum ExistingHeaderAction { EXISTING_HEADER_ACTION_UNSPECIFIED = 0; // Add another field value without changing existing values. EXISTING_HEADER_ACTION_APPEND = 1; // Remove every existing value, then add the new value. EXISTING_HEADER_ACTION_OVERWRITE = 2; // Leave the existing values unchanged. EXISTING_HEADER_ACTION_SKIP = 3; } // WriteHeader proposes one header value and defines collision behavior. message WriteHeader { string name = 1; string value = 2; ExistingHeaderAction on_existing = 3; } // RemoveHeader removes every value for a case-insensitive header name. message RemoveHeader { string name = 1; } // HeaderMutation is one ordered HTTP header operation. message HeaderMutation { oneof operation { WriteHeader write = 1; RemoveHeader remove = 2; } } // HttpRequestResult contains the decision and optional request mutations. message HttpRequestResult { // Allow or deny decision for this request. Decision decision = 1; // Free-form service diagnostic. OpenShell does not relay this text into // denied responses or security logs. Limited to 4 KiB before discarding. string reason = 2; // Replacement request body when has_body is true. Limited to 4 MiB. bytes body = 3; // True when body should replace the request body, including with an empty body. bool has_body = 4; // Ordered request-header mutations applied before the next middleware and // before forwarding. Writes and removals may target visible end-to-end // request headers, but credential, routing, framing, and hop-by-hop headers // are always protected. Written values cannot contain OpenShell credential // placeholder syntax. A violating result is a middleware failure handled // according to the policy failure mode. At most 64 operations, 32 KiB of // validated name/value data, and 64 KiB encoded are accepted. repeated HeaderMutation header_mutations = 5; // Audit-safe findings produced during evaluation. For operator-run services, // OpenShell logs platform-owned fields derived from the operator-owned // registration name rather than service-provided type, label, confidence, // or metadata text. // At most 32 findings of at most 4 KiB encoded each are accepted per stage. // A policy selects at most 10 stages, so one chain retains at most 320. repeated Finding findings = 6; // Non-secret service-defined metadata included in diagnostics. At most 64 // entries and 32 KiB of combined key/value data are accepted. map metadata = 7; // Optional stable machine-readable code for a deny decision. Codes must // start with a lowercase ASCII letter and contain only lowercase ASCII // letters, digits, and underscores, with a maximum length of 64 bytes. // OpenShell may return this code to the requester, unlike free-form reason. string reason_code = 8; }