diff --git a/fern/.docs-snapshots.yml b/fern/.docs-snapshots.yml index 42505e534..da2d00d29 100644 --- a/fern/.docs-snapshots.yml +++ b/fern/.docs-snapshots.yml @@ -1,5 +1,5 @@ snapshots: dev: - source-ref: 0a0a563dd347e68cd3b254c60879bfef51ab542f - source-sha: 0a0a563dd347e68cd3b254c60879bfef51ab542f - version: 0.0.117.dev172 + source-ref: 04146692d9f5805a427c65aaf26252b5b22afadf + source-sha: 04146692d9f5805a427c65aaf26252b5b22afadf + version: 0.0.117.dev191 diff --git a/fern/pages-dev/about/installation.mdx b/fern/pages-dev/about/installation.mdx index a40d908fa..33b4dfc95 100644 --- a/fern/pages-dev/about/installation.mdx +++ b/fern/pages-dev/about/installation.mdx @@ -16,7 +16,7 @@ Install OpenShell with a single command: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh ``` -The script detects your operating system and installs the OpenShell CLI and gateway with your native package manager. It then starts the local gateway server so you can begin creating sandboxes. +The script detects your operating system and installs the OpenShell CLI, standalone policy prover, and gateway with your native package manager. It then starts the local gateway server so you can begin creating sandboxes. You can also download release artifacts directly from the [OpenShell GitHub Releases](https://github.com/NVIDIA/OpenShell/releases) page. @@ -42,7 +42,7 @@ For detailed driver behavior, refer to [Sandbox Compute Drivers](/reference/sand ## macOS -On macOS, the install script uses Homebrew. The Homebrew package installs the `openshell` CLI, the gateway binary, and a Homebrew-managed gateway service. +On macOS, the install script uses Homebrew. The Homebrew package installs the `openshell` CLI, `openshell-prover`, the gateway binary, and a Homebrew-managed gateway service. The Homebrew service uses the gateway's built-in `127.0.0.1:17670` listener and generates a local mTLS bundle on install. The installer registers `https://localhost:17670` with the CLI so TLS uses a DNS name covered by the generated certificate. The formula creates a Homebrew prefix config, such as `/opt/homebrew/var/openshell/gateway.toml`, without overriding `bind_address`. Docker Desktop and Podman Machine reuse the primary listener for sandbox callbacks when they can reach it. The gateway reads `~/.config/openshell/gateway.toml` instead when that file exists. Homebrew upgrades migrate exact package-generated schema-v1 prefix configs, including the affected IPv6 variant. They preserve edited prefix configs and all user configs. Follow the [schema version 2 migration steps](/reference/gateway-config#migrate-to-schema-version-2) for an edited v1 file. @@ -57,9 +57,9 @@ brew services restart openshell ## Linux -On Fedora and RHEL, the install script uses RPM packages. The RPM installs the `openshell` CLI, the `openshell-gateway` daemon, and a systemd user service. +On Fedora and RHEL, the install script uses RPM packages. The RPMs install the `openshell` CLI, `openshell-prover`, the `openshell-gateway` daemon, and a systemd user service. -On Debian and Ubuntu, the install script uses a Debian package. The Debian package installs the `openshell` CLI, the `openshell-gateway` daemon, VM sandbox support, and a systemd user service. +On Debian and Ubuntu, the install script uses a Debian package. The Debian package installs the `openshell` CLI, `openshell-prover`, the `openshell-gateway` daemon, VM sandbox support, and a systemd user service. Linux packages require glibc 2.28 or newer. The installer checks libc before downloading packages and exits with an error on older glibc versions, Alpine, musl-based distributions, or unknown libc environments. diff --git a/fern/pages-dev/about/release-notes.mdx b/fern/pages-dev/about/release-notes.mdx index c5f518fd7..0b774e541 100644 --- a/fern/pages-dev/about/release-notes.mdx +++ b/fern/pages-dev/about/release-notes.mdx @@ -12,6 +12,30 @@ NVIDIA OpenShell follows a frequent release cadence. Use the following GitHub re ## 0.1.0 migration notes +### Provider profiles are import-only + +OpenShell 0.1.0 stops compiling provider profiles into the gateway binary. A gateway's catalog contains exactly the profiles an operator imported, the default `provider_profile_sources` is `[{ type = "user" }]`, and the `builtin` source type is removed: a `gateway.toml` that still names it is rejected at startup with the import command. A gateway with nothing imported reaches ready and serves an empty catalog. + +Export the profiles your deployment relies on **before** upgrading: + +```shell +openshell provider profile export -o yaml --global > .yaml +``` + +After upgrading, import them at the scope your providers use. You can also take them from the `providers/` directory of the matching release tag: + +```shell +openshell provider profile import --from ./providers --global +``` + +Omit `--global` for workspace-scoped providers. The canonical IDs are no longer reserved, so `github`, `openai`, `pypi` and the rest import at their own IDs, and the imported profile is the only definition for that ID. + +A provider whose profile is absent after the upgrade stays listable and exportable, but creating a sandbox with it, or attaching it to one, fails with a message naming the provider, the missing profile ID, and the import command. Legacy provider type aliases are gone with the compiled catalog: use a profile's own ID, so `--type claude-code` rather than `--type claude`, and `--type github` rather than `--type gh`. + +Command-to-provider inference is removed. `openshell sandbox create -- ` no longer derives a provider from the trailing command, and the compiled alias table that drove it is gone. A profile's `binaries` list authorizes a binary to reach that profile's endpoints; it is not a statement that running the binary asks for the provider, so treating it as attachment intent could attach credentials the operator did not select. Name providers explicitly with `--provider `, which still creates a missing provider from local discovery when the name matches an imported profile ID. + +The files under `providers/` are reviewable examples, not defaults. Read each file's header before importing it: several bind network access to binary paths that exist only in the OpenShell Community image, and imported unchanged into another image the profile matches nothing. + ### Network policy binaries OpenShell 0.1.0 removes the deprecated `NetworkBinary.harness` protobuf field and reserves its field number and name. Generated protobuf decoders ignore the unknown value. When the gateway loads policy history written by an older version, it migrates binaries marked by the former field into endpoint-provenance-marked rules so the upgrade cannot widen private-address access. diff --git a/fern/pages-dev/extensibility/gateway-interceptors.mdx b/fern/pages-dev/extensibility/gateway-interceptors.mdx index bf9656a5e..4d931da5c 100644 --- a/fern/pages-dev/extensibility/gateway-interceptors.mdx +++ b/fern/pages-dev/extensibility/gateway-interceptors.mdx @@ -152,7 +152,7 @@ provider_profile_sources = [ ] ``` -Include `{ type = "builtin" }` or `{ type = "user" }` entries to compose interceptor profiles with the built-in or user-managed sources. Duplicate normalized profile IDs fail instead of overriding one source with another. +Include a `{ type = "user" }` entry to compose interceptor profiles with imported profiles. Duplicate normalized profile IDs fail instead of overriding one source with another, so an interceptor-vended profile and an imported profile cannot share an ID. The gateway validates snapshot structure and provider profile semantics. It treats the configured interceptor as a trusted source and does not verify interceptor-defined signature, hash, or key annotations. diff --git a/fern/pages-dev/extensibility/supervisor-middleware.mdx b/fern/pages-dev/extensibility/supervisor-middleware.mdx index 9f3c723b1..13935436c 100644 --- a/fern/pages-dev/extensibility/supervisor-middleware.mdx +++ b/fern/pages-dev/extensibility/supervisor-middleware.mdx @@ -22,6 +22,8 @@ For each inspected HTTP request, the supervisor: 5. Re-checks body-aware protocol policy (GraphQL, JSON-RPC, MCP) after each stage that replaces the body. Every middleware receives a payload the policy admits, and a transformation cannot smuggle a denied or unparseable operation to a later stage or the upstream. 6. Applies allowed transformations, injects provider credentials, and forwards the request. +Response middleware advertising `HTTP_RESPONSE/PRE_RETURN` inspects the final upstream response before OpenShell returns it to the workload. Its preflight includes upstream `Content-Length`, `Content-Encoding`, and `Content-Range` fields as read-only metadata, except when `Connection` nominates the field as hop-by-hop. Middleware cannot write or remove these fields. OpenShell computes downstream framing separately and emits the final `Content-Length` or `Transfer-Encoding` exactly once after middleware processing. + For an RFC 6455 upgrade over `ws://` or `wss://`, the supervisor first finds every host-matched attachment, then selects only implementations that advertise `WEBSOCKET_MESSAGE/PRE_CREDENTIALS`. It opens one ordered, phase-specific `EvaluateWebSocketSession` stream per selected stage. OpenShell sends `WebSocketSessionEvent` values, while the service returns `WebSocketSessionEventResult` values only for preflight and message events; session start and end are notifications. Future upstream-to-client inspection uses the same RPC with `PRE_RETURN`; an implementation that advertises both phases receives two independent streams for the WebSocket session. An attachment without the selected binding can still inspect the HTTP upgrade request when it advertises the HTTP binding, but it is not a failed WebSocket stage. OpenShell allows post-upgrade traffic and emits an informational `binding_not_selected` coverage event for that attachment. 1. A preflight before the upgrade is sent upstream. The stage chooses `INSPECT`, voluntary `SKIP`, or authoritative `DENY` and may return a bounded diagnostic reason, stable reason code, findings, and metadata. OpenShell runs selected preflights concurrently; any `DENY` rejects the upgrade regardless of `on_error`. @@ -144,6 +146,8 @@ See [Policy Schema](/reference/policy-schema#network-middleware) for the complet | `fail_closed` | Denies the HTTP request or closes the WebSocket when the stage fails. This is the default. | | `fail_open` | Skips the failed HTTP stage. For a broken WebSocket stage stream, disables that stage for the rest of the connection and continues the remaining chain. | +A valid upstream response can exceed the middleware envelope limits or contain header bytes that the middleware protocol cannot represent. OpenShell relays the original response when every selected response stage uses `fail_open`; any selected `fail_closed` stage causes the canonical 502 delivery failure. Malformed or unsafe HTTP does not qualify for this bypass. + Use `fail_open` only when bypassing the middleware preserves the intended security policy. OpenShell emits a detection finding when a failed stage is bypassed and a separate state-change finding when a WebSocket stage is disabled for the session. Capability coverage is separate from failure handling. A host-matched HTTP-only attachment does not join the WebSocket chain, regardless of `on_error`. Binary messages are outside the V1 text-message binding and pass through even when a selected stage is `fail_closed`. OpenShell records both states as informational coverage events so operators do not mistake pass-through traffic for inspected traffic. If a deployment requires all WebSocket message classes to be inspected, V1 cannot express that requirement. @@ -220,10 +224,16 @@ Middleware activity is emitted through OpenShell's OCSF logging: See [Logging](/observability/logging) for log access and [OCSF JSON Export](/observability/ocsf-json-export) for structured export. +## Runnable example + +The [content guard example](https://github.com/NVIDIA/OpenShell/tree/main/examples/supervisor-middleware-content-guard) matches configured literal terms in UTF-8 request bodies, complete response bodies, and client WebSocket text messages. It supports redaction and denial. Responses require whole-body inspection; unavailable inspection or invalid UTF-8 invokes the configured `on_error` policy. It is not a general PII detector. + +The example includes a policy, local fixture, and smoke launcher. + ## Current Limitations - Middleware applies only through operation bindings advertised by each implementation. For protocols that have no supported middleware operation at all, such as HTTP/2 prior knowledge or non-HTTP TCP, the existing uninspectable-traffic gate denies a host match containing `fail_closed` and relays an all-`fail_open` match with a detection finding. -- The typed operation and phase pairs are `HTTP_REQUEST/PRE_CREDENTIALS` and `WEBSOCKET_MESSAGE/PRE_CREDENTIALS`. +- The typed operation and phase pairs are `HTTP_REQUEST/PRE_CREDENTIALS`, `HTTP_RESPONSE/PRE_RETURN`, and `WEBSOCKET_MESSAGE/PRE_CREDENTIALS`. - A host match does not imply every advertised operation: an HTTP-only attachment can inspect the upgrade GET, then post-upgrade traffic passes with `binding_not_selected` coverage. - The V1 WebSocket binding inspects complete client text messages only. Binary messages pass with `unsupported_message_type` coverage for active stages; control frames and upstream-to-client messages remain outside the middleware operation. - Selection uses destination host include and exclude patterns. diff --git a/fern/pages-dev/get-started/quickstart.mdx b/fern/pages-dev/get-started/quickstart.mdx index 4d7a3e2de..91565d71a 100644 --- a/fern/pages-dev/get-started/quickstart.mdx +++ b/fern/pages-dev/get-started/quickstart.mdx @@ -36,6 +36,34 @@ After installing the CLI, run `openshell --help` in your terminal to view the fu Install the public OpenShell agent skills with `npx skills add NVIDIA/OpenShell`. The `openshell-cli` skill guides your agent through common workflows and uses the installed CLI help as the command reference; no OpenShell source checkout is required. +## Import a Provider Profile + +A provider profile describes the credentials, endpoints, and client binaries a +provider needs. A gateway serves only the profiles you imported, so import one +before creating a sandbox that uses a provider. + +The OpenShell repository ships example profiles in +[`providers/`](https://github.com/NVIDIA/OpenShell/tree/main/providers). Download +the one matching your agent and import it at platform scope: + +```shell +curl -LsSfO https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/claude-code.yaml +openshell provider profile import -f claude-code.yaml --global +``` + +Use `codex.yaml` for Codex, or `openai.yaml` for OpenCode. Confirm the catalog: + +```shell +openshell provider list-profiles +``` + + +Read the comment header at the top of each file before importing it. It names +the client binaries the profile expects and the image layout those paths assume. +If your sandbox image installs the agent somewhere else, edit `binaries` first — +otherwise the profile matches nothing and the credential is never injected. + + ## Create Your First OpenShell Sandbox Create a sandbox and launch an agent inside it. @@ -47,11 +75,11 @@ Choose the tab that matches your agent: Run the following command to create a sandbox with Claude Code: ```shell -openshell sandbox create -- claude +openshell sandbox create --provider claude-code -- claude ``` -The CLI prompts you to create a provider from local credentials. -Type `yes` to continue. +`--provider` names the profile to use. If no provider by that name exists yet, +the CLI prompts you to create one from local credentials; type `yes` to continue. If `ANTHROPIC_API_KEY` is set in your environment, the CLI picks it up automatically. If not, you can configure it from inside the sandbox after it launches. @@ -65,11 +93,11 @@ If not, you can configure it from inside the sandbox after it launches. Run the following command to create a sandbox with OpenCode: ```shell -openshell sandbox create -- opencode +openshell sandbox create --provider openai -- opencode ``` -The CLI prompts you to create a provider from local credentials. -Type `yes` to continue. +`--provider` names the profile to use. If no provider by that name exists yet, +the CLI prompts you to create one from local credentials; type `yes` to continue. If `OPENAI_API_KEY` or `OPENROUTER_API_KEY` is set in your environment, the CLI picks it up automatically. If not, you can configure it from inside the sandbox after it launches. @@ -79,11 +107,11 @@ If not, you can configure it from inside the sandbox after it launches. Run the following command to create a sandbox with Codex: ```shell -openshell sandbox create -- codex +openshell sandbox create --provider codex -- codex ``` -The CLI prompts you to create a provider from local credentials. -Type `yes` to continue. +`--provider` names the profile to use. If no provider by that name exists yet, +the CLI prompts you to create one from local credentials; type `yes` to continue. If `OPENAI_API_KEY` is set in your environment, the CLI picks it up automatically. If not, you can configure it from inside the sandbox after it launches. diff --git a/fern/pages-dev/get-started/tutorials/docker-compose.mdx b/fern/pages-dev/get-started/tutorials/docker-compose.mdx index fd8c13f05..77ff79f63 100644 --- a/fern/pages-dev/get-started/tutorials/docker-compose.mdx +++ b/fern/pages-dev/get-started/tutorials/docker-compose.mdx @@ -96,7 +96,16 @@ The output should show `Status: Connected`. ## Configure an AI provider -Set your API key as an environment variable and create a provider: +Import the provider profile for the service you use, then set your API key as an +environment variable and create a provider. A gateway serves only the profiles +you imported: + +```shell +curl -LsSfO https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/anthropic.yaml +openshell provider profile import -f anthropic.yaml --global +``` + +Use `openai.yaml` for the OpenAI tab below. diff --git a/fern/pages-dev/get-started/tutorials/github-sandbox.mdx b/fern/pages-dev/get-started/tutorials/github-sandbox.mdx index 89b6c0885..e420bb697 100644 --- a/fern/pages-dev/get-started/tutorials/github-sandbox.mdx +++ b/fern/pages-dev/get-started/tutorials/github-sandbox.mdx @@ -52,11 +52,18 @@ In terminal 2, create a new sandbox with Claude Code. The [default policy](/refe Create a [credential provider](/sandboxes/manage-providers) that injects your GitHub token into the sandbox automatically. The provider reads `GITHUB_TOKEN` from your host environment and sets it as an environment variable inside the sandbox: ```shell +curl -LsSfO https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/github.yaml +openshell provider profile import -f github.yaml --global + GITHUB_TOKEN= openshell provider create --name my-github --type github --from-existing openshell sandbox create --provider my-github -- claude ``` +The import step registers the `github` profile on your gateway. OpenShell ships +no profiles inside the gateway binary, so `--type github` resolves only after the +profile is imported. Import it once per gateway. + `openshell sandbox create` keeps the sandbox running after Claude Code exits, so you can apply policy updates later without recreating the environment. Add `--no-keep` if you want the sandbox deleted automatically instead. Claude Code starts inside the sandbox. It prints an authentication link. Open it in your browser, sign in to your Anthropic account, and return to the terminal. When prompted, trust the `/sandbox` workspace to allow Claude Code to read and write files. diff --git a/fern/pages-dev/kubernetes/sandbox-runtime.mdx b/fern/pages-dev/kubernetes/sandbox-runtime.mdx index 319619ee7..aa039ad02 100644 --- a/fern/pages-dev/kubernetes/sandbox-runtime.mdx +++ b/fern/pages-dev/kubernetes/sandbox-runtime.mdx @@ -110,6 +110,13 @@ creates a new generation with new Secrets and a replacement supervisor Pod while preserving the workspace PVC. The namespace-wide workload fence remains in place across sandbox generations. +If provisioning is interrupted, recovery first confirms that the old workload +Pod is gone. It then lists generation Secrets by sandbox and component labels, +deletes each stale Secret with a UID precondition, and clears the rollback state +so the next generation can be created. The Helm chart grants the gateway +`create`, `list`, and `delete` access to Secrets for this lifecycle. Listing is +required even when no generation Secrets remain. + ## Check Cluster Requirements This architecture requires the following cluster behavior: diff --git a/fern/pages-dev/providers/aws-sigv4.mdx b/fern/pages-dev/providers/aws-sigv4.mdx index cae64015d..101d796e5 100644 --- a/fern/pages-dev/providers/aws-sigv4.mdx +++ b/fern/pages-dev/providers/aws-sigv4.mdx @@ -17,6 +17,14 @@ AWS SigV4 credential signing lets sandbox agents call AWS services (Bedrock, S3, ## Provider Setup +Import the `aws` profile first; a gateway serves only the profiles you +imported: + +```shell +curl -LsSfO https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/aws.yaml +openshell provider profile import -f aws.yaml --global +``` + Create a provider with AWS credentials: ```shell diff --git a/fern/pages-dev/providers/google-cloud.mdx b/fern/pages-dev/providers/google-cloud.mdx index 127dae1d6..9168be25e 100644 --- a/fern/pages-dev/providers/google-cloud.mdx +++ b/fern/pages-dev/providers/google-cloud.mdx @@ -16,6 +16,14 @@ never holds a real GCP credential. ## Quick Start +Import the `google-cloud` profile first; a gateway serves only the profiles you +imported: + +```shell +curl -LsSfO https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/google-cloud.yaml +openshell provider profile import -f google-cloud.yaml --global +``` + If you already have `gcloud` configured with Application Default Credentials, create a provider with automatic credential refresh in one command: diff --git a/fern/pages-dev/providers/google-vertex-ai.mdx b/fern/pages-dev/providers/google-vertex-ai.mdx index 781768b7e..0832baee9 100644 --- a/fern/pages-dev/providers/google-vertex-ai.mdx +++ b/fern/pages-dev/providers/google-vertex-ai.mdx @@ -24,6 +24,14 @@ native Vertex endpoint and request format for its selected model. ## Create a Provider +Import the `google-vertex-ai` profile first; a gateway serves only the profiles you +imported: + +```shell +curl -LsSfO https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/google-vertex-ai.yaml +openshell provider profile import -f google-vertex-ai.yaml --global +``` + ### Service Account Key Create the provider with the JSON key as gateway-only bootstrap material: diff --git a/fern/pages-dev/providers/profiles.mdx b/fern/pages-dev/providers/profiles.mdx index 4255a1f89..e1774470e 100644 --- a/fern/pages-dev/providers/profiles.mdx +++ b/fern/pages-dev/providers/profiles.mdx @@ -20,7 +20,7 @@ Provider profiles keep those pieces together: | Need | Profile-backed behavior | |---|---| -| Repeatable provider setup | Built-in and custom provider profiles define reusable provider types. | +| Repeatable provider setup | Imported provider profiles define reusable provider types. | | Provider-aware policy | Attached providers contribute `_provider_*` network policy entries to the effective sandbox policy. | | Custom provider definitions | You can export, edit, lint, import, list, and delete custom profiles. | | Runtime provider lifecycle | You can list, attach, and detach providers on existing sandboxes. | @@ -31,16 +31,16 @@ Provider profiles keep those pieces together: Provider profiles include these user-facing features: -- Built-in provider profiles loaded by the gateway by default. -- Gateway configuration can compose built-in, user-managed, and interceptor-vended profile sources. Selecting only an interceptor makes its catalog authoritative by omission. -- A user-managed profile shadows a built-in profile with the same ID in its applicable workspace or platform scope. The built-in remains the fallback outside that scope. Source-managed interceptor collisions still fail closed. +- An import-only catalog: the gateway serves the profiles you imported and nothing else. A gateway with none imported serves an empty catalog. +- Gateway configuration can compose user-managed and interceptor-vended profile sources. Selecting only an interceptor makes its catalog authoritative by omission. +- Platform-scoped and workspace-scoped profiles. A workspace profile takes precedence over a platform profile with the same ID inside that workspace, and the platform profile applies outside it. An interceptor-vended profile cannot share an ID with an imported one; that collision fails closed. - `openshell provider list-profiles` with table, YAML, and JSON output. - `openshell provider profile export`, `import`, `update`, `lint`, and `delete` for custom profiles. -- Provider instances created from built-in or imported profile IDs with `openshell provider create --type `. +- Provider instances created from imported profile IDs with `openshell provider create --type `. - Provider instances whose submitted credentials can be stored by a configured gateway credential driver. -- Profile-backed credential discovery for explicit `openshell provider create --from-existing` and `openshell provider update --from-existing` flows. The built-in `google-vertex-ai` profile also supplements discovery with Vertex config env vars such as `VERTEX_AI_PROJECT_ID` and `VERTEX_AI_REGION`. +- Profile-backed credential discovery for explicit `openshell provider create --from-existing` and `openshell provider update --from-existing` flows. The `google-vertex-ai` profile also supplements discovery with Vertex config env vars such as `VERTEX_AI_PROJECT_ID` and `VERTEX_AI_REGION`. - Just-in-time effective policy composition from sandbox policy plus attached provider profiles. -- Runtime sandbox provider lifecycle commands under `openshell sandbox provider list|attach|detach`. +- Runtime sandbox provider commands under `openshell sandbox provider list|attach|detach|status`, with an option to wait until the sandbox applies a change. - Credential refresh configuration with `openshell provider refresh status|configure|rotate|delete`. - Credential expiry metadata with `openshell provider update --credential-expires-at`; values accept Unix epoch milliseconds or ISO/RFC3339 timestamps. - Dynamic token grants that use the sandbox's SPIFFE JWT-SVID as an OAuth2 client assertion and inject short-lived tokens into supported headers for matching profile endpoints. @@ -55,11 +55,13 @@ supply that boundary by default. For an endpointless profile, a sandbox policy endpoint can name the attached provider instance explicitly. The proxy checks the resulting association before it substitutes the real value. -The built-in `openai` and `anthropic` profiles bind credentials to their public +The example `openai` and `anthropic` profiles bind credentials to their public vendor endpoints. To use a proxy or compatible API at another host, import a -custom profile that declares that host and attach a provider created from that -profile. OpenShell never broadens a built-in profile's credential boundary from -a base URL environment variable alone. +separate profile that declares that host and attach a provider created from that +profile. OpenShell never broadens a profile's credential boundary from a base URL +environment variable alone: if a provider's `*_BASE_URL` points outside the hosts +its profile declares, the profile is treated as endpointless and its credential +binds only through explicit sandbox policy. A request can use a static credential only when all of these checks pass: @@ -220,51 +222,53 @@ List available profiles: openshell provider list-profiles ``` -By default the gateway lists built-in profiles plus custom profiles imported -through the profile APIs. When a configured gateway interceptor vends an -authoritative provider profile catalog, that catalog becomes the visible source -of truth: list, export, provider creation, policy composition, and sandbox -provider environment resolution use the interceptor-vended profiles instead of -built-in or user-imported profiles. +A gateway serves exactly the profiles you imported. OpenShell ships no profiles +inside the gateway binary, so a new gateway lists an empty catalog until you +import one. When a configured gateway interceptor vends an authoritative +provider profile catalog, that catalog becomes the visible source of truth: +list, export, provider creation, policy composition, and sandbox provider +environment resolution use the interceptor-vended profiles. -Built-in provider profiles currently include: +### Import the Example Profiles -| Profile ID | Category | Credential environment variables | -|---|---|---| -| `anthropic` | `inference` | `ANTHROPIC_API_KEY` | -| `claude-code` | `agent` | `ANTHROPIC_API_KEY`, `CLAUDE_API_KEY` | -| `codex` | `agent` | `CODEX_AUTH_ACCESS_TOKEN`, `CODEX_AUTH_REFRESH_TOKEN`, `CODEX_AUTH_ACCOUNT_ID`, `CODEX_AUTH_ID_TOKEN` | -| `copilot` | `agent` | `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN` | -| `cursor` | `agent` | None | -| `deepinfra` | `inference` | `DEEPINFRA_API_KEY` | -| `github` | `source_control` | `GITHUB_TOKEN`, `GH_TOKEN` | -| `google-vertex-ai` | `inference` | `GOOGLE_SERVICE_ACCOUNT_KEY`, `GOOGLE_VERTEX_AI_SERVICE_ACCOUNT_TOKEN`, `VERTEX_AI_SERVICE_ACCOUNT_TOKEN`, `GOOGLE_VERTEX_AI_TOKEN`, `VERTEX_AI_TOKEN` | -| `nvidia` | `inference` | `NVIDIA_API_KEY` | -| `openai` | `inference` | `OPENAI_API_KEY` | -| `pypi` | `data` | None | +The OpenShell repository ships example profiles for GitHub, PyPI, and the major +inference and agent providers in +[`providers/`](https://github.com/NVIDIA/OpenShell/tree/main/providers). They are +reviewable starting points, not platform defaults. -Export a built-in profile as YAML: - -```shell -openshell provider profile export github -o yaml > github-profile.yaml -``` +Read a file's header before importing it. Each one names the client binaries it +expects, the image layout those paths assume, the credential scope, the endpoint +access it grants, and a smoke test. Several name paths that exist only in the +OpenShell Community image, such as `/sandbox/.venv` or +`/usr/lib/node_modules/@openai`. Imported unchanged into a different image the +profile matches nothing: the catalog still lists it, but the credential is never +injected and the traffic is denied. Copy the file, edit `binaries` and +`endpoints` for your image and workload, and import your copy. Lint a profile before importing it: ```shell -openshell provider profile lint -f github-profile.yaml +openshell provider profile lint -f providers/github.yaml ``` -Import one profile file: +Import one profile file at platform scope: ```shell -openshell provider profile import -f github-profile.yaml +openshell provider profile import -f providers/github.yaml --global ``` Import all non-recursive `*.yaml`, `*.yml`, and `*.json` files from a directory: ```shell -openshell provider profile import --from ./provider-profiles +openshell provider profile import --from ./providers --global +``` + +Omit `--global` to import into the current workspace instead. + +Export a profile as YAML, to edit or to keep before an upgrade: + +```shell +openshell provider profile export github -o yaml --global > github-profile.yaml ``` Import is create-only. It fails if a custom profile with the same ID already exists. @@ -278,7 +282,7 @@ openshell provider profile update github-profile -f github-profile.yaml Exported custom profiles include `resource_version`. OpenShell requires that version during update so stale files cannot silently overwrite newer profile definitions. The target ID in the command must match the profile ID in the file. Update accepts one file at a time. If an update would make dynamic token grants ambiguous for an attached sandbox, OpenShell rejects it before changing the profile. -Custom profile IDs must use lowercase kebab-case with `a-z`, `0-9`, and `-`. Built-in profile IDs and legacy provider aliases are reserved. Built-in and interceptor-managed profiles are read-only through the profile APIs. OpenShell also rejects deleting a custom profile while a sandbox-attached provider uses it. +Profile IDs must use lowercase kebab-case with `a-z`, `0-9`, and `-`. No IDs are reserved: `github`, `openai`, and every other example profile imports at its own ID, and the imported profile is the only definition for it. Interceptor-managed profiles are read-only through the profile APIs. OpenShell rejects deleting a profile while a sandbox-attached provider uses it. ### Category Enum @@ -559,9 +563,9 @@ The gateway only brokers an intermediate token for a sandbox principal, and only ## Provider Instances -A provider instance stores concrete credentials and config for a profile type. Built-in profile IDs and imported custom profile IDs are accepted by `--type`. +A provider instance stores concrete credentials and config for a profile type. `--type` accepts the ID of any imported profile, matched exactly. Creating a provider for a profile the gateway does not serve fails and names the import command. -Create a GitHub provider from the built-in `github` profile: +Create a GitHub provider from an imported `github` profile: ```shell openshell provider create \ @@ -581,11 +585,11 @@ openshell provider create \ `--from-existing` uses the provider profile's `discovery` section. If no profile exists for the requested type, the command fails instead of falling -back to the retired provider registry. The built-in `openai` profile discovers -`OPENAI_API_KEY`; the built-in `anthropic` profile discovers +back to the retired provider registry. The example `openai` profile discovers +`OPENAI_API_KEY`; the example `anthropic` profile discovers `ANTHROPIC_API_KEY`. -Create a provider from an imported custom profile: +Create a provider from another imported profile: ```shell openshell provider create \ @@ -863,7 +867,7 @@ includes credential values, opaque credential handles, or config values. OpenShell stores the base policy and provider attachments separately. When a sandbox asks for its effective policy, the gateway composes the current base policy with provider policy layers just in time. -For example, the built-in GitHub profile contains these endpoints and binaries: +For example, the GitHub example profile contains these endpoints and binaries: ```yaml wordWrap showLineNumbers={false} id: github @@ -968,29 +972,66 @@ Composition follows these rules: ## Attach and Detach Providers -Attach an existing provider to a running sandbox: +Attach an existing provider and wait until the sandbox can use it: ```shell -openshell sandbox provider attach provider-demo work-github +openshell sandbox provider attach provider-demo work-github --wait --timeout 30 ``` -Detach a provider: +Detach a provider and wait until the sandbox stops resolving its credentials: ```shell -openshell sandbox provider detach provider-demo work-github +openshell sandbox provider detach provider-demo work-github --wait --timeout 30 ``` Attach and detach are idempotent. Attach validates that the provider exists before mutating the sandbox, and provider deletion fails while the provider is attached to any sandbox. +### Inspect Provider Readiness + +Without `--wait`, attach, detach, and update confirm that the gateway saved the change. Add `--wait` when the next step depends on that change taking effect. The command succeeds after the sandbox applies the matching credentials and policy, and updates the environment used by new processes. + +Each result includes a change record called a `receipt` in the API. Its ID lets you check the same change later, including after a timeout: + +```shell +openshell sandbox provider status provider-demo work-github --output json +openshell sandbox provider status provider-demo work-github --receipt RECEIPT_ID --wait --timeout 30 +``` + +Replace `RECEIPT_ID` with the `receipt_id` returned by attach, detach, or update. The ID always refers to the original change. If a later change replaces it, the status is `superseded` and the wait ends without reporting success. + +If attach, detach, or update reports `CONFIG_OPERATION_STORAGE_UNCERTAIN`, the change may already be saved even though the gateway could not record its readiness receipt. Do not blindly retry the mutation: inspect the provider and sandbox state and reconcile the saved change first. A receipt may be unavailable, so an error does not establish that the mutation was rolled back or that the sandbox is ready. + +Readiness states have these meanings: + +- `persisted`: the gateway saved the change; the command has not checked the sandbox yet. +- `pending`: the sandbox has not confirmed all parts of the change. +- `ready`: the sandbox applied the requested provider credentials, policy, and environment for new processes. +- `revoked`: the detached provider's credentials no longer resolve, and new processes do not receive its credential references. +- `withheld`: a credential or supervisor configuration prevents the sandbox from applying the change. +- `failed`: the sandbox could not install the credentials, policy, or process environment. +- `superseded`: a later change replaced the original request. + +The default wait is 30 seconds and the maximum is 3600 seconds. The timeout starts after the gateway saves the change and includes time spent requesting status. If time runs out, the command exits with an error and reports `wait_outcome: timed_out` with the last known state. The change remains saved and may finish applying later. Check its ID again to learn the result. + +An unavailable supervisor, an expired report, a failed installation, or a missing process-environment acknowledgment cannot produce `ready` or `revoked`. After a reconnect or restart, the current authenticated supervisor must report its installed state again. + +JSON and YAML output include change IDs, requested and installed revisions, timestamps, reason categories, and a result for each sandbox. Revision strings identify configurations; compare them for equality rather than numerical order. Status output excludes credential values, credential references, authorization headers, and raw installation errors. + +The `persisted_time`, `observed_time`, and `evaluated_time` fields use RFC 3339 timestamp strings. An absent `observed_time` means the current supervisor session has not supplied accepted evidence. API operation timestamps use protobuf `Timestamp`, and report intervals and observation lifetimes use protobuf `Duration`, following the [protobuf time representation](/reference/protobuf-time-types). + +The API's `operation` field records the common operation's historical outcome; its `operation_id` equals the receipt ID. Use the provider `state` and the wait result for current readiness. A disconnected supervisor can make the live state pending even after the operation previously applied, and a newer change can supersede the live result. Historical completion does not override those checks. + ### Runtime Limitations -Provider attach and detach update the persisted sandbox provider list. Running sandboxes poll for provider environment revisions and effective policy changes. +Running sandboxes periodically check for provider and policy changes. Use `--wait` or `sandbox provider status` to confirm when a saved change has taken effect. The policy effect applies to future effective policy reads after the sandbox observes the update. The credential environment effect applies only to new process launches after the update is observed, such as later SSH, exec, or SFTP sessions. -Already-running processes keep the placeholder environment they started with. OpenShell does not mutate a live process environment after provider attach, detach, or credential update. The proxy resolves existing placeholders against current credentials and bindings, so rotation, expiry, endpoint changes, and detach take effect without restarting the process. If a long-running process needs a newly attached provider credential placeholder, restart that process or launch a new process after the sandbox has observed the provider update. +Already-running processes keep the placeholder environment they started with. For ordinary static credentials, an existing reference retains its selected revision after a value update; readiness does not retarget that reference to the replacement value. After an update wait succeeds, launch a new process to receive the updated credential reference. Gateway-managed refresh follows its own reference lifecycle. Expiry, endpoint authorization, and acknowledged detach continue to apply when the proxy resolves retained references. -Detaching a provider removes its provider policy layer from future effective policy reads, revokes resolution for its existing placeholders, and removes its credential placeholders from future process environments. It does not remove the placeholder strings from already-running process environments. +For a static provider, the sequence is attach, wait, then launch client A; update, wait, then launch client B. B's first request uses the newly installed credential. Readiness does not establish that A has stopped using its retained revision or that an old upstream key can be retired. + +An acknowledged detachment removes its provider policy layer from the active effective policy, revokes future resolution for its existing placeholders, and removes its credential placeholders from future process environments. It does not remove strings from already-running process environments or undo requests already forwarded upstream. OpenShell rejects provider updates and refresh configuration when they would make two providers attached to the same sandbox expose the same active credential environment key. It also rejects attached provider sets with ambiguous dynamic token grants at equal host/path specificity. Use provider-specific credential names and make one dynamic grant selector more specific when one sandbox needs multiple providers with overlapping upstream concepts. diff --git a/fern/pages-dev/reference/api-errors.mdx b/fern/pages-dev/reference/api-errors.mdx index 9f77169ab..d6c2c0778 100644 --- a/fern/pages-dev/reference/api-errors.mdx +++ b/fern/pages-dev/reference/api-errors.mdx @@ -31,6 +31,9 @@ Recognized gateway reasons include the following. | `INVALID_ARGUMENT` | `INVALID_ARGUMENT` | Correct the fields listed in `BadRequest`. | | `RESOURCE_VERSION_CONFLICT` | `ABORTED` | Read the resource again and construct a new conditional write. `metadata.recovery` is `REFRESH_STATE`; `current_resource_version` is included when known. | | `PROFILE_SOURCE_UNAVAILABLE` | `UNAVAILABLE` | Retry a profile snapshot read after at least the supplied delay. | +| `REQUEST_ID_PAYLOAD_MISMATCH` | `FAILED_PRECONDITION` | Keep the original payload for that request ID. Inspect the original operation before submitting a different one. | +| `REQUEST_OUTCOME_UNCERTAIN` | `FAILED_PRECONDITION` | An attempt is admitted but has no confirmed replayable success. Observe resource state and reconcile effects. Do not switch to a new ID to bypass the admission. | +| `REQUEST_REPLAY_UNAVAILABLE` | `FAILED_PRECONDITION` | The original scope, resource, interceptor transformation, or fingerprint key is no longer replayable. Reconcile effects; the gateway does not execute the request again. Missing private-key material can also reject admission before work starts. | ## Status and retry guidance @@ -85,6 +88,108 @@ Go and TypeScript add typed detail fields to their existing error types. No SDK automatically retries a mutation as a result of decoding these details. +## Durable request admission + +Thirty user-callable unary RPCs accept an optional `request_id` in their protobuf requests. + +| Resource | RPCs | +|---|---| +| Workspace | `CreateWorkspace`, `DeleteWorkspace` | +| Workspace member | `AddWorkspaceMember`, `RemoveWorkspaceMember` | +| Sandbox template | `CreateSandboxTemplate`, `DeleteSandboxTemplate` | +| Sandbox | `CreateSandbox`, `DeleteSandbox`, `StartSandbox`, `StopSandbox`, `AttachSandboxProvider`, `DetachSandboxProvider` | +| Service | `ExposeService`, `DeleteService` | +| Provider | `CreateProvider`, `UpdateProvider`, `DeleteProvider`, `ConfigureProviderRefresh`, `DeleteProviderRefresh`, `RotateProviderCredential` | +| Provider profile | `ImportProviderProfiles`, `UpdateProviderProfiles`, `DeleteProviderProfile` | +| Policy and config | `UpdateConfig`, `ApproveDraftChunk`, `RejectDraftChunk`, `EditDraftChunk`, `UndoDraftChunk`, `ApproveAllDraftChunks`, `ClearDraftChunks` | + +Use a nonzero, hyphenated UUID of exactly 36 characters. Uppercase and lowercase +forms identify the same request. An empty ID preserves the existing behavior +without deduplication. This ID is separate from transport correlation metadata. +The gateway scopes it to the authenticated identity provider, OIDC issuer, +subject, RPC method, and requested workspace. Use the same identity, scope, ID, +and payload when checking an admitted attempt. Map ordering does not affect the +payload fingerprint; changed values or protobuf message presence do. + +For `CreateWorkspace` and `DeleteWorkspace`, the requested workspace is the +request's `name`. The same caller can reuse an ID for the same method on different +workspace names. Within one workspace name, a changed payload still returns +`REQUEST_ID_PAYLOAD_MISMATCH`. Workspace deletion replay does not require the +deleted workspace to exist and does not delete a same-name replacement. + +Pre-release gateways that used unscoped workspace admission require reconciliation +before upgrading. Their workspace create/delete receipts do not replay under the +corrected name-scoped namespace. Do not retry those IDs after upgrading without +reconciling the original operations' effects. + +The gateway durably admits one owner before executing the mutation. Concurrent +duplicates either replay its confirmed success or return +`REQUEST_OUTCOME_UNCERTAIN`. Disconnecting or cancelling the client does not stop +admitted work. An error, interrupted process, or failed success-record write +leaves the admission unresolved. No gateway replica takes over that admission, +even after restart or after 24 hours. Status codes alone do not prove that the +attempt had no effects. + +Successful results have a 24-hour replay window, measured from durable completion. +A successful replay includes `openshell-replayed: true` response metadata and +does not extend the window. After it expires, the same ID can admit a new +execution. Do not retry an old operation after that window without reconciling +its effects. Unresolved admissions never expire. + +Every replay checks current authorization, including membership and permission +to assign workspace administrators. A replacement workspace makes workspace-scoped +replay unavailable. Target readiness, conditional-write versions, and draft review +tokens are first-execution preconditions, not conditions to execute again. + +| Result family | Replay behavior | +|---|---| +| Workspace, membership, template, provider, and profile resources | Load the original UUID at its recorded version. Missing or changed resources make replay unavailable. Provider credentials remain redacted. | +| Sandbox resources | Load the original UUID with its current state, including newer status or configuration. Attach/detach flags describe the original operation. A missing original sandbox makes replay unavailable. | +| Provider readiness receipts | Return the original attach, detach, or update receipts and mutation ID, including the original update target set. Later attachment changes do not replace these receipts. Missing operation evidence makes replay unavailable without executing the mutation again. | +| Service endpoint | Require the original endpoint version and sandbox identity. Return the recorded URL. | +| Refresh status | Return current status for the original provider, refresh record, and authorization epoch. Reconfiguration or removal makes replay unavailable. Rotation is not repeated. | +| Policy and config | Return the original versions, counts, and nonsecret annotations while the original sandbox exists. Global config has no sandbox guard. | +| Deletion | Return the original outcome without requiring the deleted target or parent to exist and without deleting a replacement. `ACCEPTED` remains an acknowledgment of the original cleanup, not proof of completion. | + +Profile imports and updates also preserve their original public diagnostics and +success flags. Profile declarations, diagnostic source labels, and annotations +must not contain secrets. + +For sandbox, service, provider, profile, and policy/config methods, fingerprints +use HMAC with domain-separated material derived from the configured gateway JWT +private key, or the primary TLS private key when JWT signing is not configured. +These methods require readable, stable private-key material when you supply an +ID. Replicas sharing a database must share that material. Changing it makes +existing protected receipts unavailable until their successful replay window +expires; unresolved admissions remain protected indefinitely. Workspace, +membership, and template methods retain their original fingerprint format. + +Gateway interceptors run their current modification and validation phases on +every attempt, including replay. They cannot add, remove, or change `request_id`. +The gateway fingerprints the original client payload and separately guards the +effective transformed payload and workspace. A changed transformation makes +replay unavailable. A current denial remains authoritative. Successful replay +does not invoke post-commit observers again. Post-commit observation remains +best-effort; this is not a durable delivery queue. + +Replay rows contain fingerprints, resource references, and explicitly selected +public diagnostic or scalar outcomes, not credential-bearing response snapshots. +Completed receipts are limited to 64 KiB. A failure to capture a bounded receipt +after execution leaves the admission unresolved. Each caller can +hold at most 1,000 durable admissions. At capacity, the gateway removes expired +successes; if no space remains, it rejects new admissions with +`RESOURCE_EXHAUSTED`. Existing records remain protected. Unresolved records +require operator investigation; there is no automatic reset or takeover API. +The gateway also limits detached admission workers to 64 per process and these +request messages to 4 MiB. + +Supply IDs through generated/raw RPC clients. Curated SDK request-ID helpers and +automatic retry policies are not part of this contract yet. Exec, SSH sessions, +rootfs staging, and supervisor-authenticated mutations do not gain deduplication +from this feature. Sandbox principals cannot opt into `UpdateConfig` admission. +Check gateway compatibility +before relying on IDs; older protobuf servers can silently ignore unknown fields. + ## Deletion outcomes Delete, membership-removal, and SSH-revocation RPCs return a typed outcome. diff --git a/fern/pages-dev/reference/gateway-config.mdx b/fern/pages-dev/reference/gateway-config.mdx index 9940f2ee2..abced28b7 100644 --- a/fern/pages-dev/reference/gateway-config.mdx +++ b/fern/pages-dev/reference/gateway-config.mdx @@ -165,9 +165,8 @@ grpc_rate_limit_requests = 120 grpc_rate_limit_window_seconds = 60 # Optional exact provider-profile source composition. When omitted, the -# gateway uses builtin + user. +# gateway uses imported profiles only. provider_profile_sources = [ - { type = "builtin" }, { type = "user" }, ] @@ -357,13 +356,13 @@ max_payload_bytes = 262144 timeout = "500ms" ``` -Each service implements the supervisor middleware gRPC contract and exposes bindings through `Describe`. Policies reference the operator-owned registration `name`, attaching the complete middleware and all of its bindings. Bindings are identified by operation and phase. A manifest may expose at most one binding for each operation and phase pair. V1 supports `HttpRequest/pre_credentials` and `WebSocketMessage/pre_credentials`, so a service can inspect HTTP, WebSocket, or both. Registration names must be unique, and operator-run registrations cannot claim the reserved `openshell/` namespace. The service-reported manifest name is diagnostic metadata and does not need to match the registration name. +Each service implements the supervisor middleware gRPC contract and exposes bindings through `Describe`. Policies reference the operator-owned registration `name`, attaching the complete middleware and all of its bindings. Bindings are identified by operation and phase. A manifest may expose at most one binding for each operation and phase pair. V1 supports `HttpRequest/pre_credentials`, `HttpResponse/pre_return`, and `WebSocketMessage/pre_credentials`. Registration names must be unique, and operator-run registrations cannot claim the reserved `openshell/` namespace. The service-reported manifest name is diagnostic metadata and does not need to match the registration name. The gateway connects to every registered service and validates `Describe` before it starts. The service must therefore be running before the gateway. Policy creation and full policy updates call `ValidateConfig`; an unavailable service or invalid middleware configuration rejects the policy before persistence. -`max_payload_bytes` is the shared operator limit for inspectable logical payloads across every binding exposed by the service. It caps HTTP request and replacement bodies as well as complete WebSocket text messages and replacements. The value must be greater than zero, no larger than each binding's advertised `max_payload_bytes` capability, and no larger than the 4 MiB platform maximum. OpenShell rejects oversized values instead of silently clamping them. Binary WebSocket messages are not exposed to V1 middleware, so this field does not limit binary pass-through. Middleware gRPC servers should allow messages of at least 4 MiB plus 293 KiB so a maximum-size payload and its protobuf envelope fit on the transport. +`max_payload_bytes` is the shared operator limit for inspectable logical payloads across every binding exposed by the service. It caps HTTP request and response units, replacement bodies, and complete WebSocket text messages and replacements. Whole-response inspection uses it as the stage's total body limit. Streaming response inspection applies it to each unit. The value must be greater than zero, no larger than each binding's advertised `max_payload_bytes` capability, and no larger than the 4 MiB platform maximum. OpenShell rejects oversized values instead of silently clamping them. Binary WebSocket messages are not exposed to V1 middleware, so this field does not limit binary pass-through. Middleware gRPC servers should allow messages of at least 4 MiB plus 293 KiB so a maximum-size payload and its protobuf envelope fit on the transport. -`timeout` is the operator-configured service-wide RPC timeout. It accepts the same compact duration syntax as gateway interceptors: an integer followed by `ms` or `s`, such as `500ms` or `2s`. Values must be between `10ms` and `30s`, inclusive. Omit the field to use the 500 ms platform default. A binding may advertise a shorter `timeout` in the `Describe` manifest, but it cannot extend the operator-configured deadline; OpenShell uses the smaller value. OpenShell validates both levels before accepting the service. The operator-configured service timeout applies to `Describe` and `ValidateConfig`. The effective binding timeout applies only to `EvaluateHttpRequest`, WebSocket preflight, and each WebSocket message. An accepted WebSocket stream has no connection-wide RPC deadline. +`timeout` is the operator-configured service-wide RPC timeout. It accepts the same compact duration syntax as gateway interceptors: an integer followed by `ms` or `s`, such as `500ms` or `2s`. Values must be between `10ms` and `30s`, inclusive. Omit the field to use the 500 ms platform default. A binding may advertise a shorter `timeout` in the `Describe` manifest, but it cannot extend the operator-configured deadline; OpenShell uses the smaller value. OpenShell validates both levels before accepting the service. The operator-configured service timeout applies to `Describe` and `ValidateConfig`. The effective binding timeout applies to HTTP request evaluation, HTTP response preflight and unit exchanges, WebSocket preflight, and each WebSocket message. Accepted streaming protocols have no connection-wide RPC deadline. The service `grpc_endpoint` supports plaintext `http://` and TLS `https://`. HTTPS uses the platform trust store unless `tls_ca_cert_path` names a certificate-only PEM bundle. OpenShell rejects bundles containing private keys, loads the certificates at gateway startup, and distributes only public certificates to sandbox supervisors; normal TLS hostname verification still applies. `audience` sets the exact audience for gateway-minted service tokens and defaults to `urn:openshell:extension:middleware:`. After authenticated `Describe` succeeds, OpenShell treats a non-empty manifest `expected_audience` as a consistency assertion and refuses to start when it differs from the configured audience. A strict verifier may reject an incorrect audience before returning the manifest. @@ -398,7 +397,9 @@ Bindings under `allowlist` and `exact` require `rpc` or `service` plus `method`, Bindings that include `post_commit` must resolve to `failure_policy = "fail_open"`; the gateway rejects fail-closed post-commit configuration at startup. Post-commit evaluation is observational. If response observation or interceptor evaluation fails after a handler succeeds, the gateway logs and counts the failure but returns the committed response unchanged. -`provider_profile_sources` selects the exact ordered provider-profile source set. Omit it to use the built-in and user-managed sources. Use `{ type = "builtin" }`, `{ type = "user" }`, and `{ type = "interceptor", name = "" }` entries to compose a catalog. An interceptor entry must reference a configured service whose manifest advertises `provider_profiles = true`. Selecting only an interceptor makes that catalog authoritative; include local sources explicitly to compose them. Empty or duplicate source lists, unknown interceptor names, and duplicate normalized profile IDs fail closed. Source order controls collection and diagnostics, not override precedence. +`provider_profile_sources` selects the exact ordered provider-profile source set. Omit it to use imported profiles only. Use `{ type = "user" }` and `{ type = "interceptor", name = "" }` entries to compose a catalog. An interceptor entry must reference a configured service whose manifest advertises `provider_profiles = true`. Selecting only an interceptor makes that catalog authoritative; include `{ type = "user" }` explicitly to compose imported profiles with it. Empty or duplicate source lists, unknown interceptor names, and duplicate normalized profile IDs fail closed. Source order controls collection and diagnostics, not override precedence. + +OpenShell ships no profiles inside the gateway binary. A gateway with nothing imported reaches ready and serves an empty catalog; import the profiles it needs with `openshell provider profile import`. The `builtin` source type was removed and a configuration that still names it is rejected at startup. For an authoritative interceptor catalog, select only that interceptor: diff --git a/fern/pages-dev/reference/policy-prover.mdx b/fern/pages-dev/reference/policy-prover.mdx new file mode 100644 index 000000000..4e251adc3 --- /dev/null +++ b/fern/pages-dev/reference/policy-prover.mdx @@ -0,0 +1,216 @@ +--- +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +title: "Standalone Policy Prover" +sidebar-title: "Policy Prover" +description: "Install and use the standalone OpenShell policy prover to check a local candidate policy against a managed boundary." +keywords: "Generative AI, Cybersecurity, Policy, Prover, Boundary, Containment, CI" +position: 4 +--- + +`openshell-prover` checks whether the authority in a local candidate policy is +contained within a local boundary policy. The command reads files from the host +and does not connect to an OpenShell gateway. + +The candidate must be the fully composed effective policy after the proposed +change, including any provider-contributed authority. The boundary is an +operator-owned ceiling. It does not grant authority by itself. + +## Install the Prover + +The standard OpenShell installer includes `openshell-prover`: + +```shell +curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh +openshell-prover --version +``` + +The prover remains independent of the gateway at runtime. If you only need the +standalone binary, use the artifacts listed in the +[Support Matrix](/reference/support-matrix#standalone-policy-prover). These +artifacts and `openshell-prover-checksums-sha256.txt` are attached to +[OpenShell releases](https://github.com/NVIDIA/OpenShell/releases). + +The release archive includes the solver linkage required by the executable. +It does not require the main `openshell` command, a gateway configuration, or +a separate Z3 installation. + +## Check a Policy Boundary + +Create `boundary.yaml` with this boundary policy: + +```yaml +version: 1 +filesystem_policy: + read_only: + - /usr + - /etc +``` + +Create `candidate.yaml` with this contained candidate: + +```yaml +version: 1 +filesystem_policy: + read_only: + - /usr +``` + +Run the check: + +```shell +openshell-prover check candidate.yaml --boundary boundary.yaml +``` + +A contained candidate prints `result: within_boundary` and exits with status `0`. +To see an exceeding result and its counterexample, replace `candidate.yaml` +with a policy that grants writes to `/tmp`. The boundary grants no write +access: + +```yaml +version: 1 +filesystem_policy: + read_only: + - /usr + read_write: + - /tmp +``` + +```shell +openshell-prover check candidate.yaml --boundary boundary.yaml +``` + +To check the current effective policy of an existing sandbox, export it without +display metadata: + +```shell +openshell sandbox get my-sandbox --policy-only > candidate.yaml +openshell-prover check candidate.yaml --boundary boundary.yaml +``` + +This export includes provider-contributed rules. For a proposed change that is +not active, supply the complete post-change effective policy. The standalone +prover does not compose a base policy with provider rules. + +Use JSON when a script consumes the result: + +```shell +openshell-prover check candidate.yaml \ + --boundary boundary.yaml \ + --output json +``` + +The JSON object includes the result, exit code, input paths, prover version, +model version, checked policy version, and modeled domains. The documented +semantics and environmental conditions for that scope are tied to its model +version. An exceeding result also includes a filesystem or network +counterexample. Automation should use `result` and `reason_code` instead of +parsing the human-readable explanation. + +| Field | When populated | +|---|---| +| `counterexample` | An object for `exceeds_boundary`; otherwise `null`. | +| `reason_code` | A stable identifier for an emitted `error`, `unsupported`, or `inconclusive` result; otherwise `null`. | +| `reason` | A human-readable explanation paired with `reason_code`; otherwise `null`. | + +The stable reason codes are `invalid_input`, `unsupported_policy_shape`, +`unresolved_workdir`, `unresolved_binary_path`, +`unresolved_filesystem_path`, `solver_timeout`, `solver_unknown`, +`resource_limit`, `invalid_witness`, and `cancelled`. + +The solver has a finite 10-second default budget. Set a different positive +budget with an integer followed by `ms`, `s`, or `m`: + +```shell +openshell-prover check candidate.yaml \ + --boundary boundary.yaml \ + --timeout 30s \ + --output json +``` + +## Exit Codes + +| Exit | Result | Meaning | +|---|---|---| +| `0` | `within_boundary` | Containment was established for the reported model scope. | +| `1` | `exceeds_boundary` | The candidate exceeds the boundary; inspect the counterexample. | +| `2` | `error` | Arguments, input files, policy syntax, or command execution prevented a valid check. | +| `3` | `unsupported` or `inconclusive` | The model cannot soundly cover the policy shape, or the solver did not reach a determination. | +| `130` | `inconclusive` when graceful handling completes | The user interrupted the command with Ctrl-C on Unix; a second or very early interruption may prevent output. | + +Only exit `0` means the verification succeeded. Treat unsupported and +inconclusive results as failures in CI. + +## Interpretation and Limits + +The initial containment model covers filesystem paths, L4 network authority, +and enforced REST method and path authority, including explicit REST denies. +The result object reports the domains used for each check. Policies that use +recognized authority outside that model return `unsupported` rather than +silently ignoring it. + +Both inputs use the same bounded YAML/JSON parser and authored policy schema as +OpenShell. Unknown fields, duplicate keys, malformed field types, and unsupported +managed `metadata` or `review` annotations return `error` with +`reason_code: invalid_input` and exit `2`. Schema-valid controls outside the +containment model return `unsupported` and exit `3`. + +The prover applies aggregate limits across the candidate and boundary before +semantic shape validation: 1,024 network rules, 4,096 endpoints, 4,096 binary +selectors, 65,536 authored port entries, 16,384 REST rules, 4 KiB per modeled +pattern, and 1 MiB of modeled pattern text. Exceeding any limit returns +`inconclusive` with `reason_code: resource_limit`. A cancellation already +requested at preflight takes precedence over that result; otherwise a resource +limit takes precedence over unsupported policy-shape diagnostics. This ordering +keeps validation work bounded for checked-in CI inputs. + +Network binary selectors, endpoint host and path selectors, and REST allow and +deny method and path selectors must use ASCII literals in both the candidate and +boundary. A non-ASCII literal in one of these fields returns `unsupported` with +`reason_code: unsupported_policy_shape`, including when it appears only in a +deny. Embedded NUL bytes in these fields are also unsupported. This is a +prover-model limitation, not a general policy validation rule: filesystem paths +and unrelated policy text retain their existing Unicode behavior. ASCII +wildcard selectors still cover non-ASCII runtime values matched by the policy +engine. + +Containment means `Allowed(candidate)` is a subset of `Allowed(boundary)` under +the reported model. It does not establish least privilege, automatic approval +eligibility, semantic safety, or equivalence to a running sandbox's kernel +state. In particular: + +- The command does not fetch the current sandbox policy or compose provider + rules. The caller must supply the effective candidate. +- The command does not apply, approve, or persist a policy. +- Environment-dependent authority, such as an unresolved image workdir, + returns `unsupported` when the result depends on that missing context. +- Binary comparisons that can change when an image resolves an exact selector + through a symlink return `unsupported`. This covers exact candidate grants + under boundary globs and exact boundary denies replaced by candidate globs. + Use matching exact canonical paths when possible. +- Policies with overlapping L4 and enforced REST endpoints return + `unsupported` because inspection selection depends on the complete set of + matching runtime endpoint configurations. +- Network containment checks both supported runtime configurations: binary + identity enforcement enabled and disabled. When enabled, grants and denies + match the executable or an ancestor identity. Network counterexamples report + the configuration and identities that expose the additional authority. +- REST containment witnesses use canonical request methods and paths. +- If a solver string cannot be decoded and validated faithfully, the command + returns `inconclusive` with `reason_code: invalid_witness` instead of emitting + a counterexample. +- Both files are interpreted in the same sandbox filesystem namespace and + mount model, with stable path resolution when enforcement rules are created. + The CLI does not resolve sandbox paths against the host. +- Filesystem containment supports removing grants and reducing write grants to + read-only grants at matching paths. A boundary grant for `/` also covers other + paths for the same access. Comparisons between different paths otherwise + return `unsupported` with `reason_code: unresolved_filesystem_path`: a lexical + child can resolve outside its parent through a symlink, and unrelated paths + can resolve to the same object. This includes narrowing `/tmp` to `/tmp/cache`. + If the boundary grants no access of the requested kind, adding that access + returns `exceeds_boundary`. + +Use the [Policy Schema Reference](/reference/policy-schema) for the full policy +language. A successful prover result covers only the scope reported in its +evidence. diff --git a/fern/pages-dev/reference/support-matrix.mdx b/fern/pages-dev/reference/support-matrix.mdx index 7671866de..740b5c997 100644 --- a/fern/pages-dev/reference/support-matrix.mdx +++ b/fern/pages-dev/reference/support-matrix.mdx @@ -10,7 +10,7 @@ This page lists the host platform, compute driver, software, runtime, and kernel ## Supported Platforms -OpenShell publishes multi-architecture gateway images for `linux/amd64` and `linux/arm64`. The CLI, package-managed gateway, and standalone gateway binary are supported on the following host platforms: +OpenShell publishes multi-architecture gateway images for `linux/amd64` and `linux/arm64`. The CLI, policy prover, package-managed gateway, and standalone gateway binary are supported on the following host platforms: | Platform | Architecture | Status | | -------------------------------- | --------------------- | --------- | @@ -35,6 +35,18 @@ These artifacts are attached to GitHub releases. Kubernetes deployments should u On Linux, `openshell-gateway` requires glibc 2.28 or newer. Compatible systems include, for example, Ubuntu 20.04+, RHEL 8+, Rocky Linux 8+, Amazon Linux 2023+, and Fedora 32+. +## Standalone Policy Prover + +OpenShell publishes standalone `openshell-prover` release assets for manual download on these platforms: + +| Platform | Artifact pattern | +| --------------------- | ----------------------------------------------------- | +| Linux x86_64 (amd64) | `openshell-prover-x86_64-unknown-linux-musl.tar.gz` | +| Linux aarch64 (arm64) | `openshell-prover-aarch64-unknown-linux-musl.tar.gz` | +| macOS Apple Silicon | `openshell-prover-aarch64-apple-darwin.tar.gz` | + +These artifacts are attached to GitHub releases. The Linux binaries are static and do not require glibc. All prover archives include the required solver linkage. + ## Compute Drivers The gateway can manage sandboxes through several compute drivers. diff --git a/fern/pages-dev/sandboxes/inference-routing.mdx b/fern/pages-dev/sandboxes/inference-routing.mdx index 902c09eea..b0234e968 100644 --- a/fern/pages-dev/sandboxes/inference-routing.mdx +++ b/fern/pages-dev/sandboxes/inference-routing.mdx @@ -28,7 +28,7 @@ Start from an existing profile, review its access, and import it under a new ID. For example, export the NVIDIA profile: ```shell -openshell provider profile export nvidia -o yaml > nvidia-native.yaml +openshell provider profile export nvidia -o yaml --global > nvidia-native.yaml ``` In `nvidia-native.yaml`, change `id` to `nvidia-native`, give the profile a @@ -90,9 +90,11 @@ hosted service. Set a new profile ID, retain only the endpoints and credentials the workload needs, and name the actual client binaries. Provider attachment does not select or rewrite a model. -This release still loads built-in profiles for compatibility, so existing -providers continue to resolve their profiles. Treat those built-ins as starting -templates for new provider definitions rather than the primary setup workflow. +The gateway resolves a provider against the profiles you imported, so a provider +whose profile is missing fails at sandbox creation with a message naming the +profile and the import command. Treat the example profiles in +[`providers/`](https://github.com/NVIDIA/OpenShell/tree/main/providers) as +starting templates you copy and adapt, not as defaults. ## Attach a Provider to a Running Sandbox @@ -119,9 +121,11 @@ openshell sandbox provider detach inference-demo nvidia-prod ## Use a Custom or Self-hosted Endpoint -Do not reuse the built-in `openai` or `anthropic` profile for an alternate -host. Those profiles bind credentials to the public vendor endpoints. Import a -profile that names the intended endpoint and the binaries that may call it. +Do not reuse the `openai` or `anthropic` example profile for an alternate host. +Those profiles bind credentials to the public vendor endpoints, and a provider +that points `OPENAI_BASE_URL` elsewhere is treated as endpointless rather than +silently carrying the credential to the new host. Import a profile that names the +intended endpoint and the binaries that may call it. For a credentialless Ollama server on the gateway host, save this profile as `ollama-openai.yaml`: @@ -212,8 +216,9 @@ If the request is denied: OpenShell removed the workspace-global managed inference route and the `openshell inference` commands. Upgrades remove stored route records. Provider records, provider refresh configuration, and existing sandbox attachments are -preserved. Built-in profiles remain available during the transition, but new -setups should import an explicit profile under a new ID. +preserved. Import the profile each preserved provider refers to before the first +sandbox creation after the upgrade, and give new setups an explicit profile under +their own ID. The old route cannot be converted automatically. It applied one provider and model to every sandbox in a workspace, while provider attachments intentionally @@ -232,8 +237,8 @@ openshell inference set \ --timeout 300 ``` -Clients called `https://inference.local`, supplied a placeholder key and model, -and relied on OpenShell to rewrite the request. +Clients called a managed virtual endpoint, supplied a placeholder key and +model, and relied on OpenShell to rewrite the request. ### After @@ -253,9 +258,9 @@ openshell sandbox provider attach inference-demo nvidia-native-prod ``` OpenShell does not export stored credential values or change a provider's -profile type in place. If the original credential source is unavailable, the -compatibility built-in lets you attach the preserved provider while you arrange -credential rotation into the replacement provider. +profile type in place. If the original credential source is unavailable, import +the original profile at its own ID so the preserved provider keeps resolving +while you arrange credential rotation into the replacement provider. Update each workload to: @@ -266,9 +271,9 @@ Update each workload to: 5. Use the provider's native request format. Launch a new process after attachment and verify a native request before -upgrading production workloads. Code that still calls `inference.local` fails -DNS resolution because OpenShell no longer resolves or trusts that virtual -host. +upgrading production workloads. Code that still calls the retired managed +virtual endpoint fails DNS resolution because OpenShell no longer resolves or +trusts that host. ### Migration Checklist diff --git a/fern/pages-dev/sandboxes/manage-providers.mdx b/fern/pages-dev/sandboxes/manage-providers.mdx index 93f5b004b..03260b6cc 100644 --- a/fern/pages-dev/sandboxes/manage-providers.mdx +++ b/fern/pages-dev/sandboxes/manage-providers.mdx @@ -37,7 +37,7 @@ The fastest way to create a provider is to let the CLI discover credentials from your shell environment: ```shell -openshell provider create --name my-claude --type claude --from-existing +openshell provider create --name my-claude --type claude-code --from-existing ``` This reads `ANTHROPIC_API_KEY` or `CLAUDE_API_KEY` from your current environment @@ -92,9 +92,9 @@ CLI refreshes it before storing the provider credential. It does not store the OIDC refresh token in the provider. `--from-existing` uses profile-backed discovery. The requested `--type` must -have a built-in or imported provider profile with a `discovery` section. If no -matching profile exists, the CLI returns an error instead of falling back to -legacy discovery. +name an imported provider profile with a `discovery` section. If no matching +profile exists, the CLI returns an error instead of falling back to legacy +discovery. Create a provider whose profile requires no static credentials without a credential source: @@ -117,8 +117,8 @@ openshell provider profile update my-api -f my-api-profile.yaml ``` Import remains create-only and fails if the profile ID already exists. Use -`provider profile update ` for existing custom profiles. Built-in profiles are -read-only. The target ID must match the profile ID in the file. Update accepts +`provider profile update ` for existing profiles. Interceptor-managed +profiles are read-only. The target ID must match the profile ID in the file. Update accepts one file at a time and rejects stale resource versions. Updated profile policy applies to all provider instances of that type on the next sandbox config sync. @@ -133,7 +133,7 @@ failure and continues with the remaining IDs. The command exits with an error after it attempts every requested deletion if any entry failed. -Static credentials require a built-in or imported provider profile. Profiles +Static credentials require an imported provider profile. Profiles normally define at least one endpoint. An endpointless profile requires an explicit `credential_binding.provider` on a sandbox policy endpoint. OpenShell does not activate static credentials from a profileless provider because it @@ -178,6 +178,18 @@ Update a provider's credentials: openshell provider update my-claude --from-existing ``` +To wait until attached sandboxes apply the updated credentials, add `--wait`: + +```shell +openshell provider update my-claude --from-existing --wait --timeout 30 --output json +``` + +The update records which sandboxes are attached before saving the new credentials. Its result includes a change ID and outcome for each of those sandboxes. Sandboxes attached later are outside this wait. The timeout applies to the whole group; if any selected sandbox fails or times out, the command exits with an error and still reports each sandbox's outcome. When no sandboxes are attached, it reports the saved change and an empty target list. + +Credential refresh status tells you whether OpenShell obtained credentials. [Provider readiness](/providers/profiles#inspect-provider-readiness) tells you whether the sandbox applied the credentials, policy, and environment for new processes. It does not test access to a backend model or cancel requests already sent upstream. + +After a static credential update completes, launch a new client process to use the updated reference. An existing process keeps its revision-scoped reference; readiness does not make that old reference resolve the new value. Acknowledged detach revokes retained references and removes them from environments for future launches. + Set or clear a credential expiry timestamp: ```shell @@ -330,20 +342,23 @@ and `openshell sandbox provider detach`. See ### Auto-Discovery Shortcut -When the trailing command in `openshell sandbox create` maps to an available -profile, the CLI auto-creates the required provider from local profile -discovery if one does not already exist. You do not need to create the provider -separately: +Name a provider with `--provider`. If no provider by that name exists and the +name matches an imported profile ID, the CLI creates one from local profile +discovery, so you do not need to create it separately: ```shell -openshell sandbox create -- claude +openshell sandbox create --provider claude-code -- claude ``` -This detects `claude` as a known tool, finds your `ANTHROPIC_API_KEY`, creates -a provider, attaches it to the sandbox, and launches Claude Code. +This finds your `ANTHROPIC_API_KEY`, creates a `claude-code` provider, attaches +it to the sandbox, and launches Claude Code. Add `--auto-providers` to skip the +confirmation prompt, or `--no-auto-providers` to skip creation entirely. -If the inferred type has no built-in or imported profile, creation fails. Import -a custom profile first or pass an existing provider with `--provider `. +A provider is attached only when you name it. OpenShell does not derive one from +the trailing command: a profile's `binaries` list authorizes a binary to reach +that profile's endpoints, which is not a statement that running the binary asks +for the provider. A sandbox with no providers is a normal, fully supported +state. ## How Credential Injection Works @@ -411,51 +426,44 @@ Export the profile used by a provider to inspect its credential boundary: openshell provider profile export github -o yaml ``` -For the built-in GitHub profile, `GITHUB_TOKEN` and `GH_TOKEN` can resolve for +For the GitHub example profile, `GITHUB_TOKEN` and `GH_TOKEN` can resolve for the profile's `api.github.com:443` and `github.com:443` endpoints. Even if a sandbox policy allows `uploads.example.com:443`, sending either placeholder there returns `credential_endpoint_mismatch`. -For a custom service, define the credential in a custom provider profile. Put +For another service, define the credential in its own provider profile. Put stable endpoints in the profile, or leave the profile endpointless and bind each concrete provider instance from sandbox policy. Refer to [Profiles](/providers/profiles#provider-profiles) for the profile workflow and schema. -## Supported Provider Types +## Available Provider Types -The following provider types are supported. +The provider types available to `--type` are the IDs of the profiles you +imported. List them: -| Type | Environment Variables Injected | Typical Use | -|---|---|---| -| `anthropic` | `ANTHROPIC_API_KEY` | Anthropic API | -| `aws-bedrock` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION` | Declarative Bedrock credential shape. The built-in profile does not grant a bridge or AWS endpoint; use an endpoint-bearing profile for direct access. | -| `claude` | `ANTHROPIC_API_KEY`, `CLAUDE_API_KEY` | Claude Code, Anthropic API | -| `codex` | `OPENAI_API_KEY` | OpenAI Codex | -| `copilot` | `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN` | GitHub Copilot CLI | -| `deepinfra` | `DEEPINFRA_API_KEY` | DeepInfra inference API | -| `github` | `GITHUB_TOKEN`, `GH_TOKEN` | GitHub API and `gh` CLI. Refer to [GitHub Sandbox](/get-started/tutorials/github-sandbox). | -| `nvidia` | `NVIDIA_API_KEY` | NVIDIA API Catalog | -| `openai` | `OPENAI_API_KEY` | Public OpenAI API. Use a custom endpoint-bearing profile for another OpenAI-compatible host. | +```shell +openshell provider list-profiles +``` + +The repository's +[`providers/`](https://github.com/NVIDIA/OpenShell/tree/main/providers) directory +ships example profiles for GitHub, PyPI, and the major inference and agent +providers. Each file's header states the credential environment variables it +declares, the binaries it authorizes, and the endpoints it grants. Read it, +adjust the binary paths for your image, and import your copy. `ANTHROPIC_API_KEY` is an API key from [console.anthropic.com](https://console.anthropic.com), not a subscription token. Subscription users must generate a separate API key from the Anthropic Console. - -For a service not listed above, import a custom provider profile that declares -its credential environment variables and endpoints. Use the imported profile -ID as the provider `--type`. - - - ## Inference Provider Access Attach an inference provider to each sandbox that needs it and configure the workload to call the provider's native endpoint. The workload, not OpenShell, selects the model and request timeout. -| Provider | Built-in profile | Native base URL | Credential variable | +| Provider | Example profile | Native base URL | Credential variable | |---|---|---|---| | OpenAI | `openai` | `https://api.openai.com/v1` | `OPENAI_API_KEY` | | Anthropic | `anthropic` | `https://api.anthropic.com` | `ANTHROPIC_API_KEY` | @@ -463,10 +471,10 @@ selects the model and request timeout. | DeepInfra | `deepinfra` | `https://api.deepinfra.com/v1/openai` | `DEEPINFRA_API_KEY` | | Google Vertex AI | `google-vertex-ai` | Region and model dependent | `GOOGLE_VERTEX_AI_TOKEN` or `GOOGLE_VERTEX_AI_SERVICE_ACCOUNT_TOKEN` | -An OpenAI-compatible protocol does not make the built-in `openai` profile safe -for an arbitrary host. Baseten, Bitdeer, Groq, Ollama, LM Studio, self-hosted -NIM, and other alternate endpoints need a custom profile that declares the -actual host, port, credential, and allowed binaries. See +An OpenAI-compatible protocol does not make the `openai` profile safe for an +arbitrary host. Baseten, Bitdeer, Groq, Ollama, LM Studio, self-hosted NIM, and +other alternate endpoints need their own profile declaring the actual host, +port, credential, and allowed binaries. See [Provider-backed Inference](/sandboxes/inference-routing) for complete examples and migration guidance. diff --git a/fern/pages-dev/sandboxes/policies.mdx b/fern/pages-dev/sandboxes/policies.mdx index f4202319a..749dd0c5a 100644 --- a/fern/pages-dev/sandboxes/policies.mdx +++ b/fern/pages-dev/sandboxes/policies.mdx @@ -230,11 +230,13 @@ The following steps outline the hot-reload policy update workflow. --wait openshell policy update \ + --rule-name allow_api_github_com_443 \ + --binary /usr/bin/gh \ --add-allow 'api.github.com:443:POST:/repos/*/issues' \ --wait ``` - `--add-allow` and `--add-deny` target existing `protocol: rest` or `protocol: websocket` endpoints. If you pass multiple update flags in one command, OpenShell applies them as one atomic merge batch and persists at most one new revision. + `--add-allow` and `--add-deny` target existing `protocol: rest` or `protocol: websocket` endpoints. They require the rule name and complete affected binary and port scope. Run endpoint creation and L7 appends as separate commands; `--add-endpoint` cannot share a command with `--add-allow` or `--add-deny`. Compatible update flags in one command form one atomic merge batch and persist at most one new revision. 4. For larger edits, pull the current base policy and edit the YAML directly. The base policy is the user-authored policy without provider-composed `_provider_*` entries, so it is safe to round-trip through `openshell policy set`. Before reusing the file, strip the metadata header above the `---` line. @@ -329,8 +331,10 @@ The incremental update surface is split into endpoint-level operations and metho | `--remove-rule ` | Deletes a named `network_policies` entry. | Remove a whole rule by name when you no longer need it. | | `--add-allow ` | Appends method/path allow rules to an existing REST or WebSocket endpoint. | Permit one additional REST method/path or WebSocket `WEBSOCKET_TEXT` path on an API that is already configured. | | `--add-deny ` | Appends method/path deny rules to an existing REST or WebSocket endpoint. | Block a sensitive REST path or WebSocket text-message path under an endpoint that is otherwise allowed. | -| `--binary ` | Adds binaries to every `--add-endpoint` rule in the same command. | Bind a new endpoint to one or more executables. | -| `--rule-name ` | Overrides the generated rule name. | Keep a stable human-chosen rule name when adding exactly one endpoint. | +| `--binary ` | Adds binaries for `--add-endpoint`; declares the complete existing binary set for L7 appends. | Repeat for every executable sharing the target rule. | +| `--rule-name ` | Names one new endpoint rule or selects the existing rule for L7 appends. | Required for `--add-allow` and `--add-deny`. | +| `--any-binary` | Explicitly acknowledges an existing rule that authorizes every binary. | Use for L7 appends instead of `--binary` when the target rule has no binary restriction. | +| `--endpoint-path ` | Selects an exact existing endpoint path for L7 appends. | Resolve path ambiguity; pass `''` to select an endpoint without a path scope. | | `--dry-run` | Shows the merged policy locally and does not call the gateway. | Review the result before persisting it. | | `--wait` | Polls until the sandbox reports that the new revision loaded. | Confirm the change took effect before continuing. | | `--timeout ` | Sets the timeout for `--wait`. | Extend the wait window for slower sandboxes. | @@ -430,7 +434,7 @@ In shell commands, quote the full `SPEC` when it contains `*` or `**` so your sh | Segment | Meaning | |---|---| | `host` | Existing endpoint host. | -| `port` | Existing endpoint port. | +| `port` | Complete comma-separated port set of the existing endpoint, such as `443,8443`. | | `METHOD` | HTTP method for REST endpoints, or `GET` / `WEBSOCKET_TEXT` for WebSocket endpoints. The CLI normalizes it to uppercase. | | `path_glob` | URL path glob. For WebSocket text messages, this still matches the upgraded request path, not message payload content. It must start with `/`, or be `**`, or start with `**/`. | @@ -455,7 +459,9 @@ Path globs follow the same semantics as YAML allow and deny rules: - `/repos/*/issues` matches any intervening text, including multiple path segments. - `/repos/**` matches everything under `/repos/`. -The rule-level commands only modify method and path constraints. They do not change binaries, hostnames, ports, protocol settings, or WebSocket message payload matching. +The rule-level commands only modify method and path constraints. They do not change binaries, hostnames, ports, protocol settings, or WebSocket message payload matching. Each change applies to every binary and port on the target endpoint. Supply `--rule-name` and every binary with repeated `--binary`, or use `--any-binary` when the stored rule has an empty binary list. The declared binary and port sets must match the stored scope exactly; OpenShell reports the expected scope when they differ. + +Use `--endpoint-path` to select an endpoint by its stored path when the rule contains more than one endpoint matching the host and ports. `--endpoint-path ''` selects an endpoint without a path selector. This selector is separate from the request path in the allow or deny rule. All L7 additions in one command use the same rule name, binary declaration, and endpoint-path selector. ### Common Workflows @@ -496,6 +502,8 @@ Use `--add-allow` after the REST endpoint already exists. ```shell openshell policy update demo \ --add-allow 'api.github.com:443:POST:/repos/*/issues' \ + --rule-name allow_api_github_com_443 \ + --binary /usr/bin/gh \ --wait ``` @@ -508,6 +516,8 @@ Use `--add-deny` when you want to carve out a blocked subtree under an existing ```shell openshell policy update demo \ --add-deny 'api.github.com:443:POST:/admin/**' \ + --rule-name allow_api_github_com_443 \ + --binary /usr/bin/gh \ --wait ``` @@ -533,6 +543,8 @@ Use `WEBSOCKET_TEXT` when you want to refine client-to-server text-frame policy ```shell openshell policy update demo \ --add-deny 'realtime.example.com:443:WEBSOCKET_TEXT:/v1/admin/**' \ + --rule-name allow_realtime_example_com_443 \ + --binary /usr/bin/node \ --wait ``` @@ -568,6 +580,8 @@ Use `--dry-run` when you want to inspect the merged YAML before you send it to t ```shell openshell policy update demo \ --add-allow 'api.github.com:443:GET:/repos/**' \ + --rule-name allow_api_github_com_443 \ + --binary /usr/bin/gh \ --dry-run ``` @@ -577,7 +591,7 @@ The CLI validates the argument shapes before it sends the request. The gateway t - a port is outside `1` through `65535`. - `--add-allow` or `--add-deny` points at an endpoint that does not exist. - `--add-allow` or `--add-deny` targets an endpoint that is neither REST nor WebSocket. -- `--add-allow` or `--add-deny` targets a host and port that resolves to more than one endpoint. +- `--add-allow` or `--add-deny` omits the rule name or binary declaration, names incomplete binary or port scope, or still matches several endpoints within the selected rule. - `--add-deny` targets an endpoint that has no base allow set. - an update names an existing rule and adds a binary to it without declaring every endpoint and port that rule already authorizes. - an update names an existing rule and adds or changes an endpoint on it without declaring every binary that rule already authorizes. @@ -595,7 +609,20 @@ The sandbox picks the parser for a request by most-specific path, but it authori To grant one binary access to only part of an existing rule's endpoints, send it under its own `--rule-name`. The gateway normally folds an update into an existing rule that shares an endpoint, but it keeps your rule name whenever folding would grant authorization you did not declare, so the narrow grant lands as its own rule authorizing exactly what you asked for. The update reports that it kept your rule name and names the rule it would otherwise have folded into. An MCP contract conflict is the exception: one host and port carry a single MCP contract regardless of which rule holds them, so a conflicting update is rejected rather than moved to a separate rule. -Once a host and port appears in more than one rule, `--add-allow` and `--add-deny` can no longer target it. They select an endpoint by host and port alone, so they cannot say which rule's binary scope to widen, and the gateway rejects the update rather than guessing. The same applies when one rule carries two endpoints on that host and port under different paths. Use full YAML replacement to change L7 rules on an endpoint that appears more than once. +When several rules contain the same host and port, `--rule-name` selects the rule to change. Within that rule, use `--endpoint-path` if several endpoints match. OpenShell rejects missing, ambiguous, or mismatched targets atomically. Provider-owned rules cannot be changed through these operations; update the provider profile instead. + +For example, this appends one permission to an endpoint shared by two binaries on two ports: + +```shell +openshell policy update demo \ + --rule-name internal_api \ + --binary /usr/bin/curl \ + --binary /usr/bin/python3 \ + --add-allow 'api.example.com:443,8443:POST:/admin' \ + --wait +``` + +The command explicitly declares all four binary-to-port combinations affected by the new permission. Omitting either binary or either port rejects the whole update. To grant a permission to a smaller scope, create a separate rule with `--add-endpoint` or edit the policy structure first. ## Global Policy Override @@ -648,7 +675,7 @@ for the complete authorization model. For small changes, prefer `openshell policy update` over rewriting the full YAML: ```shell -openshell policy update --add-allow 'api.github.com:443:GET:/repos/**' --wait +openshell policy update --rule-name allow_api_github_com_443 --binary /usr/bin/gh --add-allow 'api.github.com:443:GET:/repos/**' --wait ``` ## Examples