Skip to content

fix(proxy): L4 and forward-proxy 403/502 responses should include a body #807

@johntmyers

Description

@johntmyers

Agent Diagnostic

Loaded skills: openshell-cli, create-github-issue. Explored credential placeholder resolution in the L7 proxy to verify header-level injection for a user report. While testing with curl -s from inside a sandbox, discovered that policy denials and upstream failures return empty-body HTTP responses, making them indistinguishable from each other (and from "no response" to the user).

Searched all 403/502 response sites in proxy.rs and l7/rest.rs. The L7 inspection path (l7/rest.rs:344-381, send_deny_response()) already returns a rich JSON body:

{"error":"policy_denied","policy":"...","rule":"GET /path","detail":"GET /path not permitted by policy"}

The inference denial path (proxy.rs:1285-1293) also returns a JSON body:

{"error":"connection not allowed by policy"}

But the L4 CONNECT and forward-proxy paths write bare status lines with no body at all.

Description

Actual behavior: 9 denial sites in proxy.rs return HTTP/1.1 403 Forbidden\r\n\r\n (empty body). The 502 at line 2366 returns HTTP/1.1 502 Bad Gateway\r\n\r\n (also empty). With curl -s, the user sees zero output — no way to distinguish "denied by policy" from "upstream unreachable" from "no response."

Empty-body 403 sites in proxy.rs:

  • Line 466 — OPA policy denial (CONNECT)
  • Line 521 — SSRF: allowed_ips check failed (CONNECT)
  • Line 556 — SSRF: invalid allowed_ips in policy (CONNECT)
  • Line 597 — SSRF: internal address without allowed_ips (CONNECT)
  • Line 2074 — OPA policy denial (forward proxy)
  • Line 2202 — L7 policy denial (forward proxy)
  • Line 2257 — SSRF: allowed_ips check failed (forward proxy)
  • Line 2295 — SSRF: invalid allowed_ips in policy (forward proxy)
  • Line 2337 — SSRF: internal IP (forward proxy)

Empty-body 502:

  • Line 2366 — upstream TCP connect failed (forward proxy)

Expected behavior: All denial and error responses should include a JSON body consistent with the L7 path, e.g.:

{"error":"policy_denied","detail":"CONNECT api.example.com:443 not permitted by policy"}
{"error":"upstream_unreachable","detail":"connection refused: api.example.com:443"}

This makes proxy errors diagnosable without -v and gives tools/SDKs a machine-readable error to surface.

Proposed Design

Add a helper (or extend the existing respond()) that writes a JSON body with Content-Type: application/json and Content-Length headers. Each site should include an error code and a detail string. Pattern after the existing send_deny_response() in l7/rest.rs.

Suggested error codes:

  • policy_denied — request blocked by network policy
  • ssrf_denied — resolved IP failed allowed_ips or internal-address check
  • upstream_unreachable — TCP connect to upstream failed
  • upstream_error — upstream returned an error or closed prematurely

Alternatives Considered

  • Plain-text body (e.g., "not permitted by policy") — simpler but not machine-readable.
  • Status code only, rely on logs — current state; insufficient for curl -s and SDK users.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sandboxSandbox runtime and isolation work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions