Agent Diagnostic
- Loaded debug-openshell-cluster and openshell-cli skills
- Traced allow_encoded_slash through OpenShell source:
- path.rs:98 defines ENCODED_SLASH_SENTINEL = 0x01
- path.rs:215: allow_encoded_slash=true → %2F → 0x01 sentinel
- path.rs:289: build_canonical_path reconstructs %2F from 0x01 (in-memory canonical form)
- proxy.rs:1957: rewrite_forward_request calls output.extend_from_slice(path.as_bytes()) — writes sentinel directly to wire, no re-encoding
- Found regression test test_rewrite_forward_request_uses_canonical_path_on_the_wire only covers .. path traversal, not encoded slashes
- Found parse_http_request_accepts_encoded_slash_when_endpoint_opts_in only tests req.target in-memory, not the rewritten wire bytes
- This is the root cause of openclaw onboard hanging when allow_encoded_slash=true — upstream receives 0x01 (non-printable byte) in request-target and closes connection
Description
When allow_encoded_slash: true is set on an endpoint in the sandbox policy, any HTTP request forwarded through the forward proxy (plain HTTP, not CONNECT tunnel) that contains an encoded slash (%2F) in its request-target will have the sentinel byte 0x01 written directly to the wire instead of being re-encoded to %2F.
This causes the upstream server to receive a malformed request-target containing 0x01, which either closes the connection immediately (ECONNRESET) or waits indefinitely without responding.
This bug was introduced in v0.0.34 by commit c960d48 (fix(sandbox): canonicalize HTTP request-targets before L7 policy evaluation (#878)), which added the ENCODED_SLASH_SENTINEL mechanism but missed the wire-encoding step in rewrite_forward_request.
Reproduction Steps
- Set up OpenShell cluster with a sandbox policy that has allow_encoded_slash: true on an HTTP endpoint (e.g., any endpoint that proxies to registry.npmjs.org)
- Run openclaw onboard (or any tool that performs npm package requests from a forward-proxy sandbox)
- Request hangs indefinitely — no response, no error, connection never completes
Minimal reproduction without OpenClaw:
# From inside the sandbox, via forward proxy
curl -v http://registry.npmjs.org/@anthropic-ai%2Fvertex-sdk
# Hangs — upstream sees malformed request-target with 0x01 byte
Root Cause
In crates/openshell-sandbox/src/l7/path.rs:
- ENCODED_SLASH_SENTINEL = 0x01 (line 98)
- canonicalize_request_target: %2F → 0x01 (line 215)
- build_canonical_path: 0x01 → %2F in the canonical string (line 289)
In crates/openshell-sandbox/src/proxy.rs, rewrite_forward_request (line 1957):
output.extend_from_slice(path.as_bytes()); // ← BUG: 0x01 written as-is, not re-encoded
The path string in memory contains 0x01 bytes (from build_canonical_path producing a string with those bytes). When written to the wire, they are not re-encoded to %2F.
Expected Behavior
When allow_encoded_slash: true, the forward proxy should:
- Accept %2F in the incoming request-target
- Store it internally as 0x01 sentinel during policy evaluation
- When forwarding to upstream, re-encode 0x01 back to %2F in the wire bytes
Fix
In rewrite_forward_request (proxy.rs ~line 1957), before writing path to output, replace 0x01 bytes with %2F:
let rewritten_path: Vec<u8> = path.bytes().flat_map(|b| {
if b == ENCODED_SLASH_SENTINEL {
b"%2F".to_vec()
} else {
vec![b]
}
}).collect();
output.extend_from_slice(&rewritten_path);
Or equivalently, use the existing build_canonical_path logic which already handles this — but the current code path bypasses it at the wire-write stage.
Additional Test Gaps
The existing test test_rewrite_forward_request_uses_canonical_path_on_the_wire only covers .. path traversal. A new test is needed:
#[test]
fn test_rewrite_forward_request_reencodes_sentinel_to_percent_encoding() {
// allow_encoded_slash: true — sentinel 0x01 must be re-encoded to %2F on wire
let raw = b"GET http://host/@anthropic%2Fvertex-sdk HTTP/1.1\r\nHost: host\r\n\r\n";
let (canon, _) = canonicalize_request_target(
"/@anthropic%2Fvertex-sdk",
&CanonicalizeOptions { allow_encoded_slash: true, ..Default::default() },
).expect("canonicalization should succeed");
assert_eq!(canon.path, "/@anthropic/vertex-sdk"); // 0x01 in memory representation
let rewritten = rewrite_forward_request(raw, raw.len(), &canon.path, None).unwrap();
let rewritten_str = String::from_utf8_lossy(&rewritten);
// Must contain %2F on wire, NOT 0x01
assert!(rewritten_str.contains("@anthropic%2Fvertex-sdk"),
"wire bytes must re-encode 0x01 sentinel to %2F, got: {rewritten_str:?}");
}
Environment
- OpenShell: v0.0.34
- Cluster: forward proxy with allow_encoded_slash: true on HTTP endpoints
- Sandbox policy: OpenClaw onboard with @anthropic-ai/vertex-sdk package download
Agent-First Checklist
Agent Diagnostic
Description
When allow_encoded_slash: true is set on an endpoint in the sandbox policy, any HTTP request forwarded through the forward proxy (plain HTTP, not CONNECT tunnel) that contains an encoded slash (%2F) in its request-target will have the sentinel byte 0x01 written directly to the wire instead of being re-encoded to %2F.
This causes the upstream server to receive a malformed request-target containing 0x01, which either closes the connection immediately (ECONNRESET) or waits indefinitely without responding.
This bug was introduced in v0.0.34 by commit c960d48 (fix(sandbox): canonicalize HTTP request-targets before L7 policy evaluation (#878)), which added the ENCODED_SLASH_SENTINEL mechanism but missed the wire-encoding step in rewrite_forward_request.
Reproduction Steps
Minimal reproduction without OpenClaw:
Root Cause
In crates/openshell-sandbox/src/l7/path.rs:
In crates/openshell-sandbox/src/proxy.rs, rewrite_forward_request (line 1957):
The path string in memory contains 0x01 bytes (from build_canonical_path producing a string with those bytes). When written to the wire, they are not re-encoded to %2F.
Expected Behavior
When allow_encoded_slash: true, the forward proxy should:
Fix
In rewrite_forward_request (proxy.rs ~line 1957), before writing path to output, replace 0x01 bytes with %2F:
Or equivalently, use the existing build_canonical_path logic which already handles this — but the current code path bypasses it at the wire-write stage.
Additional Test Gaps
The existing test test_rewrite_forward_request_uses_canonical_path_on_the_wire only covers .. path traversal. A new test is needed:
Environment
Agent-First Checklist