Proxy improvements: better handling of non-CONNECT requests and observability #42
Description
The sandbox HTTP proxy (crates/navigator-sandbox/src/proxy.rs) is a CONNECT-only proxy. Several improvements have been identified:
1. Return 403 instead of 405 for plain HTTP proxy requests
Currently, when a client sends a forward-proxy style plain HTTP request (e.g. GET http://example.com/ HTTP/1.1), the proxy returns 405 Method Not Allowed. This is technically correct (we don't support that method) but semantically misleading — from the sandboxed process's perspective, the request was denied, not unsupported.
We should return 403 Forbidden instead to align with how HTTPS CONNECT denials are surfaced.
2. Add deny logging for non-CONNECT requests
Plain HTTP proxy requests are currently rejected silently (no structured log). HTTPS CONNECT requests that are denied by policy produce structured deny logs. We should emit a similar log line for rejected non-CONNECT requests so operators have full visibility into all proxy traffic attempts.
The hostname can be extracted from the absolute-form URI in the request line (e.g. http://badstuff.com/path → badstuff.com).
3. Create cleaner user facing error messages
See first comment.
More items may be added to this issue as additional proxy improvements are identified.
Originally by @johntmyers on 2026-02-25T17:20:13.123-08:00
Activity
pimlock commentedon Feb 27, 2026
Revised messaging strategy:
The principle: if the request IS a recognized inference endpoint, we can acknowledge that in the message since inference is a first-class concept. If it is NOT inference, treat it as a generic policy deny with no mention of inference internals.
proxy.rs:872"only inference API calls are allowed on this connection""connection not allowed by policy"proxy.rs:821"no inference routes configured""inference endpoint detected without matching inference route"proxy.rs:708"missing inference context"inspect_for_inference, butInferenceContextwas never initialized (e.g. inference config missing at sandbox startup)"connection not allowed by policy"proxy.rs:765"non-inference request""connection not allowed by policy"proxy.rs:869"Non-inference request denied (inference-only mode)"info\!log emitted right before the 403 response"connection not allowed by policy"proxy.rs:851"Local inference routing failed""inference endpoint detected but upstream service failed"dev-sandbox-policy.rego:54"no matching policy and no inference routing configured""network connections not allowed by policy"Originally by @johntmyers on 2026-02-25T17:40:11.414-08:00
johntmyers commentedon Mar 3, 2026
Implementation Plan
Issue type:
fixComplexity: Low
Confidence: High — all changes are string/status-code replacements at known locations with clear before/after values
Summary
This issue covers three related proxy improvements: (1) returning 403 instead of 405 for plain HTTP proxy requests, (2) adding structured deny logging for non-CONNECT requests, and (3) revising 7 user-facing error messages across
proxy.rsanddev-sandbox-policy.regoto follow a consistent principle — inference-aware messages for recognized inference endpoints, generic policy-deny messages for everything else.Scope
crates/navigator-sandbox/src/proxy.rs: Change non-CONNECT response from 405→403, add structured deny log for non-CONNECT requests, and revise 5 error messages at known locationsdev-sandbox-policy.rego: Revise 1 deny_reason message (line 54)Implementation Steps
Step 1: Change non-CONNECT response from 405 to 403 and add deny logging (
proxy.rs:228-231)Current code:
Change to:
405 Method Not Allowedwith403 Forbiddenhttp://badstuff.com/path→badstuff.com) usingurl::Url::parseor simple string parsinginfo!log with fields:method,target_host(extracted hostname), and message"Non-CONNECT proxy request denied"Step 2: Revise error message #1 —
proxy.rsroute_inference_request()non-inference denial (line ~912)"only inference API calls are allowed on this connection""connection not allowed by policy"This is the JSON body returned as a 403 when a request inside a TLS-terminated tunnel doesn't match any inference API pattern.
Step 3: Revise error message #2 —
proxy.rsroute_inference_request()empty routes (line ~861)"no inference routes configured""inference endpoint detected without matching inference route"This is the JSON body returned as a 503 when the request IS recognized as inference but the route table is empty.
Step 4: Revise error message #3 —
proxy.rshandle_inference_interception()missing context (line ~748)"missing inference context""connection not allowed by policy"This is the
InferenceOutcome::Deniedreason whenInferenceContextisNone. Not inference → generic policy deny.Step 5: Revise error message #4 —
proxy.rshandle_inference_interception()non-inference request (line ~805)"non-inference request""connection not allowed by policy"This is the
InferenceOutcome::Deniedreason when the first HTTP request in a TLS-terminated tunnel doesn't match any inference pattern. Not inference → generic policy deny.Step 6: Revise error message #5 —
proxy.rsroute_inference_request()info log (line ~909)"Non-inference request denied (inference-only mode)""connection not allowed by policy"This is the
info!log message for the same trigger as #1/#4. Aligning the log message with the user-facing error.Step 7: Revise error message #6 —
proxy.rsroute_inference_request()routing failure (line ~891)"Local inference routing failed""inference endpoint detected but upstream service failed"This is the
warn!log when the router'sproxy_with_candidatescall fails. IS inference → explain actual failure.Step 8: Revise error message #7 —
dev-sandbox-policy.regodeny_reason (line 54)"no matching policy and no inference routing configured""network connections not allowed by policy"OPA-level deny when there are zero network policies AND zero inference routes → generic policy deny.
Step 9: Add/update unit tests
mod tests— none currently test these messages, so this is likely just new tests)parse_target-style extraction for non-CONNECT request URIsTest Plan
proxy.rsinlinemod tests.e2e/. The E2E sandbox tests exercise CONNECT flows which are not affected by the non-CONNECT status code change. The error message changes are internal to denied paths that E2E tests don't assert on.Risks & Open Questions
dev-sandbox-policy.rego:54may appear in OPA evaluation results surfaced to users — confirm this is the desired user-facing text.Documentation Impact
Revision 1 — initial plan
fix(proxy): return 403 for non-CONNECT requests, add deny logging, an…
johntmyers commentedon Mar 3, 2026
Implementation Complete
PR: #79
What was built
Improved the sandbox HTTP proxy to return 403 (instead of 405) for non-CONNECT requests with structured deny logging, and revised 7 user-facing error messages across proxy.rs and dev-sandbox-policy.rego to follow a consistent messaging principle.
Tests
Docs updated
architecture/sandbox.mdarchitecture/inference-routing.mdThe issue will auto-close when the PR is merged.
fix(proxy): return 403 for non-CONNECT requests, add deny logging, an…
fix(proxy): return 403 for non-CONNECT requests, add deny logging, an…
sandbox: add ollama sandbox with openclaw support (NVIDIA#42)
Merge pull request NVIDIA#42 from Ygnas/test/rhaieng-6877-selinux-qua…