NVIDIA / NVIDIA/OpenShell

bug: forward proxy forwards unevaluated pipelined request bytes

Open
#2,251 2 comments 0 reactions 2 assignees View on GitHub

@johntmyers is already working on this.

Since Aug 20, 2026.

area:supervisor state:stale topic:l7
Dominant language
Rust
Stars
8.7k
Forks
1.3k
Avg merge
2d 11h
Merged PRs (30d)
253

Description

Agent Diagnostic

  • Skills loaded: openshell-cli, debug-openshell-cluster, generate-sandbox-policy, and create-github-issue.
  • OpenShell version tested: 0.0.82-dev.50+gc66f9ea4 on branch 1733-supervisor-middleware/pmlocek. The affected forward-proxy path was also verified directly on main.
  • Latest release checked: v0.0.82, published July 13, 2026. The release source still injects Connection: close, appends all bytes after the first header block, and documents that one L7 evaluation suffices.
  • Known fixes reviewed: PR #158, which introduced the plain HTTP forward proxy and its one-request-per-connection assumption; PR #666, which added a single L7 evaluation based on that assumption; and the current middleware changes that exposed the invalid request-boundary handling. None fixes the underlying main-branch behavior.
  • Possible duplicates reviewed: searched open and closed issues for pipelining, leftover bytes, forward proxy request boundary, and Connection: close. No duplicate was found. Issues #155, #637, and #643 are related history but describe different problems.
  • Findings: a live Docker-backed sandbox sent an allowed request A followed by request B in one TCP write. The upstream raw TCP server received both requests on the same connection, while OpenShell logged policy evaluation only for A.
  • Remaining reason for filing: the forward proxy forwards bytes that are outside A's HTTP message boundary without parsing or independently enforcing them. Middleware did not introduce this architectural issue. It made it visible by interpreting the same leftover bytes as A's body.

Description

The plain HTTP forward proxy supports one request per connection and injects Connection: close, but it does not enforce the corresponding request boundary before forwarding bytes upstream.

The initial client read can contain a complete request A followed by some or all of a pipelined request B. OpenShell finds A's first \r\n\r\n, parses and evaluates only A, rewrites A from absolute-form to origin-form, and then appends every byte after A's header block to the upstream request. For a request with no Content-Length or Transfer-Encoding, those bytes are not a body. They belong to the next request.

Actual behavior: OpenShell forwards request B's bytes upstream after the allowed request A, despite never parsing B or applying B's L7 policy or middleware chain.

Expected behavior: Because the forward proxy does not support pipelining, it must never forward bytes beyond the end of the independently evaluated HTTP message. It should either reject the input or forward only A and discard the remainder before closing the connection.

Connection: close mitigates the impact because a compliant upstream must not process B. However, downstream compliance should not be the enforcement boundary. If an upstream or intermediary processes the additional request, B can bypass request-level L7 and middleware enforcement. The destination TCP connection is still the one selected for A, so this does not directly bypass A's destination host and port decision.

Relationship to middleware

This was discovered while reviewing middleware request-body buffering, but it is a separate issue already present on main.

Before middleware, the forward proxy already forwarded leftover bytes unchanged. Middleware merely made the bug more obvious by accidentally interpreting the leftover bytes as a request body and rebuilding the request with Content-Length. The middleware change did not introduce the architectural problem. It exposed it.

Fix options
  1. Reject the request when bytes beyond its framed message boundary are already buffered. This is fail-closed, but it is a behavioral change. A client that pipelines today might receive a successful response for A because a compliant upstream discards B; after this change, A could fail.
  2. Drop bytes beyond A's framed message boundary and complete A normally. Since pipelining is not supported and Connection: close already makes A the final request, this should preserve supported workloads while preventing unevaluated bytes from reaching upstream.

The boundary must be determined from HTTP framing, not from a TCP read: headers only for a no-body request, exactly Content-Length bytes for a fixed body, and through the terminal chunk and trailers for chunked encoding.

Reproduction Steps

  1. Start a raw TCP server on the host that records every received byte before responding.
  2. Create a sandbox policy that permits only GET /allowed on that server.
  3. From a sandbox process, connect to the proxy from HTTP_PROXY and send the following bytes in one socket write:
GET http://host.openshell.internal:19090/allowed HTTP/1.1
Host: host.openshell.internal:19090

GET /next HTTP/1.1
Host: host.openshell.internal:19090

  1. Observe that the upstream TCP server receives:
GET /allowed HTTP/1.1
Host: host.openshell.internal:19090
Connection: close
Via: 1.1 openshell-sandbox

GET /next HTTP/1.1
Host: host.openshell.internal:19090

  1. Observe that the sandbox logs contain L7 and OPA allow events only for GET /allowed. There is no evaluation event for GET /next.

Environment

  • OS: macOS 26.5.1
  • Docker: Docker Desktop engine 29.6.1
  • OpenShell: 0.0.82-dev.50+gc66f9ea4
  • Gateway: isolated plaintext Docker-backed development gateway built from the current branch
  • Latest release checked: v0.0.82; the affected source pattern is present
  • Possible duplicates checked: yes, open and closed GitHub issues were searched using multiple request-boundary and pipelining terms

Logs

captured 168 bytes
b'GET /allowed HTTP/1.1\r\nHost: host.openshell.internal:19090\r\nConnection: close\r\nVia: 1.1 openshell-sandbox\r\n\r\nGET /next HTTP/1.1\r\nHost: host.openshell.internal:19090\r\n\r\n'

HTTP:GET ALLOWED -> GET http://host.openshell.internal:19090/allowed [policy:capture_server engine:l7]
HTTP:GET ALLOWED -> GET http://host.openshell.internal:19090/allowed [policy:capture_server engine:opa]

No policy event was emitted for /next.

Definition of Done

  • A high-level regression test sends A and B in one TCP write through the plain HTTP forward proxy.
  • The upstream never receives bytes beyond A's framed message boundary.
  • Request B is not interpreted as A's body by middleware.
  • Valid fixed-length and chunked request bodies remain intact.
  • The selected reject-or-drop behavior is documented as the forward proxy's explicit non-pipelining contract.

Agent-First Checklist

  • I pointed my agent at the repo and had it investigate this issue.
  • I loaded the relevant OpenShell skills.
  • I checked the latest OpenShell release and verified the affected source pattern is present.
  • I searched existing issues for possible duplicates.
  • The issue remains unresolved because the proxy needs an explicit reject-or-drop implementation and regression coverage.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.