mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-20 06:00:06 +08:00
* refactor(proto)!: use well-known time types Signed-off-by: Derek Carr <decarr@redhat.com> * fix(proto): preserve time migration behavior Signed-off-by: Derek Carr <decarr@redhat.com> * fix(proto): preserve timestamp boundary semantics Signed-off-by: Derek Carr <decarr@redhat.com> * fix(proto): convert sandbox token expiry to timestamp Signed-off-by: Derek Carr <decarr@redhat.com> * fix(proto): preserve time compatibility semantics Signed-off-by: Derek Carr <decarr@redhat.com> * fix(proto): preserve exact endpoint and profile times Signed-off-by: Derek Carr <decarr@redhat.com> * test(e2e): use duration for interactive exec timeout Signed-off-by: Derek Carr <decarr@redhat.com> * fix(sdk-go)!: remove legacy profile duration fields BREAKING CHANGE: Go provider profile callers must use RefreshBefore, MaxLifetime, and CacheTTL with ProfileDuration instead of the whole-second fields. Signed-off-by: Derek Carr <decarr@redhat.com> --------- Signed-off-by: Derek Carr <decarr@redhat.com>
713 lines
30 KiB
Protocol Buffer
713 lines
30 KiB
Protocol Buffer
// 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/empty.proto";
|
|
import "google/protobuf/struct.proto";
|
|
import "google/protobuf/duration.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(google.protobuf.Empty) 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);
|
|
}
|
|
|
|
// 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;
|
|
// Release version of the middleware service implementation, used for
|
|
// diagnostics.
|
|
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;
|
|
}
|
|
|
|
// 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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_name = 4;
|
|
// Workspace the sandbox belongs to. For display and logging only; see the
|
|
// sandbox_name 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<string, string> 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;
|
|
}
|