Skip to content

Inspected REST endpoint: an encoded slash or conflicting framing headers cut the connection instead of returning a structured 403 #4671

Open

Description

@Kevinbeltran123

User Story

I use OpenShell to let an agent install the dependencies of real projects. When npm downloaded a dependency from GitLab, it got ECONNRESET and could not tell that the policy had blocked the request; the reason (%2F) was only in the supervisor's logs. When a request is blocked, I expect a 403 with the reason, as with other denials, so the agent can understand it.

Problem Statement

On an enforce REST endpoint, two request conditions make the supervisor close the TLS connection without any HTTP response (curl exit 56, unexpected eof while reading), instead of the structured 403 policy_denied JSON body it returns for an ordinary denied method or path:

  • a request-target that contains an encoded slash (%2F) on an endpoint without allow_encoded_slash;
  • a request that carries both Transfer-Encoding and Content-Length.

In both cases the supervisor already knows why: it emits an OCSF NET:OPEN [MED] DENIED event with the exact reason. Only the client is left without it.

Impact / Why This Matters

An agent (or any client) cannot tell these policy decisions from a network fault, so it cannot report the reason or adjust its request; it typically retries. An ordinary denial names the policy and the rule in a JSON body, which an agent can read and act on. For the %2F case, the supervisor's own log even names the fix (set allow_encoded_slash: true on this endpoint), but the agent never sees it; the operator has to find it in openshell logs.

Related, but distinct: #3577 (an unlisted hostname terminates the boundary instead of a clean denial) and #3902 (%3B silently truncates the forwarded path).

Acceptance Criteria

  • A request-target containing %2F on an endpoint without allow_encoded_slash gets an HTTP response (for example 403 with the policy_denied body and the reason) instead of a closed connection.
  • A request with both Transfer-Encoding and Content-Length gets an HTTP error response (for example 400) before the connection is closed.
  • Each response names the reason that the OCSF event already records.

Reproduction Steps

Uses only an OpenShell sandbox and the curl in its image; no third-party installs. Any image with curl and bash works.

  1. Save policy.yaml:

    version: 1
    filesystem_policy:
      include_workdir: true
      read_only: [/usr, /lib, /etc]
      read_write: [/tmp, /dev/null]
    landlock:
      compatibility: hard_requirement
    process:
      run_as_user: "1000"
      run_as_group: "1000"
    network_policies:
      github:
        endpoints:
          - host: api.github.com
            port: 443
            protocol: rest
            enforcement: enforce
            rules:
              - allow:
                  method: GET
                  path: "/**"
              - allow:
                  method: POST
                  path: /markdown
        binaries:
          - path: /usr/bin/curl
  2. openshell sandbox create --name a2 --from <image-with-curl> --policy policy.yaml

  3. In the sandbox:

    u=https://api.github.com
    # control: an ordinary denial returns a structured 403
    curl -sS -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/json' --data '{"text":"hi"}' $u/repos/NVIDIA/OpenShell/issues
    # (a) encoded slash
    curl -sS -o /dev/null -w '%{http_code}\n' $u/repos/NVIDIA%2FOpenShell
    # (d) both framing headers
    curl -sS -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/json' -H 'Transfer-Encoding: chunked' -H 'Content-Length: 16' --data '{"text":"hello"}' $u/markdown

    → control: 403, body {"error":"policy_denied","policy":"github","rule":"POST /repos/NVIDIA/OpenShell/issues",…}.
    → (a) and (d): 000, curl exit 56, OpenSSL SSL_read: … unexpected eof while reading.

  4. openshell logs a2 shows a DENIED event with the reason for each cut connection (see Logs).

Suggested UX (if applicable)

No response

Environment

  • OpenShell: v0.1.4 (gateway, supervisor and sandbox images 0.1.4; also seen on v0.1.3)
  • OS: macOS 26.6.2 (Darwin 25.6.0)
  • Deployment: local gateway, Docker driver; Docker 29.5.2 via Colima 0.10.3

Logs

HTTP:POST [MED] DENIED POST http://api.github.com:443/repos/NVIDIA/OpenShell/issues [policy:github engine:l7] [reason:L7_REQUEST deny POST api.github.com:443/repos/NVIDIA/OpenShell/issues reason=POST /repos/NVIDIA/OpenShell/issues not permitted by policy]
NET:OPEN [MED] DENIED api.github.com:443 [policy:github engine:l7] [reason:HTTP request-target rejected: request-target contains an encoded '/' (%2F) which is not allowed on this endpoint; set allow_encoded_slash: true on this endpoint if the upstream requires encoded slashes]
NET:OPEN [MED] DENIED api.github.com:443 [policy:github engine:l7] [reason:Request contains both Transfer-Encoding and Content-Length headers]
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

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions