Skip to content

Bug: allow_encoded_slash sentinel byte written directly to wire in rewrite_forward_request #987

Closed

Description

@derekhsu

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

  1. 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)
  2. Run openclaw onboard (or any tool that performs npm package requests from a forward-proxy sandbox)
  3. 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:

  1. Accept %2F in the incoming request-target
  2. Store it internally as 0x01 sentinel during policy evaluation
  3. 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

  • I pointed my agent at the repo and had it investigate this issue
  • I loaded relevant skills (debug-openshell-cluster, openshell-cli)
  • My agent could not resolve this — the diagnostic above explains why

Activity

  1. github-actions commented on Apr 27, 2026

    @github-actions

    This issue appears to have been opened without an agent investigation.

    OpenShell is an agent-first project - please point your coding agent at the repo and have it diagnose this before we triage. Your agent can load skills like debug-openshell-cluster, debug-inference, openshell-cli, and generate-sandbox-policy.

    See CONTRIBUTING.md for the full workflow.

  2. derekhsu commented on Apr 27, 2026

    @derekhsu
    Author

    This issue was investigated by a coding agent (hermes, operating on behalf of @derekhsu). The full agent diagnostic is in the issue body above — see the ## Agent Diagnostic section for the complete investigation findings, source code analysis, root cause trace, and proposed fix.

  3. derekhsu commented on Apr 27, 2026

    @derekhsu
    Author

    Revision Note (2026-04-27)

    My earlier diagnostic analysis contained an error and the proposed fix (patching rewrite_forward_request to re-encode 0x01 → %2F) was incorrect. The fix has been reverted.

    Correction: canon.path returned by build_canonical_path already contains %2F as three ASCII bytes (25 32 46), NOT the 0x01 sentinel. The sentinel conversion (0x01 ↔ %2F) is handled internally by build_canonical_path during canonicalization — the path written to wire in rewrite_forward_request already uses path.as_bytes() and correctly outputs %2F.

    The actual root cause of openclaw onboard hanging with allow_encoded_slash: true remains under investigation. The issue title and original description should be treated as unverified hypotheses, not confirmed root cause.

    I am re-examining the code flow from scratch to find the true cause.

  4. derekhsu commented on May 5, 2026

    @derekhsu
    Author

    Confirmed fixed in OpenClaw 2026.5.4. Closing.

  5. removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions