Skip to navigation

Policy Schema Reference

View as Markdown

This reference defines the sandbox policy schema. A policy is a YAML file of at most 4 MiB. OpenShell rejects a policy that contains unknown fields or duplicate keys.

Top-Level Fields

version: 1
filesystem_policy: { ... }
landlock: { ... }
process: { ... }
network_policies: { ... }
network_middlewares: { ... }
FieldTypeRequiredTakes effectDescription
versionintegerYes—Schema version. Must be 1.
filesystem_policyobjectNoStartupPaths the workload can read and write.
landlockobjectNoStartupLandlock enforcement behavior.
processobjectNoStartupUser and group that the workload runs as.
network_policiesmapNoLiveWhich binaries can reach which network endpoints.
network_middlewaresmapNoLiveMiddleware applied to allowed traffic.

Startup fields take effect when a sandbox starts. Live fields can change while it runs. Refer to How Changes Take Effect.

Filesystem Policy

FieldTypeDefaultDescription
include_workdirboolSee belowAdds the sandbox’s working directory to read_write.
read_onlylist of strings[]Paths the workload can read.
read_writelist of strings[]Paths the workload can read and write.

When filesystem_policy is omitted, include_workdir is true. When filesystem_policy is present, include_workdir defaults to false. Paths that are not listed are inaccessible. When the effective policy has at least one network rule, OpenShell also adds the baseline paths described in Default Policy.

These defaults and the filesystem path restrictions below also apply when the supervisor or standalone proxy loads local policy files directly. Local loading enforces the same non-root process identities as typed policy. It rejects invalid filesystem, Landlock, and process field types, unknown fields in those sections, and explicit null JSON-RPC or MCP options. Network settings must also use the declared types: for example, allow_encoded_slash: "true" and an array-valued deny-rule method are rejected. A rejected reload keeps the active OPA policy and its generation unchanged.

Each path must be absolute, must not contain .., and must not exceed 4096 bytes. read_write cannot contain /. A policy can list at most 256 paths.

filesystem_policy:
include_workdir: true
read_only: [/usr, /lib, /etc]
read_write: [/tmp]

Landlock

FieldTypeDefaultValues
compatibilitystringbest_effortbest_effort or hard_requirement

Both values skip individual paths that are missing or that the workload cannot open. They differ when none of the listed paths can be applied, or when Landlock fails to enforce the policy’s rules:

ValueBehavior
best_effortThe sandbox runs without the filesystem rules and logs a high-severity finding.
hard_requirementThe sandbox fails to start.

In either mode, a sandbox requires a kernel with Landlock ABI v3 or later. The sandbox log’s Landlock ruleset built event reports how many paths were applied and skipped.

Process

FieldTypeDefaultDescription
run_as_userstringDriver defaultsandbox or a numeric UID for the workload.
run_as_groupstringDriver defaultsandbox or a numeric GID for the workload.

A numeric ID must be from 1 through 4294967294, so OpenShell rejects root. Each field is independent, so you can set one and let the compute driver choose the other. Docker and Podman use these fields to select the workload identity from the policy passed at creation; omitted fields use the image’s USER. Kubernetes uses the identity configured for its driver.

MicroVM preserves the driver’s resolved overlay owner. An explicit numeric selector must match that owner; sandbox names the guest account reconciled to that owner. A conflicting selector fails before the workload starts, and omitted fields accept the driver default. openshell sandbox get <name> reports the resolved UID:GID in its WorkloadIdentity condition. This condition reports identity selection, not workload readiness. Sandbox policy updates cannot change process selectors; global policy updates must still match the resolved identity. Ordinary exec uses the same identity as the canonical process.

process:
run_as_user: "1500"
run_as_group: "1500"

Network Policies

A map of named rules. The key is the rule’s name. Each rule allows every listed binary to reach every listed endpoint. For how OpenShell evaluates rules, and for examples, refer to Network Rules.

FieldTypeRequiredDescription
namestringNoDisplay name in logs. Defaults to the key.
endpointslist of endpoint objectsNoDestinations the rule allows.
binarieslist of binary objectsNoExecutables the rule applies to. An empty list matches no binary.

