Skip to content

feat: incremental policy updates via openshell policy update #825

@johntmyers

Description

@johntmyers

⚠️ Feedback welcome ⚠️

Originally asked for in #768

Problem Statement

The only way to modify a sandbox network policy today is full replacement via openshell policy set --file policy.yaml. Adding a single endpoint means editing a YAML file, re-uploading the entire policy, and hoping you didn't break something. There's no way to make targeted changes.

Meanwhile, the mechanistic mapper generates rich L7 proposals (method, path, protocol, enforcement) from observed traffic, but chunk approval silently drops the L7 fields when merging. Users approve a chunk expecting L7 inspection to be configured -- it isn't.

Proposed Design

A new openshell policy update command for targeted, incremental policy changes. All flags are repeatable and compose into a single atomic operation.

Adding Endpoints

# Basic L4 endpoint (any binary can use it)
openshell policy update my-sandbox \
    --add-endpoint "ghcr.io:443"

# L7 endpoint with access preset and binary restriction
openshell policy update my-sandbox \
    --add-endpoint "api.github.com:443:read-write:rest:enforce" \
    --binary "/usr/bin/gh" \
    --binary "/usr/bin/curl"

Format: host:port[:access[:protocol[:enforcement]]]

  • Defaults: no access preset (L4-only), no protocol, no enforcement
  • --binary is optional -- if omitted, any process in the sandbox can use the endpoint
  • --rule-name optionally controls which named rule the endpoint is added to (auto-generated if omitted)

Adding Deny Rules

Block specific operations on endpoints that otherwise have broad access. Deny rules take precedence over allow rules (shipped in #822).

openshell policy update my-sandbox \
    --add-deny "api.github.com:443:POST:/repos/*/pulls/*/reviews" \
    --add-deny "api.github.com:443:PUT:/repos/*/branches/*/protection"

Format: host:port:METHOD:path_glob

The endpoint must already have L7 inspection enabled (protocol: rest or sql). If not, the command errors with a suggestion to add the endpoint with L7 first.

Adding Allow Rules

Add fine-grained L7 allow rules to an existing endpoint. When the endpoint uses an access preset (like read-only), the preset is automatically expanded to explicit rules before merging.

# Start with read-only, then selectively allow POST on specific routes
openshell policy update my-sandbox \
    --add-allow "api.github.com:443:POST:/repos/*/issues"

If the endpoint had access: read-only, this expands to:

  • GET /**, HEAD /**, OPTIONS /** (from the preset)
  • POST /repos/*/issues (the new rule)

The access field is replaced by explicit rules and the CLI warns about the expansion.

Removing Endpoints

openshell policy update my-sandbox \
    --remove-endpoint "old-api.example.com:443"

If the rule has no endpoints remaining after removal, the entire rule is removed. Removing a non-existent endpoint is a no-op (idempotent).

Removing Named Rules

openshell policy update my-sandbox \
    --remove-rule "allow_old_api_example_com_443"

Compound Operations

Multiple flags compose into one atomic update:

openshell policy update my-sandbox \
    --add-endpoint "api.github.com:443:read-write:rest:enforce" \
    --binary "/usr/bin/gh" \
    --add-deny "api.github.com:443:POST:/repos/*/pulls/*/reviews" \
    --add-deny "api.github.com:443:PUT:/repos/*/branches/*/protection" \
    --remove-endpoint "old-api.example.com:443"

Dry Run

Preview what would change without applying:

openshell policy update my-sandbox \
    --add-endpoint "ghcr.io:443:read-only" \
    --dry-run

Wait for Sandbox

Block until the sandbox loads the updated policy:

openshell policy update my-sandbox \
    --add-endpoint "ghcr.io:443" \
    --wait --timeout 60

Full Flag Reference

Flag Description Repeatable
--add-endpoint <spec> Add a network endpoint. host:port[:access[:protocol[:enforcement]]] Yes
--remove-endpoint <spec> Remove an endpoint by host:port Yes
--add-deny <spec> Add a deny rule. host:port:METHOD:path Yes
--add-allow <spec> Add an allow rule. host:port:METHOD:path Yes
--remove-rule <name> Remove an entire named rule Yes
--binary <path> Binary to associate with --add-endpoint Yes
--rule-name <name> Target rule name for --add-endpoint (auto-generated if omitted) No
--dry-run Show what would change without applying No
--wait Wait for sandbox to load the updated policy No
--timeout <secs> Timeout for --wait (default: 60) No

Alternatives Considered

  • YAML patching: Let users submit partial YAML that gets merged. More flexible but harder to validate and harder to express in a CLI.
  • Separate RPCs per operation: One RPC for add-endpoint, another for remove, etc. More proto surface area for the same functionality. Rejected in favor of a batched approach.

Agent Investigation

Explored the current merge_chunk_into_policy() implementation in crates/openshell-server/src/grpc/policy.rs. Confirmed that L7 fields (protocol, enforcement, rules, deny_rules) are silently dropped when merging into existing endpoints. The mechanistic mapper correctly populates these fields in proposals, but the merge function only copies binaries and allowed_ips. This is the core gap that this feature addresses.

Also explored the UpdateConfigRequest proto -- extending it with a merge_operations field is additive and backward-compatible.

Metadata

Metadata

Assignees

Labels

area:cliCLI-related workarea:policyPolicy engine and policy lifecycle workarea:tuiTerminal UI workhelp wantedExtra attention is neededtopic:l7Application-layer policy and inspection work

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions