Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion architecture/security-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ If any condition fails, the proxy returns `403 Forbidden`.
7. Rewrites the request: absolute-form → origin-form (`GET /path HTTP/1.1`), strips hop-by-hop headers, adds `Via: 1.1 openshell-sandbox` and `Connection: close`
8. Forwards the rewritten request, then relays bidirectionally using `tokio::io::copy_bidirectional` (supports chunked transfer, SSE streams, and other long-lived responses with no idle timeout)

**V1 simplifications**: Forward proxy v1 injects `Connection: close` (no keep-alive) and does not perform L7 inspection on the forwarded traffic. Every forward proxy connection handles exactly one request-response exchange.
**V1 simplifications**: Forward proxy v1 injects `Connection: close` (no keep-alive) and does not perform L7 inspection on the forwarded traffic. To avoid bypassing endpoint L7 rules, forward proxy requests are rejected when the matched endpoint has L7 configuration; use CONNECT for those endpoints. Every forward proxy connection handles exactly one request-response exchange.

**Implementation**: See `crates/openshell-sandbox/src/proxy.rs` -- `handle_forward_proxy()`, `parse_proxy_uri()`, `rewrite_forward_request()`.

Expand Down
37 changes: 37 additions & 0 deletions crates/openshell-sandbox/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,43 @@ async fn handle_forward_proxy(
};
let policy_str = matched_policy.as_deref().unwrap_or("-");

// Forward proxy requests are parsed and rewritten before relay, so they
// cannot safely enter the CONNECT-style per-request L7 inspection loop.
// Deny here when endpoint-level L7 rules are configured to avoid bypassing
// method/path policy enforcement.
if query_l7_config(&opa_engine, &decision, &host_lc, port).is_some() {
let reason = "forward proxy is not allowed for endpoints with L7 policy; use CONNECT";
info!(
src_addr = %peer_addr.ip(),
src_port = peer_addr.port(),
proxy_addr = %local_addr,
dst_host = %host_lc,
dst_port = port,
method = %method,
path = %path,
binary = %binary_str,
binary_pid = %pid_str,
ancestors = %ancestors_str,
cmdline = %cmdline_str,
action = "deny",
engine = "opa",
policy = %policy_str,
reason = %reason,
"FORWARD",
);
emit_denial_simple(
denial_tx,
&host_lc,
port,
&binary_str,
&decision,
reason,
"forward_l7",
);
respond(client, b"HTTP/1.1 403 Forbidden\r\n\r\n").await?;
return Ok(());
}

// 5. DNS resolution + SSRF defence (mirrors the CONNECT path logic).
// - If allowed_ips is set: validate resolved IPs against the allowlist
// (this is the SSRF override for private IP destinations).
Expand Down
Loading