Rule keys cannot start with _provider_, which OpenShell reserves for rules that providers contribute.

Endpoint Object

Endpoint fields fall into four groups.

Destination Fields

FieldTypeRequiredDescription
hoststringConditionalHostname, IP address, or wildcard pattern, such as *.example.com.
portintegerConditionalTCP port. Set port or ports.
portslist of integersConditionalTCP ports. Use instead of port, not with it.
pathstringNoPath glob that selects among inspected endpoints on the same host and port. The most specific match wins.
allowed_ipslist of stringsNoIP addresses or CIDR ranges that resolved addresses must fall within.

A wildcard host must have at least three DNS labels. * can appear within the first label, such as *-api.example.com, or as a whole later label, such as api.*.example.com. ** is allowed only as the whole first label.

An endpoint can omit host only when it sets allowed_ips. Exact hostnames can reach the private addresses they resolve to, unless the endpoint comes from an approved policy advisor proposal. Wildcard and hostless endpoints can reach private addresses only through allowed_ips.

Network policy never authorizes an outbound endpoint whose destination is loopback, link-local, or unspecified, including the cloud metadata address 169.254.169.254. This does not prevent a workload from connecting to a sandbox-local service listening on loopback. openshell policy update rejects an allowed_ips entry that overlaps the blocked ranges, and in a complete policy such an entry blocks every connection to the endpoint. OpenShell also blocks the Kubernetes and etcd control-plane ports 2379, 2380, 6443, 10250, and 10255 on endpoints that use an exact hostname, an IP address, or allowed_ips. A rule for host.openshell.internal can still reach services on the gateway host.

Inspection Fields

FieldTypeDefaultDescription
protocolstringNonerest, websocket, graphql, mcp, or json-rpc for request inspection, or tcp for a native TCP connection. graphql, mcp, and json-rpc endpoints refuse requests that carry an Upgrade header with 403. Refer to Connection and Request Checks.
tlsstringAutomaticskip relays traffic without terminating TLS, so OpenShell cannot inspect it. Do not use it with a request protocol.
enforcementstringauditenforce blocks requests that break the endpoint’s rules. audit logs them and allows the request.
accessstringNoneAccess preset: read-only, read-write, or full. Refer to Access Presets.
ruleslistNoneAllow rules.
deny_ruleslistNoneDeny rules, which take precedence over allow rules.
allow_encoded_slashboolfalseAccepts %2F in request paths, as used by npm scoped packages.

Credential Fields

FieldTypeDefaultDescription
credential_binding.providerstringNoneBinds the static credentials of an attached provider whose profile defines no endpoints. Valid only in a sandbox policy.
request_body_credential_rewriteboolfalseReplaces credential placeholders in REST request bodies.
websocket_credential_rewriteboolfalseReplaces credential placeholders in client WebSocket text messages.
allow_uninspected_credentialsboolfalseAllows a provider-credentialed endpoint to omit request inspection or use tls: skip.
credential_signingstringNoneAWS request signing: sigv4, sigv4:body, or sigv4:no_body.
signing_servicestringNoneAWS service name, such as bedrock or s3. Required with credential_signing.
signing_regionstringFrom hostnameAWS region override, such as us-east-1.

Credential placeholders use the form openshell:resolve:env:KEY. Body rewriting applies to UTF-8 JSON, form, and text bodies of up to 256 KiB, and cannot be combined with credential_signing. Signing requires an attached provider with AWS credentials. Refer to AWS SigV4 and Static Credential Endpoint Binding.

network_policies:
gcp_storage:
endpoints:
- host: storage.googleapis.com
port: 443
protocol: rest
access: full
credential_binding:
provider: work-gcp
binaries:
- path: /usr/bin/curl

Protocol Options

FieldTypeDefaultDescription
persisted_queriesstringdenyGraphQL hash-only queries: deny or allow_registered.
graphql_persisted_queriesmapNoneTrusted persisted-query registry, keyed by hash or saved-query ID. With allow_registered, OpenShell denies hash-only queries that are not in the registry.
graphql_max_body_bytesinteger65536Maximum GraphQL request body size for inspection.
mcp.versionslist of strings["2025-11-25"]Allowed MCP revisions. Refer to MCP Version Selection.
mcp.max_body_bytesinteger65536Maximum MCP request body size for inspection.
mcp.strict_tool_namesbooltrueRequires tool names to match ^[A-Za-z0-9_.-]{1,128}$.
mcp.allow_all_known_mcp_methodsboolfalseAllow core MCP methods for the selected request revision. Tool restrictions still apply, and deny rules take precedence. Refer to Allow Core MCP Methods for the behavior and Core MCP Methods for the method list.
json_rpc.max_body_bytesinteger65536Maximum JSON-RPC request body size for inspection.

Endpoint Constraints

OpenShell rejects an endpoint that breaks these rules:

  • access and rules cannot be combined. rest, websocket, and graphql endpoints need one of them. mcp and json-rpc endpoints need rules, unless an MCP endpoint sets mcp.allow_all_known_mcp_methods: true.
  • deny_rules require protocol, and require rules or access on endpoints other than MCP.
  • protocol: tcp requires a hostname and a port. It accepts no request fields, such as path, enforcement, access, rules, credential rewriting or signing, or protocol options.
  • Endpoints whose hosts can match the same name on the same port, including through wildcards, must use the same tls and allowed_ips values. Inspected endpoints that can match the same request must also agree on protocol, enforcement, and credential settings.

Without protocol, access and rules have no effect.

Access Presets

REST, WebSocket, and GraphQL endpoints accept these presets. MCP and JSON-RPC endpoints do not.

ValueRESTWebSocketGraphQL
fullAll methods and paths.Upgrade and all client text messages.All operations.
read-onlyGET, HEAD, OPTIONS.Upgrade only.query operations.
read-writeGET, HEAD, OPTIONS, POST, PUT, PATCH.Upgrade and client text messages.query and mutation operations.

Allow and Deny Rules

Each entry in rules wraps its matcher fields in allow. Each entry in deny_rules lists the matcher fields directly. With enforcement: enforce, a request that matches any deny rule is blocked, regardless of the allow rules or access preset.

rules:
- allow:
method: GET
path: /repos/**
deny_rules:
- method: GET
path: /repos/private/**

The matcher fields depend on the endpoint’s protocol.

REST Rules

FieldTypeRequiredDescription
methodstringYesHTTP method, or * for any method.
pathstringYesPath glob. Refer to Matcher Semantics.
querymapNoQuery parameter matchers, keyed by parameter name. Each value is a glob or { any: [globs] }.

In an allow rule, every value of a repeated query parameter must match. In a deny rule, every configured parameter must be present, and one matching value per parameter is enough.

rules:
- allow:
method: GET
path: /api/v1/download
query:
platform:
any: ["linux-*", "darwin-*"]
deny_rules:
- method: "*"
path: "/repos/*/*/rulesets"

WebSocket Rules

FieldTypeRequiredDescription
methodstringYesGET for the upgrade request, WEBSOCKET_TEXT for client text messages, or * for both.
pathstringYesPath glob of the upgrade request, not message content.
querymapNoQuery parameter matchers of the upgrade request.

OpenShell does not inspect binary frames or messages from the server.

