⚠️ 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.
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 updatecommand for targeted, incremental policy changes. All flags are repeatable and compose into a single atomic operation.Adding Endpoints
Format:
host:port[:access[:protocol[:enforcement]]]--binaryis optional -- if omitted, any process in the sandbox can use the endpoint--rule-nameoptionally 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_globThe endpoint must already have L7 inspection enabled (
protocol: restorsql). 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
accesspreset (likeread-only), the preset is automatically expanded to explicit rules before merging.If the endpoint had
access: read-only, this expands to:GET /**,HEAD /**,OPTIONS /**(from the preset)POST /repos/*/issues(the new rule)The
accessfield is replaced by explicitrulesand 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-runWait for Sandbox
Block until the sandbox loads the updated policy:
openshell policy update my-sandbox \ --add-endpoint "ghcr.io:443" \ --wait --timeout 60Full Flag Reference
--add-endpoint <spec>host:port[:access[:protocol[:enforcement]]]--remove-endpoint <spec>host:port--add-deny <spec>host:port:METHOD:path--add-allow <spec>host:port:METHOD:path--remove-rule <name>--binary <path>--add-endpoint--rule-name <name>--add-endpoint(auto-generated if omitted)--dry-run--wait--timeout <secs>--wait(default: 60)Alternatives Considered
Agent Investigation
Explored the current
merge_chunk_into_policy()implementation incrates/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 copiesbinariesandallowed_ips. This is the core gap that this feature addresses.Also explored the
UpdateConfigRequestproto -- extending it with amerge_operationsfield is additive and backward-compatible.