11---
22# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33# SPDX-License-Identifier: Apache-2.0
4- title : " Standalone Policy Prover"
4+ title : " Policy Prover"
55sidebar-title : " Prover"
6- description : " Install and use the standalone OpenShell policy prover to check a local candidate policy against a managed boundary ."
7- keywords : " Generative AI, Cybersecurity, Policy, Prover, Boundary, Containment, CI"
6+ description : " Check that a policy grants no more access than a boundary policy you define, for example before you apply a change or in CI ."
7+ keywords : " Generative AI, Cybersecurity, Policy, Prover, Boundary, CI"
88position : 8
99---
1010
11- ` openshell-prover ` checks whether the authority in a local candidate policy is
12- contained within a local boundary policy. The command reads files from the host
13- and does not connect to an OpenShell gateway.
11+ The policy prover checks that a policy grants no more access than a limit you
12+ set. You write the limit as a second policy, called the boundary. The prover
13+ determines whether everything allowed by the policy you are testing, called the
14+ candidate, is also allowed by the boundary. If the candidate allows something
15+ the boundary does not, the prover shows an example.
1416
15- The candidate must be the fully composed effective policy after the proposed
16- change, including any provider-contributed authority. The boundary is an
17- operator-owned ceiling. It does not grant authority by itself.
17+ The prover checks only some parts of a policy: filesystem access, process
18+ identity, Landlock settings, network destinations, and REST methods and paths.
19+ If a policy uses anything else, such as GraphQL or MCP rules, the prover reports
20+ that it cannot check the policy instead of ignoring those rules.
21+
22+ Use the prover to check a policy before you apply it, for example in CI, so
23+ that no change grants more than your organization allows. The prover is a
24+ separate command, ` openshell-prover ` , that reads policy files on your machine.
25+ It does not connect to a gateway, and it does not apply or approve policies.
1826
1927The [ policy advisor] ( /sandboxes/policy-advisor#what-the-prover-checks ) runs a
20- separate prover check automatically on each proposal to find whether the
21- proposal adds new access compared with the current policy. Use this command when
22- you need to check a complete policy against a fixed boundary, for example in
23- CI.
28+ different prover check on each proposal. That check compares the proposal with
29+ the sandbox's current policy instead of a boundary you write.
2430
2531## Install the Prover
2632
27- The standard OpenShell installer includes ` openshell-prover ` :
33+ The OpenShell installer includes ` openshell-prover ` :
2834
2935``` shell
3036curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
3137openshell-prover --version
3238```
3339
34- The prover remains independent of the gateway at runtime. If you only need the
35- standalone binary, use the artifacts listed in the
36- [ Support Matrix] ( /reference/support-matrix#standalone-policy-prover ) . These
37- artifacts and ` openshell-prover-checksums-sha256.txt ` are attached to
38- [ OpenShell releases] ( https://github.com/NVIDIA/OpenShell/releases ) .
39-
40- The release archive includes the solver linkage required by the executable.
41- It does not require the main ` openshell ` command, a gateway configuration, or
42- a separate Z3 installation.
40+ To install only the prover, download its archive from
41+ [ OpenShell releases] ( https://github.com/NVIDIA/OpenShell/releases ) . The
42+ [ Support Matrix] ( /reference/support-matrix#standalone-policy-prover ) lists the
43+ available platforms. The prover runs on its own, without the ` openshell ` CLI, a
44+ gateway, or a separate Z3 installation.
4345
44- ## Check a Policy Boundary
46+ ## Check a Policy
4547
46- Create ` boundary.yaml ` with this boundary policy :
48+ Create ` boundary.yaml ` , a boundary that allows reading ` /usr ` and ` /etc ` :
4749
4850``` yaml
4951version : 1
@@ -53,7 +55,7 @@ filesystem_policy:
5355 - /etc
5456` ` `
5557
56- Create ` candidate.yaml` with this contained candidate :
58+ Create ` candidate.yaml`, a candidate that allows reading only `/usr` :
5759
5860` ` ` yaml
5961version: 1
@@ -62,16 +64,22 @@ filesystem_policy:
6264 - /usr
6365` ` `
6466
65- Run the check :
67+ Check the candidate against the boundary :
6668
6769` ` ` shell
6870openshell-prover check candidate.yaml --boundary boundary.yaml
6971` ` `
7072
71- A contained candidate prints `result : within_boundary` and exits with status `0`.
72- To see an exceeding result and its counterexample, replace `candidate.yaml`
73- with a policy that grants writes to `/tmp`. The boundary grants no write
74- access :
73+ The candidate allows less than the boundary, so the check passes :
74+
75+ ` ` ` text
76+ result: within_boundary
77+ coverage: domains=filesystem,network_l4,network_rest,process,landlock
78+ ` ` `
79+
80+ The `coverage` line lists the parts of the policy that the prover checked. Now
81+ change the candidate so that it also allows writing to `/tmp`, which the
82+ boundary does not allow :
7583
7684` ` ` yaml
7785version: 1
@@ -82,196 +90,92 @@ filesystem_policy:
8290 - /tmp
8391` ` `
8492
85- ` ` ` shell
86- openshell-prover check candidate.yaml --boundary boundary.yaml
93+ Run the same command again. The check fails, and the `counterexample` line
94+ shows access that the candidate allows but the boundary does not :
95+
96+ ` ` ` text
97+ result: exceeds_boundary
98+ coverage: domains=filesystem,network_l4,network_rest,process,landlock
99+ counterexample: filesystem write /tmp
87100` ` `
88101
89- To check the current effective policy of an existing sandbox, export it without
90- display metadata :
102+ # ## Check a Sandbox's Policy
103+
104+ To check the policy that a sandbox enforces, save its effective policy and use
105+ it as the candidate :
91106
92107` ` ` shell
93108openshell sandbox get my-sandbox --policy-only > candidate.yaml
94109openshell-prover check candidate.yaml --boundary boundary.yaml
95110` ` `
96111
97- This export includes provider-contributed rules. For a proposed change that is
98- not active, supply the complete post-change effective policy. The standalone
99- prover does not compose a base policy with provider rules.
100-
101- Use JSON when a script consumes the result :
112+ The effective policy includes rules from attached providers, so the check covers
113+ everything the sandbox can reach. The prover does not add provider rules itself.
114+ To check a change before you apply it, give the prover the complete effective
115+ policy as it would be after the change.
102116
103- ` ` ` shell
104- openshell-prover check candidate.yaml \
105- --boundary boundary.yaml \
106- --output json
107- ` ` `
117+ # # Read the Result
108118
109- The JSON object includes the result, exit code, input paths, schema version,
110- prover version, and modeled domains. `schema_version` versions the JSON contract,
111- ` prover_version` identifies the implementation that produced the result, and
112- ` coverage.domains` is the machine-readable declaration of modeled policy
113- domains. An exceeding result also includes a typed counterexample. Automation
114- should use `result` and `reason_code` instead of parsing the human-readable
115- explanation.
116-
117- | Field | When populated |
118- |---|---|
119- | `counterexample` | An object for `exceeds_boundary`; otherwise `null`. |
120- | `reason_code` | A stable identifier for an emitted `error`, `unsupported`, or `inconclusive` result; otherwise `null`. |
121- | `reason` | A human-readable explanation paired with `reason_code`; otherwise `null`. |
122-
123- The `counterexample.domain` field selects one of these objects :
124-
125- | Domain | Fields |
126- |---|---|
127- | `filesystem` | `access` (`read` or `write`) and `path`. |
128- | `process` | `field` (`run_as_user` or `run_as_group`), `boundary`, and `candidate`. |
129- | `landlock` | `boundary` and `candidate` compatibility modes. |
130- | `network` | `binary`, `ancestor_binary`, `binary_identity_required`, `host`, `destination_ip`, `trusted_gateway`, `port`, `protocol`, `method`, and `path`. |
131-
132- Network `binary` and `ancestor_binary` values are `null` when binary identity
133- enforcement is disabled. `method` and `path` are `null` for L4 witnesses.
134- `trusted_gateway : true` means the witness uses a recognized host-gateway alias
135- with a runtime-provided trusted gateway binding; `false` uses ordinary
136- destination validation.
137-
138- The stable reason codes are `invalid_input`, `unsupported_policy_shape`,
139- ` unresolved_workdir` , `unresolved_binary_path`,
140- ` unresolved_filesystem_path` , `solver_timeout`, `solver_unknown`,
141- ` resource_limit` , `invalid_witness`, and `cancelled`.
142-
143- The solver has a finite 10-second default budget. Set a different positive
144- budget with an integer followed by `ms`, `s`, or `m` :
119+ The prover reports one of these results :
145120
146- ` ` ` shell
147- openshell-prover check candidate.yaml \
148- --boundary boundary.yaml \
149- --timeout 30s \
150- --output json
121+ | Result | Exit code | Meaning |
122+ |---|---|---|
123+ | `within_boundary` | `0` | The candidate allows nothing beyond the boundary in the parts of the policy that the prover checks. |
124+ | `exceeds_boundary` | `1` | The candidate allows access that the boundary does not. The counterexample shows one example. |
125+ | `error` | `2` | The prover could not run the check, for example because a file is missing or a policy is invalid. |
126+ | `unsupported` | `3` | A policy uses something the prover cannot check. The reason explains what. |
127+ | `inconclusive` | `3` | The prover could not finish, for example because it ran out of time or the policies are too large. |
128+
129+ Only `within_boundary` means that the check passed. In CI, treat every other
130+ result as a failure. For example, a candidate with a GraphQL rule returns :
131+
132+ ` ` ` text
133+ result: unsupported
134+ coverage: domains=filesystem,network_l4,network_rest,process,landlock
135+ reason: candidate policy rule 'g' uses protocol 'graphql'; only L4 TCP and REST are modeled
151136` ` `
152137
153- # # Exit Codes
154-
155- | Exit | Result | Meaning |
156- |---|---|---|
157- | `0` | `within_boundary` | Containment was established for the reported modeled domains. |
158- | `1` | `exceeds_boundary` | The candidate exceeds the boundary; inspect the counterexample. |
159- | `2` | `error` | Arguments, input files, policy syntax, or command execution prevented a valid check. |
160- | `3` | `unsupported` or `inconclusive` | The model cannot soundly cover the policy shape, or the solver did not reach a determination. |
161- | `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. |
162-
163- Only exit `0` means the verification succeeded. Treat unsupported and
164- inconclusive results as failures in CI.
165-
166- # # Interpretation and Limits
167-
168- The containment check covers filesystem paths, process identity settings,
169- Landlock compatibility requirements, L4 destination authority, and enforced
170- REST method and path authority, including explicit REST denies. The result
171- object reports the policy domains modeled by each check. Policies that use
172- recognized authority outside that coverage return `unsupported` rather than
173- silently ignoring it.
174-
175- Both inputs use the same bounded YAML/JSON parser and authored policy schema as
176- OpenShell. Unknown fields, duplicate keys, malformed field types, and unsupported
177- managed `metadata` or `review` annotations return `error` with
178- `reason_code : invalid_input` and exit `2`. Schema-valid controls outside the
179- containment model return `unsupported` and exit `3`.
180-
181- The prover applies aggregate limits across the candidate and boundary before
182- semantic shape validation : 1,024 network rules, 4,096 endpoints, 4,096 binary
183- selectors, 65,536 authored port entries, 4,096 `allowed_ips` entries, 16,384
184- REST rules, 4 KiB per modeled pattern, and 1 MiB of modeled pattern text.
185- Exceeding any limit returns `inconclusive` with `reason_code : resource_limit`.
186- A cancellation already requested at preflight takes precedence over that
187- result; otherwise a resource limit takes precedence over unsupported
188- policy-shape diagnostics. This ordering keeps validation work bounded for
189- checked-in CI inputs.
190-
191- # ## Process and Landlock settings
192-
193- Matching supported `run_as_user` and `run_as_group` values do not expand the
194- configuration. Changing an explicit non-root identity to `root` or `0` returns
195- ` exceeds_boundary` with the field and both values. Other identity changes return
196- ` unsupported` ; the command does not resolve accounts from a sandbox image.
197- These comparisons assume consistent identity resolution and execution settings.
198- They do not prove permission relationships between arbitrary Linux accounts.
199-
200- Landlock `hard_requirement` may not become `best_effort`. That change returns
201- ` exceeds_boundary` . Keeping the same mode or strengthening it to
202- ` hard_requirement` passes this part of the check. This compares the requested
203- requirement, not whether a target kernel successfully installed Landlock
204- restrictions.
205-
206- # ## Destination IP restrictions
207-
208- The network action includes an IPv4 or IPv6 destination address. CIDR entries
209- in `allowed_ips` are modeled together with host, port, executable, and REST
210- restrictions; network counterexamples include `destination_ip`. An empty IP
211- list follows the runtime's destination rules and is not a universal allowlist.
212- The check does not resolve DNS on the host running the CLI.
213-
214- Policies whose potentially overlapping endpoints select different destination
215- restrictions remain unsupported because the runtime uses the selected
216- endpoint's address filter. The prover conservatively treats wildcard host
217- selectors on a shared port as potentially overlapping, including when their
218- literal suffixes differ. CIDR unions within a supported endpoint are checked by
219- the solver. Unsupported IP-literal wildcard selectors and ranges rejected by
220- the runtime also cannot produce a successful proof.
221-
222- Consumers must inspect `coverage.domains` and require every domain relevant to
223- their authorization decision. A successful result applies only to those
224- reported domains and the documented assumptions.
225-
226- # ## Remaining limits
227-
228- Network binary selectors, endpoint host and path selectors, and REST allow and
229- deny method and path selectors must use ASCII literals in both the candidate and
230- boundary. A non-ASCII literal in one of these fields returns `unsupported` with
231- `reason_code : unsupported_policy_shape`, including when it appears only in a
232- deny. Embedded NUL bytes in these fields are also unsupported. This is a
233- prover-model limitation, not a general policy validation rule : filesystem paths
234- and unrelated policy text retain their existing Unicode behavior. ASCII
235- wildcard selectors still cover non-ASCII runtime values matched by the policy
236- engine.
237-
238- Containment means `Allowed(candidate)` is a subset of `Allowed(boundary)` under
239- the reported model. It does not establish least privilege, automatic approval
240- eligibility, semantic safety, or equivalence to a running sandbox's kernel
241- state. In particular :
242-
243- - The command does not fetch the current sandbox policy or compose provider
244- rules. The caller must supply the effective candidate.
245- - The command does not apply, approve, or persist a policy.
246- - Environment-dependent authority, such as an unresolved image workdir,
247- returns `unsupported` when the result depends on that missing context.
248- - Binary comparisons that can change when an image resolves an exact selector
249- through a symlink return `unsupported`. This covers exact candidate grants
250- under boundary globs and exact boundary denies replaced by candidate globs.
251- Use matching exact canonical paths when possible.
252- - Policies with overlapping L4 and enforced REST endpoints return
253- ` unsupported` because inspection selection depends on the complete set of
254- matching runtime endpoint configurations.
255- - Network containment checks both supported runtime configurations : binary
256- identity enforcement enabled and disabled. When enabled, grants and denies
257- match the executable or an ancestor identity. Network counterexamples report
258- the configuration and identities that expose the additional authority.
259- - REST containment witnesses use canonical request methods and paths.
260- - If a solver string cannot be decoded and validated faithfully, the command
261- returns `inconclusive` with `reason_code : invalid_witness` instead of emitting
262- a counterexample.
263- - Both files are interpreted in the same sandbox filesystem namespace and
264- mount model, with stable path resolution when enforcement rules are created.
265- The CLI does not resolve sandbox paths against the host.
266- - Filesystem containment supports removing grants and reducing write grants to
267- read-only grants at matching paths. A boundary grant for `/` also covers other
268- paths for the same access. Comparisons between different paths otherwise
269- return `unsupported` with `reason_code : unresolved_filesystem_path`: a lexical
270- child can resolve outside its parent through a symlink, and unrelated paths
271- can resolve to the same object. This includes narrowing `/tmp` to `/tmp/cache`.
272- If the boundary grants no access of the requested kind, adding that access
273- returns `exceeds_boundary`.
274-
275- Use the [Policy Schema Reference](/reference/policy-schema) for the full policy
276- language. A successful prover result covers only the policy domains reported in
277- its evidence.
138+ For scripts, add `--output json`. Read the `result` and `reason_code` fields
139+ instead of parsing the text output. `reason_code` is a stable identifier, such
140+ as `unsupported_policy_shape` or `solver_timeout`. Also check that
141+ ` coverage.domains` includes every part of the policy that you rely on. Run
142+ ` openshell-prover check --help` for all options, including `--timeout`, which
143+ changes the default 10-second time limit.
144+
145+ # # What a Passing Result Means
146+
147+ A passing result means that the candidate allows nothing beyond the boundary in
148+ the parts of the policy that the prover checks. It does not mean that the policy
149+ is as narrow as it could be, that it is safe for a particular task, or that a
150+ running sandbox enforces it.
151+
152+ The prover reports `unsupported` instead of guessing when the answer depends on
153+ something it cannot see or does not check :
154+
155+ - Network rules other than connection rules and REST rules, such as WebSocket,
156+ GraphQL, MCP, and JSON-RPC rules.
157+ - Filesystem comparisons between different paths, such as narrowing `/tmp` to
158+ ` /tmp/cache` . A symlink in the sandbox image could make the paths refer to
159+ different locations. Removing a path, or changing it from read-write to
160+ read-only, is supported.
161+ - Binary rules that compare an exact path with a glob, because a symlink in the
162+ sandbox image could change which executable the path refers to. Use matching
163+ exact paths when you can.
164+ - Settings that depend on the sandbox image, such as the working directory.
165+ - Endpoints for the same host and port that set different `allowed_ips`, or
166+ that mix connection-only and REST rules.
167+ - Process identity changes other than changing a non-root user or group to
168+ root. The prover reports a change to root as exceeding the boundary.
169+ - Host, path, method, or binary values that contain non-ASCII characters.
170+
171+ The prover also handles these cases in specific ways :
172+
173+ - Changing Landlock from `hard_requirement` to `best_effort` exceeds the
174+ boundary. The prover compares the settings, not what a kernel enforces.
175+ - The prover checks network rules both with and without binary identity
176+ enforcement, because a sandbox runtime can turn it off.
177+ - The prover does not resolve hostnames on your machine. It checks
178+ ` allowed_ips` ranges together with hosts, ports, binaries, and REST rules.
179+ - Very large policies, for example with more than 1,024 network rules or 4,096
180+ endpoints across both files, return `inconclusive` with the `reason_code`
181+ ` resource_limit` .
0 commit comments