rules:
- allow:
method: GET
path: /v1/realtime
- allow:
method: WEBSOCKET_TEXT
path: /v1/realtime
deny_rules:
- method: WEBSOCKET_TEXT
path: /v1/admin/**

GraphQL Rules

FieldTypeRequiredDescription
operation_typestringYesquery, mutation, or subscription. To allow every operation type, use access: full.
operation_namestringNoOperation name glob.
fieldslist of stringsNoTop-level field globs.

In an allow rule, every top-level field must match. In a deny rule, one matching field is enough, and omitting fields denies every matching operation. One denied operation denies an entire batched request.

rules:
- allow:
operation_type: query
- allow:
operation_type: mutation
fields: [createIssue]
deny_rules:
- operation_type: mutation
fields: [deleteRepository]

For GraphQL over WebSocket, use protocol: websocket with a GET rule for the upgrade and GraphQL rules for operations. Do not mix WebSocket and GraphQL matcher fields in one rule.

MCP Rules

FieldTypeRequiredDescription
methodstringConditionalMCP method, such as initialize or tools/call. Globs are allowed only in the tools/ family, and * is not allowed. Required unless mcp.allow_all_known_mcp_methods is true.
toolstring or { any: [globs] }NoTool name matcher for tools/call.
params.namestring or { any: [globs] }NoLower-level equivalent of tool.
  • Rules with tool or params.name must set method: tools/call, unless mcp.allow_all_known_mcp_methods is true.
  • A rule that matches all of tools/call cannot be combined with tool-specific allow rules.
  • Wildcard tool matchers require mcp.strict_tool_names: true.
  • Tool arguments are not matched, so an allowed tool accepts any arguments.
  • One denied call denies an entire batched request.
  • Server responses and server-to-client messages are not inspected.
  • Only an allow rule that names an extension method exactly can allow it. Method globs and mcp.allow_all_known_mcp_methods do not. An extension method is one that no supported MCP revision defines.

Under the 2025 revisions, a client sends initialize and notifications/initialized before calling tools. When mcp.allow_all_known_mcp_methods is omitted or false, allow both explicitly:

rules:
- allow:
method: initialize
- allow:
method: notifications/initialized
- allow:
method: tools/call
tool:
any: [search_web, list_issues]
deny_rules:
- method: tools/call
tool: send_email

OpenShell uses the tower-mcp-types library to inspect each request under its MCP revision. It checks the JSON-RPC structure, whether a client can send the method in that revision, and the parameter types of known methods. It rejects a body that repeats a key within any JSON object, and a method that MCP defines but the request’s revision does not, such as server/discover under 2025-11-25. A request that fails these checks returns 400 with the error invalid_mcp_request, even on an endpoint that uses enforcement: audit. These type checks are not complete JSON Schema validation.

Allow Core MCP Methods

Set mcp.allow_all_known_mcp_methods: true to allow the core methods available in each request’s selected MCP revision. A policy that accepts several revisions still checks each request against its own revision. The option does not combine their method sets.

Tool-specific allow rules continue to restrict tools/call to matching names, and deny rules take precedence over every allow. Without tool-specific allow rules, this option allows calls to every tool name, subject to deny rules and tool-name validation. Extension methods require an allow rule that names the method exactly. An explicit allow rule cannot enable a core method that is unavailable in the selected revision.

For example, this endpoint allows core methods for 2025-11-25, while limiting tool calls to search_web and read_document:

endpoints:
- host: mcp.example.com
port: 443
protocol: mcp
enforcement: enforce
mcp:
versions: ["2025-11-25"]
allow_all_known_mcp_methods: true
rules:
- allow:
tool:
any: [search_web, read_document]

Here, tools/call is the JSON-RPC method, and search_web is the tool name in params.name. Allowing a method does not establish that the server supports it or that a tool is read-only.

Core MCP Methods

The following list covers client-to-server requests and notifications that OpenShell inspects over HTTP. Server-to-client methods, such as sampling/createMessage, roots/list, and elicitation/create, are outside this inspection. Each row lists the revisions that accept those methods over HTTP in the client-to-server direction.

Methods or notificationsMCP revisions
tools/list, tools/callAll supported revisions.
resources/list, resources/templates/list, resources/readAll supported revisions.
prompts/list, prompts/get, completion/completeAll supported revisions.
notifications/cancelled2025-03-26, 2025-06-18, 2025-11-25.
initialize, ping, logging/setLevel2025-03-26, 2025-06-18, 2025-11-25.
resources/subscribe, resources/unsubscribe2025-03-26, 2025-06-18, 2025-11-25.
notifications/initialized, notifications/progress, notifications/roots/list_changed2025-03-26, 2025-06-18, 2025-11-25.
tasks/get, tasks/result, tasks/list, tasks/cancel, notifications/tasks/status2025-11-25 only; experimental.
server/discover, subscriptions/listen2026-07-28 only.

For 2026-07-28 HTTP requests, cancel a request by closing its response stream. OpenShell rejects notifications/cancelled over HTTP in that revision.

MCP introduced tasks as an experimental feature in 2025-11-25, so the March and June 2025 revisions do not include the task methods or notifications/tasks/status. The default policy revision includes them, but using tasks also requires the peer’s task capabilities and, for task-augmented tool calls, the tool’s task-support declaration. The policy option does not negotiate these capabilities.

MCP Version Selection

mcp.versions lists the MCP revisions an endpoint accepts: 2025-03-26, 2025-06-18, 2025-11-25, or 2026-07-28. When omitted, the list is exactly ["2025-11-25"], so the endpoint does not accept 2026-07-28 until you list it.

mcp:
versions: ["2025-03-26", "2025-11-25"]

OpenShell selects each request’s revision from its MCP-Protocol-Version header. When the header is absent, OpenShell selects 2025-03-26, so an endpoint whose clients omit the header must allow 2025-03-26. A duplicate, empty, or unsupported header value returns 400, and a supported revision that the endpoint does not allow returns 403.

OpenShell inspects each request under its selected revision only. For example, only 2025-03-26 accepts JSON-RPC batches, with at most 64 messages each, and later revisions reject a request body that is a JSON array.

Under the 2025 revisions, a client starts with a single initialize request that proposes a revision in its body, so OpenShell does not check that request’s revision against mcp.versions. An endpoint that allows only 2026-07-28 rejects initialize.

For a client that requires an unsupported revision, omit protocol and mcp to allow its traffic without MCP inspection.

Understand MCP Rejections

Use the response’s error code and detail to distinguish a protocol problem from a policy denial:

  • unsupported_mcp_protocol_version (400) means this OpenShell build does not support the header’s revision. Configure the client to use a revision that OpenShell and the server support and that mcp.versions permits.
  • mcp_protocol_version_not_allowed (403) means the selected revision is supported but excluded by the endpoint policy. Use a compatible revision the policy permits, or ask the policy owner to review mcp.versions.
  • A detail that names the missing-header fallback means OpenShell selected 2025-03-26, even if the policy defaults to 2025-11-25. Send the client/server revision explicitly in MCP-Protocol-Version; the policy must permit it.
  • invalid_mcp_request (400) can mean the method is unavailable in the selected revision. For example, tasks/get is unavailable in 2025-06-18. Use a method available in that revision, or configure the client and server to use a compatible revision that includes it and is permitted by policy. Adding an allow rule or enabling allow_all_known_mcp_methods cannot override this protocol check.
  • A rule denial (403 under enforcement: enforce) requires a policy review. An extension method needs an exact method allow rule; a denied tool call may need a matching tool selector. An explicit deny rule takes precedence, so adding an allow rule cannot override it. Ask the policy owner to review the relevant allow or deny rule.

Sessionless MCP Requests

The 2026-07-28 revision has no initialize handshake or session. Each request carries the client’s protocol version and capabilities. A client can call server/discover to learn what the server supports, and can call subscriptions/listen to receive notifications on that request’s response stream. Each HTTP POST carries one JSON-RPC request or extension notification.

OpenShell checks each 2026-07-28 request for the following:

  • params._meta contains io.modelcontextprotocol/protocolVersion set to 2026-07-28, and io.modelcontextprotocol/clientCapabilities. Extension requests need this metadata too.
  • The MCP-Protocol-Version header is 2026-07-28. Without it, OpenShell selects 2025-03-26 and rejects the request.
  • The Mcp-Method header equals the JSON-RPC method.
  • For tools/call and prompts/get, the Mcp-Name header equals params.name. For resources/read, it equals params.uri. OpenShell decodes a =?base64?...?= value before comparing it.

A request with missing or invalid metadata, or with a missing, duplicate, or mismatched Mcp-Method or Mcp-Name header, returns 400. OpenShell also rejects initialize, notifications/initialized, notifications/cancelled, JSON-RPC responses from the client, and batches. A request that uses an HTTP method other than POST returns 405.

OpenShell repeats these checks after middleware changes a request. A middleware that replaces the body of a 2026-07-28 request must also update Mcp-Method and Mcp-Name to match the new body.

An extension notification needs MCP-Protocol-Version: 2026-07-28 and an allow rule that names its method exactly, such as method: vendor/notice. It does not need params._meta, Mcp-Method, or Mcp-Name.

OpenShell forwards Mcp-Param-* headers without comparing them with tool arguments.

JSON-RPC Rules

FieldTypeRequiredDescription
methodstringYesExact method name, or * for all methods. Other globs are rejected.

Parameters are not matched. One denied call denies an entire batched request. OpenShell rejects a body that repeats a key within any JSON object.

rules:
- allow:
method: reports.search
deny_rules:
- method: reports.delete

Binary Object

FieldTypeRequiredDescription
pathstringYesExecutable path or glob, such as /usr/bin/curl or /usr/lib/jvm/*/bin/java.

