Use Policy Advisor

View as Markdown

Policy advisor lets a running sandboxed agent ask for a narrow network policy change after OpenShell denies a request. The agent submits a draft through policy.local, a developer approves or rejects it from outside the sandbox, and approved network policy hot-reloads into the same sandbox.

Policy advisor preserves OpenShell’s default-deny posture. The structured rule is the approval contract, and the agent’s rationale is supporting context. By default every proposal lands in the draft inbox for human review. Opt-in auto mode lets the gateway approve provably safe proposals — those whose prover delta is empty — without a reviewer in the loop; proposals with any prover finding still require human approval.

Enable Policy Advisor

Policy advisor is disabled by default. Enable it globally when you want every sandbox on the selected gateway to expose the agent proposal surface:

$openshell settings set --global \
> --key agent_policy_proposals_enabled \
> --value true \
> --yes

You can also enable it for one sandbox, unless the key is managed globally:

$openshell settings set <sandbox-name> \
> --key agent_policy_proposals_enabled \
> --value true

Check the effective setting for a sandbox:

$openshell settings get <sandbox-name>

The output shows whether agent_policy_proposals_enabled is global, sandbox, or unset. A global value overrides sandbox-scoped values. To return control to sandbox-scoped settings, delete the global key:

$openshell settings delete --global \
> --key agent_policy_proposals_enabled \
> --yes

Set the value before creating a sandbox when you want the first denied request to include policy advisor guidance. Running sandboxes poll settings and can enable the surface after startup, but startup enablement gives the agent the clearest first-denial path.

Approval Modes

Every proposal — mechanistic or agent-authored — is routed through the policy prover. The proposal_approval_mode setting decides what happens when the prover finds nothing to flag.

ModeWhen unset / manualauto
Empty prover deltaLands in the draft inbox for human review.Approved automatically; the sandbox hot-reloads the new rule and the agent retries.
Any prover findingLands in the draft inbox.Lands in the draft inbox — auto-approval is gated on an empty delta.

manual is the default. Auto mode is an explicit opt-in; OpenShell’s default-deny posture is preserved unless you choose otherwise.

Enable auto mode at gateway scope when you want every sandbox on this gateway to auto-approve safe proposals:

$openshell settings set --global \
> --key proposal_approval_mode \
> --value auto \
> --yes

Enable it for one sandbox when no global value is set:

$openshell settings set <sandbox-name> \
> --key proposal_approval_mode \
> --value auto

The shorthand at create time writes the sandbox-scoped setting for you:

$openshell sandbox create --approval-mode auto <name>

Only manual and auto are accepted; typos like autom are rejected at configure time. Stale or unknown values found in storage are still treated as manual at runtime as a defense-in-depth measure.

Precedence. Gateway scope wins over sandbox scope. A reviewer can pin manual for a fleet by setting it globally; per-sandbox overrides only apply when no global value is set.

Audit trail. Every auto-approval emits a CONFIG:APPROVED event with auto=true, source=<mechanistic|agent_authored>, prover_delta=empty, and resolved_from=<gateway|sandbox|default> so operators can reconstruct why a given approval ran without human review.

How It Works

When policy advisor is enabled, the sandbox supervisor turns on three agent-facing surfaces:

  • It installs /etc/openshell/skills/policy_advisor.md inside the sandbox.
  • It also installs /etc/openshell/skills/policy-advisor/SKILL.md as a short Codex/generic-agent pointer, and writes a root /AGENTS.md pointer only when the image does not already provide one.
  • It serves http://policy.local from inside the sandbox.
  • It adds agent_guidance and next_steps to L7 policy_denied response bodies so the agent can find the skill and local API.

The loop has seven steps:

  1. A sandboxed process attempts a network request that policy denies.
  2. For inspected REST traffic, OpenShell returns a structured 403 body with fields such as layer, host, port, binary, method, path, rule_missing, agent_guidance, and next_steps.
  3. The agent reads the policy advisor skill, inspects the current policy, and optionally reads recent denial log lines.
  4. The agent submits one or more addRule proposals to http://policy.local/v1/proposals.
  5. The gateway stores accepted proposals as pending draft chunks for the sandbox and runs the policy prover against the proposed delta.
  6. Under auto mode, proposals with an empty prover delta are approved immediately and skipped past human review. Under manual mode (the default), every proposal — and under auto mode, every proposal with a prover finding — lands in the draft inbox for a developer to approve or reject.
  7. The agent waits on /v1/proposals/{chunk_id}/wait until a decision is available. Approved proposals hot-reload into the sandbox; rejected proposals return rejection_reason and validation_result so the agent can revise.

When a proposal is approved, /wait reports policy_reloaded: true only after the local sandbox policy covers the approved rule. At that point the agent can retry the original denied action once. If a proposal is rejected, /wait returns rejection_reason and validation_result so the agent can revise or stop. validation_result carries the categorical prover findings — link_local_reach, l7_bypass_credentialed, credential_reach_expansion, capability_expansion — so the agent can narrow the next attempt to the specific concern the prover flagged.

What Gets Proposed

OpenShell has two proposal paths:

PathSourceTypical rule shape
Mechanistic mapperAggregated denial summaries from the sandbox.Groups by host, port, and binary. If L7 request samples are available, it can draft REST method and path rules. Otherwise it drafts an L4 endpoint.
Agent-authored proposalThe in-sandbox agent, using policy.local.Usually a REST addRule with exact host, port, binary, method, and path from the structured denial. It can also propose L4 rules for opaque protocols.

For REST APIs, prefer L7 rules over broad L4 access. A good proposal allows one method and the smallest safe path:

1{
2 "intent_summary": "Allow gh to update documentation in NVIDIA/OpenShell.",
3 "operations": [
4 {
5 "addRule": {
6 "ruleName": "github_contents_docs_write",
7 "rule": {
8 "name": "github_contents_docs_write",
9 "endpoints": [
10 {
11 "host": "api.github.com",
12 "port": 443,
13 "protocol": "rest",
14 "enforcement": "enforce",
15 "rules": [
16 {
17 "allow": {
18 "method": "PUT",
19 "path": "/repos/NVIDIA/OpenShell/contents/docs/**"
20 }
21 }
22 ]
23 }
24 ],
25 "binaries": [
26 {
27 "path": "/usr/bin/gh"
28 }
29 ]
30 }
31 }
32 }
33 ]
34}

The current policy.local JSON shape covers L4 endpoints and REST method or path rules. Use Customize Sandbox Policies or Policy Schema Reference for policy fields that are not part of the agent-authored proposal surface, such as WebSocket credential rewrite, GraphQL operation matching, endpoint path scoping, and provider-owned policy bundles.

Policy advisor proposals do not add allowed_ips automatically. If an advisor-proposed hostname resolves to an internal or private address, OpenShell’s SSRF protections still block the connection until a developer explicitly adds the required allowed_ips entry. Exact hostname trust for user-declared policy endpoints does not apply to advisor-generated proposal binaries.

What Auto-Approval Checks

The policy prover runs against every proposal — mechanistic and agent-authored alike — and asks four formal questions about the proposed change. Each “yes” is one categorical finding. Any finding blocks auto-approval; only an empty delta is eligible.

CategoryTriggered when
link_local_reachThe proposal reaches a host in 169.254.0.0/16, fe80::/10, or a known metadata hostname such as metadata.google.internal (cloud-metadata territory, which serves credentials regardless of sandbox state). Unconditional.
l7_bypass_credentialedA binary using a wire protocol the L7 proxy cannot inspect (git-remote-https, ssh, nc) gains reach to a host where a credential is in scope.
credential_reach_expansionA binary gains credentialed reach to a (host, port) it could not reach before.
capability_expansionOn a (binary, host, port) that already had credentialed reach, the proposal adds a new HTTP method. The finding cites the specific method.

Findings are categorical — there is no severity tier. The reviewer reads the category and the structured evidence to decide. When the prover delta is empty, the proposal is provably safe under the model and auto-approval (if enabled) can fire.

The full reasoning model lives in crates/openshell-prover/README.md. Provider profiles composed in via Providers v2 are part of the effective policy the prover reasons over.

Review Proposals

Review pending chunks from the host:

$openshell rule get <sandbox-name> --status pending

Under auto mode, only proposals the prover flagged appear here; empty-delta proposals are already approved and visible under --status approved with the auto-approval audit fields described in Approval Modes. Under manual mode, every proposal — regardless of prover verdict — shows up as pending.

The output shows the chunk ID, status, rationale, binary, and endpoint summary. For L7 proposals, the endpoint summary includes the protocol, method, and path:

Endpoints: api.github.com:443 [L7 rest, allow PUT /repos/NVIDIA/OpenShell/contents/docs/**]

Approve only when the structured rule matches the access you intend to grant:

$openshell rule approve <sandbox-name> --chunk-id <chunk-id>

Reject with guidance when the rule is too broad or points at the wrong target:

$openshell rule reject <sandbox-name> \
> --chunk-id <chunk-id> \
> --reason "Scope this to docs/ paths only."

The rejection reason is returned to the agent through policy.local. The agent can use it to draft a narrower proposal.

Agent API

policy.local is available only inside the sandbox and uses plain HTTP:

EndpointPurpose
GET /v1/policy/currentReturns the current effective sandbox policy as YAML.
GET /v1/denials?last=10Returns recent denied OCSF shorthand log lines, newest first. Query strings are redacted before lines are returned to the agent.
POST /v1/proposalsSubmits addRule operations. The response includes accepted_chunk_ids and rejection_reasons.
GET /v1/proposals/{chunk_id}Returns one proposal’s current pending, approved, or rejected status.
GET /v1/proposals/{chunk_id}/wait?timeout=300Holds one HTTP request open until the proposal is approved, rejected, or the timeout expires.

If policy advisor is disabled, every route returns 404 feature_disabled, the skill is not installed for new sandboxes, and L7 deny bodies do not advertise policy.local routes or include agent_guidance.

What to Expect

Approved network rules hot-reload without restarting the sandbox. HTTP L7 keep-alive connections are closed at the reload boundary so the next parsed request uses the new policy. Raw streams remain connection-scoped, as described in Customize Sandbox Policies.

Policy advisor emits audit events into the sandbox log. Use these lines to trace the full loop:

$openshell logs <sandbox-name> --since 10m

Look for HTTP:* DENIED, CONFIG:PROPOSED, CONFIG:APPROVED or CONFIG:REJECTED, CONFIG:LOADED, and the final allowed request if the agent retries successfully. Auto-approved chunks emit CONFIG:APPROVED with auto=true, source=<mechanistic|agent_authored>, prover_delta=empty, and resolved_from=<gateway|sandbox|default>.

Next Steps