diff --git a/crates/right-openshell/proto/UPSTREAM.md b/crates/right-openshell/proto/UPSTREAM.md index 7cb43f68..4c3c883b 100644 --- a/crates/right-openshell/proto/UPSTREAM.md +++ b/crates/right-openshell/proto/UPSTREAM.md @@ -1,3 +1,3 @@ -tag: v0.0.62 -fetched: 2026-06-13T07:00:33Z +tag: v0.0.101 +fetched: 2026-08-10T06:03:48Z upstream: https://github.com/NVIDIA/OpenShell diff --git a/crates/right-openshell/proto/openshell/datamodel.proto b/crates/right-openshell/proto/openshell/datamodel.proto index f92d7b7a..b990f057 100644 --- a/crates/right-openshell/proto/openshell/datamodel.proto +++ b/crates/right-openshell/proto/openshell/datamodel.proto @@ -5,10 +5,13 @@ syntax = "proto3"; package openshell.datamodel.v1; +import "options.proto"; + // Kubernetes-style metadata shared by all top-level OpenShell domain objects. // -// This structure provides consistent metadata (identity, labels, timestamps, -// resource versioning) across Sandbox, Provider, SshSession, and other resources. +// This structure provides consistent metadata (identity, labels, annotations, +// timestamps, resource versioning) across Sandbox, Provider, SshSession, and +// other resources. message ObjectMeta { // Stable object ID generated by the gateway. string id = 1; @@ -26,6 +29,54 @@ message ObjectMeta { // Optimistic concurrency control version. // Incremented by the gateway on each update. Clients can use this for compare-and-swap operations. uint64 resource_version = 5; + + // Opaque key-value metadata that is not used for selectors. + // Annotation keys use the same qualified-key shape as labels, but values may be longer. + map annotations = 6; + + // Workspace that owns this resource. Empty is normalized to "default" by the + // gateway. Immutable after creation. + string workspace = 7; + + // Milliseconds since Unix epoch when graceful deletion was initiated. + // Zero means the object is not being deleted. Once set, this field is + // immutable — the only path forward is completing deletion. + int64 deletion_timestamp_ms = 8; +} + +// Phase of a workspace's lifecycle. +enum WorkspacePhase { + WORKSPACE_PHASE_UNSPECIFIED = 0; + WORKSPACE_PHASE_ACTIVE = 1; + WORKSPACE_PHASE_TERMINATING = 2; +} + +// Status of a workspace. +message WorkspaceStatus { + WorkspacePhase phase = 1; +} + +// Workspace resource. A hard isolation boundary for sandboxes, providers, and +// other workspace-scoped resources. +message Workspace { + // Kubernetes-style metadata (id, name, labels, timestamps, resource version). + // The workspace field in this ObjectMeta is unused (a workspace does not + // belong to another workspace). + ObjectMeta metadata = 1; + + // Current lifecycle status. + WorkspaceStatus status = 2; +} + +// Opaque handle for a provider credential stored by gateway credential storage. +// Handles are created by OpenShell and must not be authored by users. +message CredentialHandle { + // Internal storage owner or credential driver that owns this handle. + string driver = 1; + // Owner-owned opaque handle string. + string handle = 2; + // Owner-owned non-secret metadata. + map metadata = 3; } // Provider model stored by OpenShell. @@ -35,10 +86,17 @@ message Provider { // Canonical provider type slug (for example: "claude", "gitlab"). string type = 2; // Secret values used for authentication. - map credentials = 3; + map credentials = 3 [(openshell.options.v1.secret) = true]; // Non-secret provider configuration. map config = 4; // Expiration timestamps for credential values, keyed by credential/env var // name. A zero or missing value means the credential does not expire. map credential_expires_at_ms = 5; + // Workspace where this provider's type profile is stored. + // Empty string = platform/global scope. Must be empty or match + // metadata.workspace; cross-workspace references are rejected. + string profile_workspace = 6; + // Opaque handles for secret values stored through gateway credential storage. + // This map is internal gateway state and is not accepted as user-authored input. + map credential_handles = 7; } diff --git a/crates/right-openshell/proto/openshell/openshell.proto b/crates/right-openshell/proto/openshell/openshell.proto index d701956d..9f2fdf90 100644 --- a/crates/right-openshell/proto/openshell/openshell.proto +++ b/crates/right-openshell/proto/openshell/openshell.proto @@ -7,6 +7,7 @@ package openshell.v1; import "datamodel.proto"; import "google/protobuf/struct.proto"; +import "options.proto"; import "sandbox.proto"; // OpenShell service provides sandbox, provider, and runtime management capabilities. @@ -19,145 +20,409 @@ import "sandbox.proto"; // resource messages before persisting or returning them to clients. service OpenShell { // Check the health of the service. - rpc Health(HealthRequest) returns (HealthResponse); + rpc Health(HealthRequest) returns (HealthResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "unauthenticated" + }; + } + + // Return the authenticated caller identity established by the gateway. + rpc GetCurrentUser(GetCurrentUserRequest) returns (GetCurrentUserResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + }; + } + + // Fetch elevated live gateway runtime metadata. + rpc GetGatewayInfo(GetGatewayInfoRequest) returns (GetGatewayInfoResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:read" + global_role: "platform_admin" + }; + } // Create a new sandbox. - rpc CreateSandbox(CreateSandboxRequest) returns (SandboxResponse); + rpc CreateSandbox(CreateSandboxRequest) returns (SandboxResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Fetch a sandbox by name. - rpc GetSandbox(GetSandboxRequest) returns (SandboxResponse); + rpc GetSandbox(GetSandboxRequest) returns (SandboxResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // List sandboxes. - rpc ListSandboxes(ListSandboxesRequest) returns (ListSandboxesResponse); + rpc ListSandboxes(ListSandboxesRequest) returns (ListSandboxesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // List provider records attached to a sandbox. rpc ListSandboxProviders(ListSandboxProvidersRequest) - returns (ListSandboxProvidersResponse); + returns (ListSandboxProvidersResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // Attach a provider record to an existing sandbox. rpc AttachSandboxProvider(AttachSandboxProviderRequest) - returns (AttachSandboxProviderResponse); + returns (AttachSandboxProviderResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Detach a provider record from an existing sandbox. rpc DetachSandboxProvider(DetachSandboxProviderRequest) - returns (DetachSandboxProviderResponse); + returns (DetachSandboxProviderResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Delete a sandbox by name. - rpc DeleteSandbox(DeleteSandboxRequest) returns (DeleteSandboxResponse); + rpc DeleteSandbox(DeleteSandboxRequest) returns (DeleteSandboxResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Create a short-lived SSH session for a sandbox. - rpc CreateSshSession(CreateSshSessionRequest) returns (CreateSshSessionResponse); + rpc CreateSshSession(CreateSshSessionRequest) returns (CreateSshSessionResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Create or update a sandbox HTTP service endpoint for local routing. - rpc ExposeService(ExposeServiceRequest) returns (ServiceEndpointResponse); + rpc ExposeService(ExposeServiceRequest) returns (ServiceEndpointResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Fetch one sandbox HTTP service endpoint. - rpc GetService(GetServiceRequest) returns (ServiceEndpointResponse); + rpc GetService(GetServiceRequest) returns (ServiceEndpointResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // List sandbox HTTP service endpoints. - rpc ListServices(ListServicesRequest) returns (ListServicesResponse); + rpc ListServices(ListServicesRequest) returns (ListServicesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // Delete one sandbox HTTP service endpoint. - rpc DeleteService(DeleteServiceRequest) returns (DeleteServiceResponse); + rpc DeleteService(DeleteServiceRequest) returns (DeleteServiceResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Revoke a previously issued SSH session. - rpc RevokeSshSession(RevokeSshSessionRequest) returns (RevokeSshSessionResponse); + rpc RevokeSshSession(RevokeSshSessionRequest) returns (RevokeSshSessionResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Execute a command in a ready sandbox and stream output. - rpc ExecSandbox(ExecSandboxRequest) returns (stream ExecSandboxEvent); + rpc ExecSandbox(ExecSandboxRequest) returns (stream ExecSandboxEvent) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Forward one CLI-side TCP connection to a loopback TCP target in a sandbox. - rpc ForwardTcp(stream TcpForwardFrame) returns (stream TcpForwardFrame); + rpc ForwardTcp(stream TcpForwardFrame) returns (stream TcpForwardFrame) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Execute an interactive command with bidirectional stdin/stdout streaming. // The first client message MUST carry an ExecSandboxInput with the start // variant. Subsequent messages carry stdin bytes or window resize events. - rpc ExecSandboxInteractive(stream ExecSandboxInput) returns (stream ExecSandboxEvent); + rpc ExecSandboxInteractive(stream ExecSandboxInput) returns (stream ExecSandboxEvent) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } // Create a provider. - rpc CreateProvider(CreateProviderRequest) returns (ProviderResponse); + rpc CreateProvider(CreateProviderRequest) returns (ProviderResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Fetch a provider by name. - rpc GetProvider(GetProviderRequest) returns (ProviderResponse); + rpc GetProvider(GetProviderRequest) returns (ProviderResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + workspace_role: "user" + }; + } // List providers. - rpc ListProviders(ListProvidersRequest) returns (ListProvidersResponse); + rpc ListProviders(ListProvidersRequest) returns (ListProvidersResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + workspace_role: "user" + }; + } // List available provider type profiles. rpc ListProviderProfiles(ListProviderProfilesRequest) - returns (ListProviderProfilesResponse); + returns (ListProviderProfilesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + workspace_role: "user" + }; + } // Fetch one provider type profile by id. rpc GetProviderProfile(GetProviderProfileRequest) - returns (ProviderProfileResponse); + returns (ProviderProfileResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + workspace_role: "user" + }; + } // Import custom provider type profiles. rpc ImportProviderProfiles(ImportProviderProfilesRequest) - returns (ImportProviderProfilesResponse); + returns (ImportProviderProfilesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } + + // Update an existing custom provider type profile. + rpc UpdateProviderProfiles(UpdateProviderProfilesRequest) + returns (UpdateProviderProfilesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Validate provider type profiles without registering them. rpc LintProviderProfiles(LintProviderProfilesRequest) - returns (LintProviderProfilesResponse); + returns (LintProviderProfilesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + workspace_role: "user" + }; + } // Update an existing provider by name. - rpc UpdateProvider(UpdateProviderRequest) returns (ProviderResponse); + rpc UpdateProvider(UpdateProviderRequest) returns (ProviderResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Fetch refresh status for one provider or provider credential. rpc GetProviderRefreshStatus(GetProviderRefreshStatusRequest) - returns (GetProviderRefreshStatusResponse); + returns (GetProviderRefreshStatusResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + workspace_role: "user" + }; + } // Configure gateway-owned refresh material for one provider credential. rpc ConfigureProviderRefresh(ConfigureProviderRefreshRequest) - returns (ConfigureProviderRefreshResponse); + returns (ConfigureProviderRefreshResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Record a gateway-owned refresh request for one provider credential. rpc RotateProviderCredential(RotateProviderCredentialRequest) - returns (RotateProviderCredentialResponse); + returns (RotateProviderCredentialResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Delete gateway-owned refresh configuration for one provider credential. rpc DeleteProviderRefresh(DeleteProviderRefreshRequest) - returns (DeleteProviderRefreshResponse); + returns (DeleteProviderRefreshResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Delete a provider by name. - rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse); + rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Delete a custom provider type profile by id. rpc DeleteProviderProfile(DeleteProviderProfileRequest) - returns (DeleteProviderProfileResponse); + returns (DeleteProviderProfileResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + workspace_role: "admin" + }; + } // Get sandbox settings by id (called by sandbox entrypoint and poll loop). rpc GetSandboxConfig(openshell.sandbox.v1.GetSandboxConfigRequest) - returns (openshell.sandbox.v1.GetSandboxConfigResponse); + returns (openshell.sandbox.v1.GetSandboxConfigResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "dual" + scope: "config:read" + workspace_role: "user" + }; + } - // Get gateway-global settings. + // Get gateway-global settings (read-only feature flags; any authenticated + // user may read these so the CLI and TUI can discover capabilities like + // providers_v2_enabled without requiring Platform Admin). + // + // Scope-only (no role): scopes are granted by the IdP at token issuance, + // orthogonal to workspace membership. Deployments that enable scope + // enforcement configure the IdP to grant config:read (or openshell:all) + // to all sandbox users, so this does not block least-privilege flows. rpc GetGatewayConfig(openshell.sandbox.v1.GetGatewayConfigRequest) - returns (openshell.sandbox.v1.GetGatewayConfigResponse); + returns (openshell.sandbox.v1.GetGatewayConfigResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:read" + }; + } // Update settings or policy at sandbox or global scope. rpc UpdateConfig(UpdateConfigRequest) - returns (UpdateConfigResponse); + returns (UpdateConfigResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "dual" + scope: "config:write" + workspace_role: "admin" + }; + } // Get the load status of a specific policy version. rpc GetSandboxPolicyStatus(GetSandboxPolicyStatusRequest) - returns (GetSandboxPolicyStatusResponse); + returns (GetSandboxPolicyStatusResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // List policy history for a sandbox. rpc ListSandboxPolicies(ListSandboxPoliciesRequest) - returns (ListSandboxPoliciesResponse); + returns (ListSandboxPoliciesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // Report policy load result (called by sandbox after reload attempt). rpc ReportPolicyStatus(ReportPolicyStatusRequest) - returns (ReportPolicyStatusResponse); + returns (ReportPolicyStatusResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } // Get provider environment for a sandbox (called by sandbox supervisor at startup). rpc GetSandboxProviderEnvironment(GetSandboxProviderEnvironmentRequest) - returns (GetSandboxProviderEnvironmentResponse); + returns (GetSandboxProviderEnvironmentResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } // Fetch recent sandbox logs (one-shot). - rpc GetSandboxLogs(GetSandboxLogsRequest) returns (GetSandboxLogsResponse); + rpc GetSandboxLogs(GetSandboxLogsRequest) returns (GetSandboxLogsResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // Push sandbox supervisor logs to the server (client-streaming). - rpc PushSandboxLogs(stream PushSandboxLogsRequest) returns (PushSandboxLogsResponse); + rpc PushSandboxLogs(stream PushSandboxLogsRequest) returns (PushSandboxLogsResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } // Persistent supervisor-to-gateway session (bidirectional streaming). // @@ -166,7 +431,11 @@ service OpenShell { // SSH connect, ExecSandbox, and targetable sandbox services. Raw service // bytes flow over RelayStream calls (separate HTTP/2 streams on the same // connection), not over this stream. - rpc ConnectSupervisor(stream SupervisorMessage) returns (stream GatewayMessage); + rpc ConnectSupervisor(stream SupervisorMessage) returns (stream GatewayMessage) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } // Raw byte relay between supervisor and gateway. // @@ -179,7 +448,11 @@ service OpenShell { // // This rides the same TCP+TLS+HTTP/2 connection as ConnectSupervisor — // no new TLS handshake, no reverse HTTP CONNECT. - rpc RelayStream(stream RelayFrame) returns (stream RelayFrame); + rpc RelayStream(stream RelayFrame) returns (stream RelayFrame) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } // Watch a sandbox and stream updates. // @@ -187,7 +460,13 @@ service OpenShell { // - Sandbox status snapshots (phase/status) // - OpenShell server process logs correlated by sandbox_id // - Platform events correlated to the sandbox - rpc WatchSandbox(WatchSandboxRequest) returns (stream SandboxStreamEvent); + rpc WatchSandbox(WatchSandboxRequest) returns (stream SandboxStreamEvent) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } // --------------------------------------------------------------------------- // Draft policy recommendation RPCs @@ -195,42 +474,98 @@ service OpenShell { // Submit denial analysis results from sandbox (summaries + proposed chunks). rpc SubmitPolicyAnalysis(SubmitPolicyAnalysisRequest) - returns (SubmitPolicyAnalysisResponse); + returns (SubmitPolicyAnalysisResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } // Get draft policy recommendations for a sandbox. - rpc GetDraftPolicy(GetDraftPolicyRequest) returns (GetDraftPolicyResponse); + rpc GetDraftPolicy(GetDraftPolicyRequest) returns (GetDraftPolicyResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "dual" + scope: "config:read" + workspace_role: "user" + }; + } // Approve a single draft policy chunk (merges into active policy). rpc ApproveDraftChunk(ApproveDraftChunkRequest) - returns (ApproveDraftChunkResponse); + returns (ApproveDraftChunkResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:write" + workspace_role: "admin" + }; + } // Reject a single draft policy chunk. rpc RejectDraftChunk(RejectDraftChunkRequest) - returns (RejectDraftChunkResponse); + returns (RejectDraftChunkResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:write" + workspace_role: "admin" + }; + } // Approve all pending draft chunks (skips security-flagged unless forced). rpc ApproveAllDraftChunks(ApproveAllDraftChunksRequest) - returns (ApproveAllDraftChunksResponse); + returns (ApproveAllDraftChunksResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:write" + workspace_role: "admin" + }; + } // Edit a pending draft chunk in-place (e.g. narrow allowed_ips). - rpc EditDraftChunk(EditDraftChunkRequest) returns (EditDraftChunkResponse); + rpc EditDraftChunk(EditDraftChunkRequest) returns (EditDraftChunkResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:write" + workspace_role: "admin" + }; + } // Reverse an approval (remove merged rule from active policy). - rpc UndoDraftChunk(UndoDraftChunkRequest) returns (UndoDraftChunkResponse); + rpc UndoDraftChunk(UndoDraftChunkRequest) returns (UndoDraftChunkResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:write" + workspace_role: "admin" + }; + } // Clear all pending draft chunks for a sandbox. rpc ClearDraftChunks(ClearDraftChunksRequest) - returns (ClearDraftChunksResponse); + returns (ClearDraftChunksResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:write" + workspace_role: "admin" + }; + } // Get decision history for a sandbox's draft policy. - rpc GetDraftHistory(GetDraftHistoryRequest) returns (GetDraftHistoryResponse); + rpc GetDraftHistory(GetDraftHistoryRequest) returns (GetDraftHistoryResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "config:read" + workspace_role: "user" + }; + } // Exchange a sandbox-bootstrap credential (e.g. a Kubernetes projected // ServiceAccount token) for a gateway-minted JWT bound to the calling // sandbox's UUID. Used by the Kubernetes driver path; singleplayer // drivers receive the gateway JWT directly from the create-sandbox flow // and never call this RPC. - rpc IssueSandboxToken(IssueSandboxTokenRequest) returns (IssueSandboxTokenResponse); + rpc IssueSandboxToken(IssueSandboxTokenRequest) returns (IssueSandboxTokenResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } // Renew the calling sandbox's gateway JWT. Older tokens remain valid // until their own expiry; deployments should keep token TTLs short to @@ -239,7 +574,78 @@ service OpenShell { // memory only — the on-disk bootstrap file is intentionally not // rewritten. rpc RefreshSandboxToken(RefreshSandboxTokenRequest) - returns (RefreshSandboxTokenResponse); + returns (RefreshSandboxTokenResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } + + // --------------------------------------------------------------------------- + // Workspace management RPCs + // --------------------------------------------------------------------------- + + // Create a workspace. + rpc CreateWorkspace(CreateWorkspaceRequest) returns (CreateWorkspaceResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "workspace:write" + global_role: "platform_admin" + }; + } + + // Fetch a workspace by name. + rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "workspace:read" + workspace_role: "user" + }; + } + + // List workspaces. + rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "workspace:read" + workspace_role: "user" + }; + } + + // Delete a workspace by name. + rpc DeleteWorkspace(DeleteWorkspaceRequest) returns (DeleteWorkspaceResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "workspace:write" + global_role: "platform_admin" + }; + } + + // Add a member to a workspace. + rpc AddWorkspaceMember(AddWorkspaceMemberRequest) returns (AddWorkspaceMemberResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "workspace:write" + workspace_role: "admin" + }; + } + + // Remove a member from a workspace. + rpc RemoveWorkspaceMember(RemoveWorkspaceMemberRequest) returns (RemoveWorkspaceMemberResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "workspace:write" + workspace_role: "admin" + }; + } + + // List members of a workspace. + rpc ListWorkspaceMembers(ListWorkspaceMembersRequest) returns (ListWorkspaceMembersResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "workspace:read" + workspace_role: "user" + }; + } } // IssueSandboxToken request. Empty body; identity is established by the @@ -252,7 +658,7 @@ message IssueSandboxTokenRequest {} // gateway RPC. message IssueSandboxTokenResponse { // Gateway-minted JWT bound to the calling sandbox's UUID. - string token = 1; + string token = 1 [(openshell.options.v1.secret) = true]; // Absolute expiry of the issued token, milliseconds since the epoch. 0 means // the token is non-expiring. int64 expires_at_ms = 2; @@ -267,7 +673,7 @@ message RefreshSandboxTokenRequest {} // in-memory bearer credential. message RefreshSandboxTokenResponse { // Fresh gateway-minted JWT bound to the same sandbox UUID. - string token = 1; + string token = 1 [(openshell.options.v1.secret) = true]; // Absolute expiry of the new token, milliseconds since the epoch. 0 means // the token is non-expiring. int64 expires_at_ms = 2; @@ -285,6 +691,61 @@ message HealthResponse { string version = 2; } +// Current-user request. The identity comes from the authenticated request. +message GetCurrentUserRequest {} + +// Authenticated user identity as validated by the gateway. +message GetCurrentUserResponse { + // Stable identity subject (for example, the OIDC `sub` claim). + string subject = 1; + + // Human-readable identity name when supplied by the authentication provider. + string display_name = 2; + + // Roles granted to the authenticated identity. + repeated string roles = 3; + + // OAuth2 scopes granted to the authenticated identity. + repeated string scopes = 4; + + // Authentication provider that established the identity. + string identity_provider = 5; +} + +// Gateway info request. +message GetGatewayInfoRequest {} + +// Gateway info response. +message GetGatewayInfoResponse { + // Service status. + ServiceStatus status = 1; + + // OpenShell gateway binary version. + string gateway_version = 2; + + // Compute driver runtimes initialized by this gateway. Current gateways + // return exactly one entry. + repeated ComputeDriverInfo compute_drivers = 3; +} + +// Info for one initialized compute driver runtime. +message ComputeDriverInfo { + // Gateway-selected driver name used for routing and driver_config keys. + string name = 1; + + // Capabilities reported by the driver during gateway runtime initialization. + ComputeDriverCapabilities capabilities = 2; +} + +// Public compute driver capability snapshot. +message ComputeDriverCapabilities { + // Driver-reported human-readable name from the startup capability snapshot. + string driver_name = 1; + + // Driver-reported implementation version from the startup capability snapshot. + string driver_version = 2; +} + // Public sandbox resource exposed by the OpenShell API. // // This is the canonical gateway-owned view of a sandbox. It merges user intent @@ -317,8 +778,9 @@ message SandboxSpec { openshell.sandbox.v1.SandboxPolicy policy = 7; // Provider names to attach to this sandbox. repeated string providers = 8; - // Request NVIDIA GPU resources for this sandbox. - bool gpu = 9; + // Portable resource requirements used by the gateway for driver selection + // and by drivers for provisioning. + ResourceRequirements resource_requirements = 9; reserved 10; reserved "gpu_device"; // Field 11 was `proposal_approval_mode`. The approval mode is now a @@ -329,6 +791,18 @@ message SandboxSpec { reserved "proposal_approval_mode"; } +message ResourceRequirements { + // GPU requirements for the sandbox. Presence indicates a GPU request. + GpuResourceRequirements gpu = 1; +} + +// Public GPU resource requirements. +message GpuResourceRequirements { + // Optional number of GPUs requested. When omitted, the request is for one + // GPU using the selected driver's default assignment behavior. + optional uint32 count = 1; +} + // Public sandbox template mapped onto compute-driver template inputs. message SandboxTemplate { // Fully-qualified OCI image reference used to boot the sandbox. @@ -345,8 +819,8 @@ message SandboxTemplate { map environment = 6; // Platform-specific compute resource requirements and limits. google.protobuf.Struct resources = 7; - // Optional platform-specific volume claim templates. - google.protobuf.Struct volume_claim_templates = 9; + reserved 9; + reserved "volume_claim_templates"; // Enable Kubernetes user namespace isolation (hostUsers: false). // When true, container UID 0 maps to a non-root host UID and capabilities // become namespaced. Requires Kubernetes 1.33+ with user namespace support @@ -430,12 +904,18 @@ message CreateSandboxRequest { string name = 2; // Optional labels for the sandbox (key-value metadata). map labels = 3; + // Optional annotations for the sandbox (non-selector metadata). + map annotations = 4; + // Workspace for the sandbox. Empty defaults to "default". + string workspace = 5; } // Get sandbox request. message GetSandboxRequest { // Sandbox name (canonical lookup key). string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; } // List sandboxes request. @@ -444,12 +924,18 @@ message ListSandboxesRequest { uint32 offset = 2; // Optional label selector for filtering (format: "key1=value1,key2=value2"). string label_selector = 3; + // Workspace scope. Empty defaults to "default". + string workspace = 4; + // List across all workspaces. Mutually exclusive with workspace. + bool all_workspaces = 5; } // List providers attached to a sandbox request. message ListSandboxProvidersRequest { // Sandbox name (canonical lookup key). string sandbox_name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; } // Attach provider to sandbox request. @@ -463,6 +949,8 @@ message AttachSandboxProviderRequest { // If non-zero, the server validates that the sandbox's current resource_version // matches this value before applying the mutation, returning ABORTED on mismatch. uint64 expected_resource_version = 3; + // Workspace scope. Empty defaults to "default". + string workspace = 4; } // Detach provider from sandbox request. @@ -476,12 +964,16 @@ message DetachSandboxProviderRequest { // If non-zero, the server validates that the sandbox's current resource_version // matches this value before applying the mutation, returning ABORTED on mismatch. uint64 expected_resource_version = 3; + // Workspace scope. Empty defaults to "default". + string workspace = 4; } // Delete sandbox request. message DeleteSandboxRequest { // Sandbox name (canonical lookup key). string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; } // Sandbox response. @@ -538,7 +1030,7 @@ message CreateSshSessionResponse { // Session token for the gateway tunnel. URL-safe ASCII // ([A-Za-z0-9._~+/=-]) up to 4096 bytes. No shell metacharacters or // whitespace. - string token = 2; + string token = 2 [(openshell.options.v1.secret) = true]; // Gateway host for SSH proxy connection. IPv4 address, bracketed IPv6 // address, or DNS hostname (Punycode-encoded for IDN). Alphanumeric plus @@ -568,6 +1060,8 @@ message ExposeServiceRequest { uint32 target_port = 3; // Whether to print/use the browser-facing service URL. bool domain = 4; + // Workspace scope. Empty defaults to "default". + string workspace = 5; } // Request to fetch an exposed sandbox service endpoint. @@ -576,6 +1070,8 @@ message GetServiceRequest { string sandbox = 1; // Service name within the sandbox. Empty selects the unnamed endpoint. string service = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } // Request to list exposed sandbox service endpoints. @@ -586,6 +1082,10 @@ message ListServicesRequest { uint32 limit = 2; // Page offset. uint32 offset = 3; + // Workspace scope. Empty defaults to "default". + string workspace = 4; + // List across all workspaces. Mutually exclusive with workspace. + bool all_workspaces = 5; } // Response containing exposed sandbox service endpoints. @@ -599,6 +1099,8 @@ message DeleteServiceRequest { string sandbox = 1; // Service name within the sandbox. Empty selects the unnamed endpoint. string service = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } // Response for deleting an exposed sandbox service endpoint. @@ -632,7 +1134,7 @@ message ServiceEndpointResponse { // Revoke SSH session request. message RevokeSshSessionRequest { // Session token to revoke. - string token = 1; + string token = 1 [(openshell.options.v1.secret) = true]; } // Revoke SSH session response. @@ -708,7 +1210,7 @@ message TcpForwardInit { } // Optional target-specific authorization token. SSH targets use this as the // short-lived SSH session token issued by CreateSshSession. - string authorization_token = 7; + string authorization_token = 7 [(openshell.options.v1.secret) = true]; } // A single frame on the CLI-to-gateway TCP forward stream. @@ -747,7 +1249,7 @@ message SshSession { string sandbox_id = 2; // Session token. - string token = 3; + string token = 3 [(openshell.options.v1.secret) = true]; // Expiry timestamp in milliseconds since epoch. 0 means no expiry // (backward-compatible default for sessions created before this field existed). @@ -828,17 +1330,25 @@ message SandboxStreamWarning { // Create provider request. message CreateProviderRequest { openshell.datamodel.v1.Provider provider = 1; + // Workspace for the provider. Empty defaults to "default". + string workspace = 2; } // Get provider request. message GetProviderRequest { string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; } // List providers request. message ListProvidersRequest { uint32 limit = 1; uint32 offset = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; + // List across all workspaces. Mutually exclusive with workspace. + bool all_workspaces = 4; } // Update provider request. @@ -847,11 +1357,15 @@ message UpdateProviderRequest { // Optional per-credential expiry timestamps to merge into the provider. // A zero value removes the expiry for that credential. map credential_expires_at_ms = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } // Delete provider request. message DeleteProviderRequest { string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; } // Provider response. @@ -868,11 +1382,18 @@ message ListProvidersResponse { message ListProviderProfilesRequest { uint32 limit = 1; uint32 offset = 2; + // Workspace scope. When set, returns workspace-scoped + built-in profiles. + // When empty, returns platform-scoped + built-in only. + string workspace = 3; } // Fetch provider type profile request. message GetProviderProfileRequest { string id = 1; + // Workspace scope for two-tier profile resolution. When set, checks + // workspace-scoped profiles first, then platform-scoped, then built-in. + // When empty, checks platform-scoped then built-in only. + string workspace = 2; } // Provider profile payload with optional source metadata for diagnostics. @@ -957,6 +1478,7 @@ enum ProviderCredentialRefreshStrategy { PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_REFRESH_TOKEN = 3; PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_CLIENT_CREDENTIALS = 4; PROVIDER_CREDENTIAL_REFRESH_STRATEGY_GOOGLE_SERVICE_ACCOUNT_JWT = 5; + PROVIDER_CREDENTIAL_REFRESH_STRATEGY_AWS_STS_ASSUME_ROLE = 6; } message ProviderCredentialRefreshMaterial { @@ -966,6 +1488,15 @@ message ProviderCredentialRefreshMaterial { bool secret = 4; } +// Declares that a single refresh operation mints more than one credential. +// The refresh is attached to a primary credential; each additional output +// maps a strategy-defined semantic output id to a sibling credential whose +// env_vars receive the minted value. +message ProviderCredentialRefreshOutput { + string output = 1; // strategy-defined semantic output id (e.g. "session_token") + string credential = 2; // sibling credential name whose env_vars receive this output +} + message ProviderCredentialRefresh { ProviderCredentialRefreshStrategy strategy = 1; string token_url = 2; @@ -973,6 +1504,7 @@ message ProviderCredentialRefresh { int64 refresh_before_seconds = 4; int64 max_lifetime_seconds = 5; repeated ProviderCredentialRefreshMaterial material = 6; + repeated ProviderCredentialRefreshOutput additional_outputs = 7; } message ProviderCredentialRefreshStatus { @@ -999,7 +1531,7 @@ message StoredProviderCredentialRefreshState { string provider_name = 3; string credential_key = 4; ProviderCredentialRefreshStrategy strategy = 5; - map material = 6; + map material = 6 [(openshell.options.v1.secret) = true]; repeated string secret_material_keys = 7; int64 expires_at_ms = 8; int64 next_refresh_at_ms = 9; @@ -1010,11 +1542,18 @@ message StoredProviderCredentialRefreshState { repeated string scopes = 14; int64 refresh_before_seconds = 15; int64 max_lifetime_seconds = 16; + // Resolved mapping of strategy-defined output id -> concrete env key, pinned + // at configure time from the profile's additional_outputs. Read by minting, + // collision reservation, and env-key surfacing so later profile edits cannot + // silently redirect writes. + map additional_output_keys = 17; } message GetProviderRefreshStatusRequest { string provider = 1; string credential_key = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } message GetProviderRefreshStatusResponse { @@ -1025,9 +1564,11 @@ message ConfigureProviderRefreshRequest { string provider = 1; string credential_key = 2; ProviderCredentialRefreshStrategy strategy = 3; - map material = 4; + map material = 4 [(openshell.options.v1.secret) = true]; repeated string secret_material_keys = 5; optional int64 expires_at_ms = 6; + // Workspace scope. Empty defaults to "default". + string workspace = 7; } message ConfigureProviderRefreshResponse { @@ -1037,6 +1578,8 @@ message ConfigureProviderRefreshResponse { message RotateProviderCredentialRequest { string provider = 1; string credential_key = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } message RotateProviderCredentialResponse { @@ -1046,6 +1589,8 @@ message RotateProviderCredentialResponse { message DeleteProviderRefreshRequest { string provider = 1; string credential_key = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } message DeleteProviderRefreshResponse { @@ -1075,6 +1620,18 @@ message ProviderProfile { repeated openshell.sandbox.v1.NetworkBinary binaries = 7; bool inference_capable = 8; ProviderProfileDiscovery discovery = 9; + // Storage resource version for custom profiles. Built-in profiles and new + // profile files use 0. Gateway responses set this for stored custom profiles. + // Update calls use this for optimistic concurrency. + uint64 resource_version = 10; + // Optional non-secret annotations attached by profile sources or importers. + map annotations = 11; + // Server-set provenance: "builtin", "user", or "interceptor/{name}". + // Ignored on import/update payloads. + string source = 12; + // Server-set visibility: "platform", "workspace", or empty for + // non-scoped sources. Ignored on import/update payloads. + string scope = 13; } // Stored custom provider profile object. @@ -1096,6 +1653,9 @@ message ListProviderProfilesResponse { // Import custom provider profiles request. message ImportProviderProfilesRequest { repeated ProviderProfileImportItem profiles = 1; + // Workspace scope. When set, profiles are workspace-scoped (Workspace Admin). + // When empty, profiles are platform-scoped (Platform Admin). + string workspace = 2; } // Import custom provider profiles response. @@ -1105,9 +1665,34 @@ message ImportProviderProfilesResponse { bool imported = 3; } +// Update one custom provider profile request. +message UpdateProviderProfilesRequest { + ProviderProfileImportItem profile = 1; + // Expected storage resource version for optimistic concurrency control. + // If 0, the server uses the resource_version embedded in profile.profile. + // Updates without a non-zero version are rejected to prevent stale files from + // silently overwriting newer profile definitions. + uint64 expected_resource_version = 2; + // Existing custom provider profile ID to update. The payload ID must match. + string id = 3; + // Workspace scope. When set, targets workspace-scoped profile. When empty, + // targets platform-scoped profile. + string workspace = 4; +} + +// Update one custom provider profile response. +message UpdateProviderProfilesResponse { + repeated ProviderProfileDiagnostic diagnostics = 1; + ProviderProfile profile = 2; + bool updated = 3; +} + // Lint provider profiles request. message LintProviderProfilesRequest { repeated ProviderProfileImportItem profiles = 1; + // Workspace scope. Used to check for conflicts against existing profiles + // in the target workspace. + string workspace = 2; } // Lint provider profiles response. @@ -1124,6 +1709,9 @@ message DeleteProviderResponse { // Delete custom provider profile request. message DeleteProviderProfileRequest { string id = 1; + // Workspace scope. When set, targets workspace-scoped profile. When empty, + // targets platform-scoped profile. + string workspace = 2; } // Delete custom provider profile response. @@ -1140,7 +1728,7 @@ message GetSandboxProviderEnvironmentRequest { // Get sandbox provider environment response. message GetSandboxProviderEnvironmentResponse { // Provider credential environment variables. - map environment = 1; + map environment = 1 [(openshell.options.v1.secret) = true]; // Fingerprint for the provider credential inputs that produced environment. uint64 provider_env_revision = 2; // Expiration timestamps for returned environment variables. @@ -1186,6 +1774,15 @@ message UpdateConfigRequest { // matches this value before applying the mutation, returning ABORTED on mismatch. // Ignored for global-scoped updates. uint64 expected_resource_version = 8; + // Caller-provided annotations associated with a sandbox-scoped update. Values + // must not contain secrets; the gateway treats them as opaque metadata and does + // not interpret or verify their semantics. For policy updates, the gateway + // stores the annotations immutably with the revision and merges them into + // sandbox metadata as a convenience projection. For setting-only updates, it + // only merges them into sandbox metadata. + map annotations = 9; + // Workspace scope. Empty defaults to "default". Ignored for global-scoped updates. + string workspace = 10; } message PolicyMergeOperation { @@ -1241,6 +1838,8 @@ message UpdateConfigResponse { uint64 settings_revision = 3; // True when a setting delete operation removed an existing key. bool deleted = 4; + // Sandbox metadata annotations after the update. Empty for global updates. + map annotations = 5; } // Get sandbox policy status request. @@ -1251,6 +1850,8 @@ message GetSandboxPolicyStatusRequest { uint32 version = 2; // Query global policy revisions instead of a sandbox-scoped one. bool global = 3; + // Workspace scope. Empty defaults to "default". Ignored when global is true. + string workspace = 4; } // Get sandbox policy status response. @@ -1269,6 +1870,8 @@ message ListSandboxPoliciesRequest { uint32 offset = 3; // List global policy revisions instead of sandbox-scoped ones. bool global = 4; + // Workspace scope. Empty defaults to "default". Ignored when global is true. + string workspace = 5; } // List sandbox policies response. @@ -1307,6 +1910,8 @@ message SandboxPolicyRevision { int64 loaded_at_ms = 6; // The full policy (only populated when explicitly requested). openshell.sandbox.v1.SandboxPolicy policy = 7; + // Immutable provenance supplied with this policy revision. + map provenance = 8; } // Policy load status. @@ -1338,6 +1943,8 @@ message GetSandboxLogsRequest { repeated string sources = 4; // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. string min_level = 5; + // Workspace scope. Empty defaults to "default". + string workspace = 6; } // Batch of log lines pushed from sandbox to server. @@ -1637,6 +2244,8 @@ message SubmitPolicyAnalysisRequest { string name = 4; // Anonymous network activity counters. repeated NetworkActivitySummary network_activity_summaries = 5; + // Workspace scope. Empty defaults to "default". + string workspace = 6; } message SubmitPolicyAnalysisResponse { @@ -1658,6 +2267,8 @@ message GetDraftPolicyRequest { string name = 1; // Optional status filter: "pending", "approved", "rejected", or "" for all. string status_filter = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } message GetDraftPolicyResponse { @@ -1677,6 +2288,8 @@ message ApproveDraftChunkRequest { string name = 1; // Chunk ID to approve. string chunk_id = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } message ApproveDraftChunkResponse { @@ -1694,6 +2307,8 @@ message RejectDraftChunkRequest { string chunk_id = 2; // Optional reason for rejection (fed to LLM context in future analysis). string reason = 3; + // Workspace scope. Empty defaults to "default". + string workspace = 4; } message RejectDraftChunkResponse {} @@ -1704,6 +2319,8 @@ message ApproveAllDraftChunksRequest { string name = 1; // Include chunks with security_notes (default false: skips them). bool include_security_flagged = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } message ApproveAllDraftChunksResponse { @@ -1725,6 +2342,8 @@ message EditDraftChunkRequest { string chunk_id = 2; // The modified rule (replaces existing proposed_rule). openshell.sandbox.v1.NetworkPolicyRule proposed_rule = 3; + // Workspace scope. Empty defaults to "default". + string workspace = 4; } message EditDraftChunkResponse {} @@ -1735,6 +2354,8 @@ message UndoDraftChunkRequest { string name = 1; // Chunk ID to undo. string chunk_id = 2; + // Workspace scope. Empty defaults to "default". + string workspace = 3; } message UndoDraftChunkResponse { @@ -1748,6 +2369,8 @@ message UndoDraftChunkResponse { message ClearDraftChunksRequest { // Sandbox name. string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; } message ClearDraftChunksResponse { @@ -1759,6 +2382,8 @@ message ClearDraftChunksResponse { message GetDraftHistoryRequest { // Sandbox name. string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; } message DraftHistoryEntry { @@ -1788,6 +2413,8 @@ message PolicyRevisionPayload { string load_error = 3; // When the policy version was reported as loaded (ms since epoch). 0 if unset. int64 loaded_at_ms = 4; + // Immutable provenance supplied when this revision was created. + map provenance = 5; } // Stored payload for a draft policy chunk row in the generic objects table. @@ -1831,6 +2458,7 @@ message StoredPolicyRevision { optional string load_error = 7; int64 created_at_ms = 8; optional int64 loaded_at_ms = 9; + map provenance = 10; } // Internal stored draft chunk row materialized from the generic objects table. @@ -1857,3 +2485,116 @@ message StoredDraftChunk { // Operator-supplied free-form rejection text. See PolicyChunk. string rejection_reason = 19; } + +// --------------------------------------------------------------------------- +// Workspace messages +// --------------------------------------------------------------------------- + +// Create workspace request. +message CreateWorkspaceRequest { + // Workspace name. Must be a valid DNS-1123 label. + string name = 1; + // Optional labels for the workspace (key-value metadata). + map labels = 2; +} + +// Create workspace response. +message CreateWorkspaceResponse { + openshell.datamodel.v1.Workspace workspace = 1; +} + +// Get workspace request. +message GetWorkspaceRequest { + // Workspace name (canonical lookup key). + string name = 1; +} + +// Get workspace response. +message GetWorkspaceResponse { + openshell.datamodel.v1.Workspace workspace = 1; +} + +// List workspaces request. +message ListWorkspacesRequest { + uint32 limit = 1; + uint32 offset = 2; + // Optional label selector for filtering (format: "key1=value1,key2=value2"). + string label_selector = 3; +} + +// List workspaces response. +message ListWorkspacesResponse { + repeated openshell.datamodel.v1.Workspace workspaces = 1; +} + +// Delete workspace request. +message DeleteWorkspaceRequest { + // Workspace name (canonical lookup key). + string name = 1; +} + +// Delete workspace response. +message DeleteWorkspaceResponse { + bool deleted = 1; +} + +// --------------------------------------------------------------------------- +// Workspace membership messages +// --------------------------------------------------------------------------- + +// Workspace-scoped role for members. +enum WorkspaceRole { + WORKSPACE_ROLE_UNSPECIFIED = 0; + WORKSPACE_ROLE_USER = 1; + WORKSPACE_ROLE_ADMIN = 2; +} + +// Workspace membership record. +message WorkspaceMember { + openshell.datamodel.v1.ObjectMeta metadata = 1; + // OIDC subject claim identifying the principal. + string principal_subject = 2; + // Role assigned to the principal within the workspace. + WorkspaceRole role = 3; +} + +// Add workspace member request. +message AddWorkspaceMemberRequest { + // Workspace name. + string workspace = 1; + // OIDC subject claim identifying the principal. + string principal_subject = 2; + // Role to assign. + WorkspaceRole role = 3; +} + +// Add workspace member response. +message AddWorkspaceMemberResponse { + WorkspaceMember member = 1; +} + +// Remove workspace member request. +message RemoveWorkspaceMemberRequest { + // Workspace name. + string workspace = 1; + // OIDC subject claim identifying the principal to remove. + string principal_subject = 2; +} + +// Remove workspace member response. +message RemoveWorkspaceMemberResponse { + bool removed = 1; +} + +// List workspace members request. +message ListWorkspaceMembersRequest { + // Workspace name. + string workspace = 1; + uint32 limit = 2; + uint32 offset = 3; +} + +// List workspace members response. +message ListWorkspaceMembersResponse { + repeated WorkspaceMember members = 1; +} diff --git a/crates/right-openshell/proto/openshell/sandbox.proto b/crates/right-openshell/proto/openshell/sandbox.proto index ef0b0540..9ccefade 100644 --- a/crates/right-openshell/proto/openshell/sandbox.proto +++ b/crates/right-openshell/proto/openshell/sandbox.proto @@ -5,6 +5,8 @@ syntax = "proto3"; package openshell.sandbox.v1; +import "google/protobuf/struct.proto"; + // Sandbox-supervisor configuration and policy messages. // // Conventions: @@ -25,6 +27,10 @@ message SandboxPolicy { ProcessPolicy process = 4; // Network access policies keyed by name (e.g. "claude_code", "gitlab"). map network_policies = 5; + // Reusable supervisor middleware configs for network egress, keyed by their + // policy-local names. At most 10 configs are accepted, and at most 10 stages + // can be selected per request. + map network_middlewares = 6; } // Filesystem access policy. @@ -61,6 +67,32 @@ message NetworkPolicyRule { repeated NetworkBinary binaries = 3; } +// A reusable middleware config selected for admitted egress by host. +message NetworkMiddlewareConfig { + // Human-readable name for this middleware config. + string name = 1; + // Built-in middleware name or operator-owned registration name. + string middleware = 2; + // Service-specific configuration. + google.protobuf.Struct config = 3; + // Failure behavior: "fail_closed" (default) or "fail_open". + string on_error = 4; + // Host selector controlling which admitted destinations use this config. + MiddlewareEndpointSelector endpoints = 5; + // Execution order. Values must be unique within a policy; lower values run first. + int32 order = 6; +} + +// Host selector controlling which admitted destinations use a middleware config. +message MiddlewareEndpointSelector { + // Exact host or DNS glob patterns included in the selection. Include and + // exclude accept at most 32 combined patterns. + repeated string include = 1; + // Exact host or DNS glob patterns removed from the selection. + // Exclusions take precedence over inclusions. + repeated string exclude = 2; +} + // A network endpoint (host + port) with optional L7 inspection config. message NetworkEndpoint { // Hostname or host glob pattern. Exact match is case-insensitive. @@ -128,6 +160,53 @@ message NetworkEndpoint { // Advisor-proposed endpoints must not satisfy exact-host SSRF trust unless // they are converted through an explicit user-authored policy path. bool advisor_proposed = 18; + // Proxy-side credential signing mode: "sigv4" for AWS SigV4 re-signing. + // When set, the proxy strips the client's Authorization header and computes + // a fresh SigV4 signature using real credentials from the provider. + string credential_signing = 19; + // AWS signing service name override. Required when credential_signing is + // "sigv4" — e.g. "bedrock" for bedrock-runtime endpoints. + string signing_service = 20; + // AWS region override for SigV4 signing. When set, takes precedence over + // hostname-based region extraction. Required for non-standard endpoints. + string signing_region = 21; + // Maximum JSON-RPC-over-HTTP request body bytes to buffer for inspection. + // Defaults to 65536 when unset. + uint32 json_rpc_max_body_bytes = 22; + // MCP-only policy and inspection options. Only used when protocol is "mcp". + McpOptions mcp = 23; +} + +// MCP options are grouped so MCP-specific policy can grow without adding more +// top-level NetworkEndpoint fields. Current enforcement targets the active +// 2025-11-25 Streamable HTTP/tools behavior, while preserving space for +// version-profile policy if OpenShell adopts 2026-07-28 draft behavior later. +// +// Planned policy extensions should use OpenShell-owned static definitions for +// MCP method/version profiles rather than treating dependency enums as the +// policy contract. Candidate profile checks include request metadata/header +// validation, response/SSE introspection, trusted annotation handling, +// resultType/cache metadata validation, x-mcp-header tool-definition checks, +// and subscriptions/listen handling. +// +// Sources: +// - https://modelcontextprotocol.io/specification/2025-11-25/server/tools +// - https://modelcontextprotocol.io/specification/draft/changelog +// - https://modelcontextprotocol.io/specification/draft/basic/transports/streamable-http +// - https://modelcontextprotocol.io/specification/draft/server/tools +message McpOptions { + // Hardening boundary for tools/call params.name. When unset or true, the + // supervisor enforces the MCP recommended tool-name syntax + // ^[A-Za-z0-9_.-]{1,128}$ before policy evaluation. Set false only for + // compatibility with servers that intentionally use non-recommended names. + // + // Source: + // - https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names + optional bool strict_tool_names = 1; + // Method-layer default for MCP endpoints. When true, OpenShell allows parsed + // MCP-family methods at the method layer unless a tool-name policy narrows + // tools/call. When unset or false, explicit method rules are required. + optional bool allow_all_known_mcp_methods = 2; } // Trusted GraphQL operation classification. @@ -144,7 +223,8 @@ message GraphqlOperation { // Mirrors L7Allow — same fields, same matching semantics, inverted effect. // Deny rules are evaluated after allow rules and take precedence. message L7DenyRule { - // HTTP method (REST): GET, POST, etc. or "*" for any. + // Protocol method: HTTP method (REST/WebSocket), JSON-RPC method name, or + // "*" for any when supported by the protocol. string method = 1; // URL path glob pattern (REST): "/repos/*/pulls/*/reviews", "**" for any. string path = 2; @@ -160,6 +240,10 @@ message L7DenyRule { // GraphQL root field globs. Deny rules match when any selected root field // matches any configured glob. repeated string fields = 7; + reserved 8; + // MCP params matcher map. Currently only params.name is supported for + // tools/call filtering. Generic protocol "json-rpc" rejects params matchers. + map params = 9; } // An L7 policy rule (allow-only). @@ -169,7 +253,8 @@ message L7Rule { // Allowed action definition for L7 rules. message L7Allow { - // HTTP method (REST): GET, POST, etc. or "*" for any. + // Protocol method: HTTP method (REST/WebSocket), JSON-RPC method name, or + // "*" for any when supported by the protocol. string method = 1; // URL path glob pattern (REST): "/repos/**", "**" for any. string path = 2; @@ -186,6 +271,10 @@ message L7Allow { // GraphQL root field globs. Allow rules match only when every selected root // field matches one of the configured globs. Omit to match all fields. repeated string fields = 7; + reserved 8; + // MCP params matcher map. Currently only params.name is supported for + // tools/call filtering. Generic protocol "json-rpc" rejects params matchers. + map params = 9; } // Query value matcher for one query parameter key. @@ -272,4 +361,29 @@ message GetSandboxConfigResponse { // Fingerprint for provider credential inputs attached to this sandbox. // Changes when attached provider names or attached provider records change. uint64 provider_env_revision = 8; + // Operator-registered supervisor middleware services required by the + // effective policy. Built-in middleware is not included. + repeated SupervisorMiddlewareService supervisor_middleware_services = 9; + // Workspace the sandbox belongs to. Allows the supervisor to learn its + // workspace context for subsequent workspace-scoped RPCs. + string workspace = 10; + // Gateway-configured posture for rejected policy generations. Valid values + // are "fail_closed" and "retain_last_valid". Unknown or empty values must + // be treated as fail_closed by the supervisor. + string policy_validation_failure_mode = 11; +} + +// Connection details for one operator-registered supervisor middleware service. +// V1 supports plaintext and server-authenticated TLS gRPC. +message SupervisorMiddlewareService { + // Operator-owned registration name used by policy attachments and diagnostics. + string name = 1; + // gRPC endpoint reachable from the sandbox supervisor. + string grpc_endpoint = 2; + // Operator-owned body limit applied to every binding exposed by the service. + uint64 max_body_bytes = 3; + // Default RPC timeout for this service. Empty uses the platform default of + // 500ms. Values use an integer with an `ms` or `s` suffix and must be + // between 10ms and 30s. + string timeout = 4; }