A binary matches the executable that opens the connection or any of its parent processes. Scripts run as their interpreter, so list the interpreter, such as /usr/bin/python3.12, for a Python script. List the executable’s real path, not a symlink to it. OpenShell records a hash of each executable the first time it takes part in a connection, and denies later connections if the file changes. Refer to Binary Matching.

Network Middleware

A map of up to 10 middleware configurations. Middleware runs on traffic that network rules allow, in ascending order.

FieldTypeRequiredDescription
middlewarestringYesBuilt-in middleware, such as openshell/regex, or the name of a service registered with the gateway.
endpoints.includelist of stringsYesHost patterns the middleware applies to.
endpoints.excludelist of stringsNoHost patterns to skip. Takes precedence over include.
orderintegerNoRun order. Lower values run first. Values must be unique. Defaults to 0.
configobjectNoConfiguration for the middleware.
on_errorstringNofail_closed blocks traffic when the middleware fails. fail_open skips it. Defaults to fail_closed.
namestringNoDisplay name. Defaults to the key.

Host patterns match the same way as endpoint hosts, up to 32 patterns per configuration. A fail_closed configuration cannot apply to an endpoint with tls: skip. Refer to Supervisor Middleware.

network_middlewares:
regex-redactor:
middleware: openshell/regex
order: 10
config:
mode: redact
on_error: fail_closed
endpoints:
include: ["*.example.com"]
exclude: ["trusted.example.com"]

Matcher Semantics

Glob patterns follow one set of rules. Each matcher splits values at a separator:

MatcherSeparatorCase-sensitive
Endpoint host and middleware hosts.No
Binary path/Yes
REST and WebSocket rule path/Yes
Query values and MCP tool names.Yes
  • * matches any characters except the separator.
  • ** matches across separators only when it is a whole segment, as in /repos/**, **.example.com, or github.**. Next to other characters, as in **secret**, it behaves like *. A whole-segment ** needs at least one segment, so /repos/** does not match /repos.
  • ? matches one character except the separator, and bracket classes such as [0-9] match one character from a set. Endpoint hosts accept only * and **, as described in Destination Fields.

Because query values and MCP tool names use . as the separator, * does not match a value that contains a dot. For example, 1.* matches 1.2 but not 1.2.3, and github.* matches github.search but not github.search.code. Use ** to match any value.

The endpoint path field, which selects among endpoints on the same host and port, uses different rules. An empty path, **, or /** matches every path, /v1/** matches /v1 and every path under it, and in other patterns * also matches /.

Full Example

version: 1
filesystem_policy:
include_workdir: true
read_only: [/usr, /lib, /etc]
read_write: [/tmp]
network_policies:
github_rest_api:
endpoints:
- host: api.github.com
port: 443
protocol: rest
enforcement: enforce
access: read-only
binaries:
- path: /usr/bin/gh
npm_registry:
endpoints:
- host: registry.npmjs.org
port: 443
protocol: rest
enforcement: enforce
access: read-only
allow_encoded_slash: true
binaries:
- path: /usr/bin/node