Summary
When a CONNECT request is denied because the requesting binary doesn't match the policy, the 403 response says:
{"detail":"CONNECT host:port not permitted by policy","error":"policy_denied"}
This is the same message for "endpoint not in policy" and "endpoint matched but binary didn't match." The user has no way to distinguish the two without reading supervisor logs (which require podman logs access).
The OPA policy already computes a detailed deny reason (e.g., "binary '/usr/bin/node' not allowed in policy 'X'") but the proxy doesn't include it in the 403 response.
Reproduction
# Add endpoint with WRONG binary path
openshell policy update my-sandbox \
--add-endpoint us-east5-aiplatform.googleapis.com:443:read-write:rest:enforce \
--binary /wrong/path
# Agent tries to connect
openshell sandbox exec --name my-sandbox -- claude -p "test"
# User sees:
# 403 {"detail":"CONNECT us-east5-aiplatform.googleapis.com:443 not permitted by policy"}
#
# No indication that the ENDPOINT matched but the BINARY didn't.
# User thinks they need to add the endpoint. Actually they need to fix the binary path.
Expected Behavior
The 403 response should include the deny reason from OPA:
{
"detail": "CONNECT us-east5-aiplatform.googleapis.com:443 not permitted by policy",
"error": "policy_denied",
"reason": "binary '/usr/bin/node' not allowed in policy 'allow_us_east5_aiplatform' (ancestors: [/usr/local/bin/claude])"
}
Or at minimum, distinguish between "no matching endpoint" and "endpoint matched, binary denied."
Impact
This is the #1 debugging friction when setting up agents. Users iterate on egress endpoints when the real issue is binary identity. The fix is usually adding the correct full path (e.g., /usr/local/bin/claude instead of claude, or adding /usr/bin/node for Node.js-based agents).
Relevant Code
crates/openshell-supervisor-network/src/proxy.rs lines 738-739: hardcoded "not permitted by policy" message
crates/openshell-supervisor-network/data/sandbox-policy.rego: OPA deny_reason rule computes the detailed reason but it's not forwarded to the response
Summary
When a CONNECT request is denied because the requesting binary doesn't match the policy, the 403 response says:
{"detail":"CONNECT host:port not permitted by policy","error":"policy_denied"}This is the same message for "endpoint not in policy" and "endpoint matched but binary didn't match." The user has no way to distinguish the two without reading supervisor logs (which require
podman logsaccess).The OPA policy already computes a detailed deny reason (e.g., "binary '/usr/bin/node' not allowed in policy 'X'") but the proxy doesn't include it in the 403 response.
Reproduction
Expected Behavior
The 403 response should include the deny reason from OPA:
{ "detail": "CONNECT us-east5-aiplatform.googleapis.com:443 not permitted by policy", "error": "policy_denied", "reason": "binary '/usr/bin/node' not allowed in policy 'allow_us_east5_aiplatform' (ancestors: [/usr/local/bin/claude])" }Or at minimum, distinguish between "no matching endpoint" and "endpoint matched, binary denied."
Impact
This is the #1 debugging friction when setting up agents. Users iterate on egress endpoints when the real issue is binary identity. The fix is usually adding the correct full path (e.g.,
/usr/local/bin/claudeinstead ofclaude, or adding/usr/bin/nodefor Node.js-based agents).Relevant Code
crates/openshell-supervisor-network/src/proxy.rslines 738-739: hardcoded "not permitted by policy" messagecrates/openshell-supervisor-network/data/sandbox-policy.rego: OPAdeny_reasonrule computes the detailed reason but it's not forwarded to the response