Skip to content

Commit 62da29d

Browse files
committed
docs(policy): document JSON-RPC L7 rules
Document JSON-RPC endpoint configuration, rpc_method and params matchers, batch denial behavior, current directionality limits, matcher scope, and the current policy update CLI limitation. Signed-off-by: Kris Hicks <khicks@nvidia.com>
1 parent 0d5e4e0 commit 62da29d

3 files changed

Lines changed: 120 additions & 10 deletions

File tree

architecture/sandbox.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ paths, such as proxy support files or GPU device paths when a GPU is present.
4949
All ordinary agent egress is routed through the sandbox proxy. The proxy
5050
identifies the calling binary, checks trust-on-first-use binary identity, rejects
5151
unsafe internal destinations, and evaluates the active policy.
52+
For inspected HTTP traffic, the proxy can enforce REST method/path rules,
53+
WebSocket upgrade and text-message rules, GraphQL operation rules, and
54+
JSON-RPC method and params rules on sandbox-to-server request bodies. JSON-RPC
55+
responses and server-to-client MCP messages on response or SSE streams are
56+
relayed but are not currently parsed for policy enforcement.
5257

5358
`https://inference.local` is special. It bypasses OPA network policy and is
5459
handled by the inference interception path:

docs/reference/policy-schema.mdx

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ Each endpoint defines a reachable destination and optional inspection rules.
155155
| `host` | string | Yes | Hostname or IP address. Supports a `*` wildcard inside the first DNS label only: `*.example.com`, `**.example.com`, and intra-label patterns like `*-aiplatform.googleapis.com` are accepted; bare `*`/`**`, TLD wildcards (`*.com`), and wildcards outside the first label are rejected at load time. |
156156
| `port` | integer | Yes | TCP port number. |
157157
| `path` | string | No | Optional HTTP path glob used to select between L7 endpoints that share the same host and port. Empty means all paths. Use this when REST and GraphQL live under the same host, such as `/repos/**` and `/graphql`. |
158-
| `protocol` | string | No | Set to `rest` for HTTP method/path inspection, `websocket` for RFC 6455 upgrade and client text-message inspection, or `graphql` for GraphQL-over-HTTP operation inspection. WebSocket endpoints can also use GraphQL operation rules for GraphQL-over-WebSocket traffic. Omit for TCP passthrough. |
158+
| `protocol` | string | No | Set to `rest` for HTTP method/path inspection, `websocket` for RFC 6455 upgrade and client text-message inspection, `graphql` for GraphQL-over-HTTP operation inspection, or `json-rpc` for sandbox-to-server JSON-RPC-over-HTTP method and params inspection. WebSocket endpoints can also use GraphQL operation rules for GraphQL-over-WebSocket traffic. Omit for TCP passthrough. |
159159
| `tls` | string | No | TLS handling mode. The proxy auto-detects TLS by peeking the first bytes of each connection and terminates it for inspected HTTPS traffic, so this field is optional in most cases. Set to `skip` to disable auto-detection for edge cases such as client-certificate mTLS or non-standard protocols. The values `terminate` and `passthrough` are deprecated and log a warning; they are still accepted for backward compatibility but have no effect on behavior. |
160160
| `enforcement` | string | No | `enforce` actively blocks disallowed requests. `audit` logs violations but allows traffic through. |
161161
| `access` | string | No | Access preset. One of `read-only`, `read-write`, or `full`. Mutually exclusive with `rules`. |
@@ -175,11 +175,13 @@ Credential rewrite recognizes the canonical `openshell:resolve:env:KEY` placehol
175175

176176
The `access` field accepts one of the following values:
177177

178-
| Value | REST expansion | WebSocket expansion | GraphQL expansion |
179-
|---|---|---|---|
180-
| `full` | All methods and paths. | WebSocket upgrade and all inspected client text-message paths. | All operation types. |
181-
| `read-only` | `GET`, `HEAD`, `OPTIONS`. | WebSocket upgrade handshake only. | `query` operations. |
182-
| `read-write` | `GET`, `HEAD`, `OPTIONS`, `POST`, `PUT`, `PATCH`. | WebSocket upgrade handshake and client text messages. | `query` and `mutation` operations. |
178+
| Value | REST expansion | WebSocket expansion | GraphQL expansion | JSON-RPC behavior |
179+
|---|---|---|---|---|
180+
| `full` | All methods and paths. | WebSocket upgrade and all inspected client text-message paths. | All operation types. | Allows matching HTTP requests without constraining JSON-RPC methods. |
181+
| `read-only` | `GET`, `HEAD`, `OPTIONS`. | WebSocket upgrade handshake only. | `query` operations. | Expands to HTTP read methods and does not allow typical JSON-RPC `POST` calls. |
182+
| `read-write` | `GET`, `HEAD`, `OPTIONS`, `POST`, `PUT`, `PATCH`. | WebSocket upgrade handshake and client text messages. | `query` and `mutation` operations. | Allows matching HTTP write requests without constraining JSON-RPC methods. |
183+
184+
For JSON-RPC endpoints, prefer explicit `rules` with `rpc_method` and optional `params` when you need method-level control.
183185

184186
#### Allow Rule Objects
185187

@@ -274,6 +276,40 @@ rules:
274276

275277
Do not combine `method`, `path`, or `query` with `operation_type`, `operation_name`, or `fields` inside the same WebSocket rule. When a WebSocket endpoint has GraphQL operation policy, use GraphQL rules for client messages instead of a raw `WEBSOCKET_TEXT` allow rule.
276278

279+
##### JSON-RPC Allow Rule (`protocol: json-rpc`)
280+
281+
JSON-RPC allow rules match sandbox-to-server JSON-RPC-over-HTTP request objects by RPC method and optional params. They apply to single JSON-RPC requests and batch requests. For a batch, OpenShell evaluates each call independently. JSON-RPC responses and server-to-client messages on response bodies or MCP SSE streams are relayed but are not currently parsed for policy enforcement.
282+
283+
| Field | Type | Required | Description |
284+
|---|---|---|---|
285+
| `rpc_method` | string | Yes | JSON-RPC method name or glob, such as `initialize`, `tools/list`, or `tools/*`. |
286+
| `params` | map | No | Params matchers keyed by flattened object-param path. Use dot-separated keys for nested object params, such as `arguments.scope`. Matcher value can be a glob string or an object with `any`. Strings, numbers, and booleans are converted to strings; arrays, `null`, and non-object top-level params do not produce matcher keys. |
287+
288+
Example JSON-RPC allow rules:
289+
290+
```yaml showLineNumbers={false}
291+
endpoints:
292+
- host: mcp.example.com
293+
port: 443
294+
path: /mcp
295+
protocol: json-rpc
296+
enforcement: enforce
297+
rules:
298+
- allow:
299+
rpc_method: initialize
300+
- allow:
301+
rpc_method: tools/list
302+
- allow:
303+
rpc_method: tools/call
304+
params:
305+
name: read_status
306+
- allow:
307+
rpc_method: tools/call
308+
params:
309+
name: submit_report
310+
arguments.scope: workspace/main
311+
```
312+
277313
#### Deny Rule Objects
278314

279315
Blocks specific operations on endpoints that otherwise have broad access. Deny rules are evaluated after allow rules and take precedence: if a request matches any deny rule, it is blocked regardless of what the allow rules or access preset permit.
@@ -356,6 +392,33 @@ endpoints:
356392
operation_name: Admin*
357393
```
358394

395+
##### JSON-RPC Deny Rule (`protocol: json-rpc`)
396+
397+
JSON-RPC deny rules use the same field names as JSON-RPC allow rules, but they appear directly under each `deny_rules` entry instead of under an `allow` wrapper. Deny rules take precedence over allow rules. In a batch request, one denied call denies the full batch.
398+
399+
| Field | Type | Required | Description |
400+
|---|---|---|---|
401+
| `rpc_method` | string | Yes | JSON-RPC method name or glob to deny. |
402+
| `params` | map | No | Params matchers keyed by flattened object-param path. Omit to deny every call matching `rpc_method`. Strings, numbers, and booleans are converted to strings; arrays, `null`, and non-object top-level params do not produce matcher keys. |
403+
404+
Example JSON-RPC deny rules:
405+
406+
```yaml showLineNumbers={false}
407+
endpoints:
408+
- host: mcp.example.com
409+
port: 443
410+
path: /mcp
411+
protocol: json-rpc
412+
enforcement: enforce
413+
rules:
414+
- allow:
415+
rpc_method: tools/*
416+
deny_rules:
417+
- rpc_method: tools/call
418+
params:
419+
name: delete_resource
420+
```
421+
359422
### Binary Object
360423

361424
Identifies an executable that is permitted to use the associated endpoints.

docs/sandboxes/policies.mdx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ The following steps outline the hot-reload policy update workflow.
148148

149149
To inspect a stored sandbox-authored revision instead of the current effective policy, pass `--rev <version>`.
150150

151-
5. Edit the YAML: add or adjust `network_policies` entries, binaries, `access`, or `rules`.
151+
5. Edit the YAML: add or adjust `network_policies` entries, binaries, `access`, `rules`, or protocol-specific matchers such as GraphQL operation fields and JSON-RPC `rpc_method` / `params` rules.
152152

153153
6. Push the updated policy when you need a full replacement. Exit codes: 0 = loaded, 1 = validation failed, 124 = timeout.
154154

@@ -173,7 +173,7 @@ Use `openshell policy update` when you want to merge network policy changes into
173173
- remove one endpoint or one named rule without rewriting the rest of the file.
174174
- preview a merged result locally with `--dry-run` before you send it to the gateway.
175175

176-
Use `openshell policy set` instead when you want to replace the full policy, update static sections, or make broader edits that are easier to express in YAML.
176+
Use `openshell policy set` instead when you want to replace the full policy, update static sections, or make broader edits that are easier to express in YAML. Use full YAML for GraphQL and JSON-RPC rule shapes.
177177

178178
### Update Commands
179179

@@ -210,6 +210,7 @@ This is the practical difference:
210210
Current constraints:
211211

212212
- `--add-allow` and `--add-deny` work on `protocol: rest` and `protocol: websocket` endpoints.
213+
- GraphQL and JSON-RPC fine-grained rules require full policy YAML applied with `openshell policy set`.
213214
- `--add-deny` requires the endpoint to already have an allow base, either an `access` preset or explicit allow `rules`.
214215
- `protocol: sql` is not a practical incremental workflow today. OpenShell does not do full SQL parsing, and SQL enforcement is not meaningfully supported yet.
215216

@@ -228,7 +229,7 @@ Each segment has a fixed meaning:
228229
| `host` | Yes | Destination hostname. |
229230
| `port` | Yes | Destination port, `1` through `65535`. |
230231
| `access` | No | Access preset for L7 endpoints: `read-only`, `read-write`, or `full`. Incremental updates expand presets into protocol-specific method/path rules for REST and WebSocket endpoints. |
231-
| `protocol` | No | L7 inspection mode: `rest`, `websocket`, or `sql`. `sql` is audit-only and not a recommended workflow today. |
232+
| `protocol` | No | L7 inspection mode accepted by `openshell policy update`: `rest`, `websocket`, or `sql`. `sql` is audit-only and not a recommended workflow today. Full policy YAML also supports `graphql` and `json-rpc`. |
232233
| `enforcement` | No | Enforcement mode for inspected traffic: `enforce` or `audit`. |
233234
| `options` | No | Comma-separated endpoint options. Use `websocket-credential-rewrite` with `protocol: websocket` or REST compatibility endpoints that perform a WebSocket upgrade. Use `request-body-credential-rewrite` only with `protocol: rest`. |
234235

@@ -548,7 +549,7 @@ For an end-to-end walkthrough that combines this policy with a GitHub credential
548549
- { path: /usr/bin/gh }
549550
```
550551

551-
Endpoints with `protocol: rest` enable HTTP request inspection and can opt in to supported text request body credential rewrite. Endpoints with `protocol: websocket` validate WebSocket upgrades and inspect client text messages on the upgraded request path. WebSocket endpoints can also classify GraphQL-over-WebSocket operation messages with the same operation rules used by GraphQL-over-HTTP. Endpoints with `protocol: graphql` parse GraphQL-over-HTTP payloads before evaluating rules. The endpoint-level `path` field lets these protocols share `api.github.com:443` without treating GraphQL payloads as plain REST `POST /graphql` requests.
552+
Endpoints with `protocol: rest` enable HTTP request inspection and can opt in to supported text request body credential rewrite. Endpoints with `protocol: websocket` validate WebSocket upgrades and inspect client text messages on the upgraded request path. WebSocket endpoints can also classify GraphQL-over-WebSocket operation messages with the same operation rules used by GraphQL-over-HTTP. Endpoints with `protocol: graphql` parse GraphQL-over-HTTP payloads before evaluating rules. Endpoints with `protocol: json-rpc` parse JSON-RPC-over-HTTP request bodies and evaluate `rpc_method` and optional params rules. The endpoint-level `path` field lets these protocols share `api.github.com:443` without treating GraphQL payloads as plain REST `POST /graphql` requests.
552553
</Tab>
553554

554555
</Tabs>
@@ -579,6 +580,47 @@ REST rules can also constrain query parameter values:
579580

580581
`query` matchers are case-sensitive and run on decoded values. If a request has duplicate keys (for example, `tag=a&tag=b`), every value for that key must match the configured glob(s).
581582

583+
### JSON-RPC matching
584+
585+
JSON-RPC endpoints use `protocol: json-rpc`. The proxy parses sandbox-to-server JSON-RPC-over-HTTP request bodies, evaluates the `method` field against `rpc_method`, and can match object params through dot-separated `params` keys.
586+
587+
JSON-RPC policy enforcement is directional. It applies to HTTP request bodies sent by the sandboxed process to the configured endpoint. JSON-RPC responses and server-to-client messages carried on response bodies or MCP SSE streams are relayed but are not currently parsed for policy enforcement.
588+
589+
JSON-RPC endpoint policies currently require full policy YAML applied with `openshell policy set`; the incremental `openshell policy update --add-endpoint` parser does not accept `json-rpc` as a protocol.
590+
591+
```yaml showLineNumbers={false}
592+
mcp_server:
593+
name: mcp_server
594+
endpoints:
595+
- host: mcp.example.com
596+
port: 443
597+
path: /mcp
598+
protocol: json-rpc
599+
enforcement: enforce
600+
rules:
601+
- allow:
602+
rpc_method: initialize
603+
- allow:
604+
rpc_method: tools/list
605+
- allow:
606+
rpc_method: tools/call
607+
params:
608+
name: read_status
609+
- allow:
610+
rpc_method: tools/call
611+
params:
612+
name: submit_report
613+
arguments.scope: workspace/main
614+
deny_rules:
615+
- rpc_method: tools/call
616+
params:
617+
name: delete_resource
618+
binaries:
619+
- { path: /usr/bin/python3 }
620+
```
621+
622+
`params` matchers are case-sensitive and use the same string glob or `{ any: [...] }` matcher syntax as REST query parameters. They match scalar leaf values from object params: strings, numbers, and booleans are converted to strings, and nested JSON object params are flattened with dot-separated keys before matching. Arrays, `null`, and non-object top-level params do not produce matcher keys. This is useful for controls such as matching MCP `tools/call` by `params.name`, but it is not a complete MCP payload policy for rich nested content. For batch requests, OpenShell evaluates each JSON-RPC call independently and denies the whole batch if any call is denied.
623+
582624
### GraphQL matching
583625

584626
GraphQL endpoints use `protocol: graphql`. The proxy parses GraphQL-over-HTTP `GET` and `POST` requests, classifies each operation, and evaluates rules against the operation type, optional operation name, and selected root fields.

0 commit comments

Comments
 (0)