mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[PR #174] [CLOSED] fix(proxy): stop leaking the /proxy prefix and the sandbox address to clients #227
Labels
No labels
bug
documentation
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/AgentENV#227
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/kvcache-ai/AgentENV/pull/174
Author: @suixinio
Created: 8/15/2026
Status: ❌ Closed
Base:
main← Head:fix/proxy-root-prefix-and-self-redirects📝 Commits (2)
f3035a2fix(proxy): strip the prefix for the bare /proxy/ pathbf54f6efix(proxy): keep the sandbox address out of proxied redirects📊 Changes
1 file changed (+274 additions, -3 deletions)
View changed files
📝
src/api/proxy.rs(+274 -3)📄 Description
What
Two fixes on the sandbox proxy hop in
src/api/proxy.rs, both about internal proxy details reaching the client:/proxy/— the prefix with nothing after it — matched neither prefix route, fell through to the header-routed fallback, and was forwarded to the sandbox unmodified, so the sandbox saw/proxy/instead of/.Location/Content-Locationvalues pointing back at the sandbox's internalip:portwere passed through to the client verbatim.Why
Bare
/proxy/. The router matches/proxyexactly and/proxy/{*proxy_path}with a non-empty segment, so the empty-remainder shape reachesproxy_via_fallback, whose contract is to forward the path unchanged. The distributed gateway rewrites data-plane traffic to"/proxy" + path(upstreamTargetPath), so a client request for/always arrives at the node in exactly that shape. Consequences on a real deployment:Location: /proxy, which is relative, so it lands in the client's address bar and 404s on the next request;Every non-root path was fine, which makes this look like an application bug rather than a proxy one.
sandbox_proxy_classifieralready treatspath == "/proxy" || path.starts_with("/proxy/")as proxy traffic, so the classifier and the router disagreed about this one shape.Sandbox address in redirects. Requests are forwarded with
Hostset to the upstream authority, so an application that builds absolute URLs fromHostreturns the sandbox's internal address inLocation. Passing it through both exposes the sandbox network layout and points the client's next request at an address that is only reachable from that node.Related issue
None. Scope is small and obvious, submitted directly per CONTRIBUTING.
Scope and non-goals
Included:
/proxy/, reusing the existingproxy_via_prefixhandler;Location/Content-Locationon the HTTP response path and on a rejected websocket handshake;Not included:
Hosthandling, or the fallback contract;Design and behavior changes
Routing.
.route("/proxy/", any(proxy_via_prefix::<I>)). The handler is unchanged:strip_proxy_prefix("/proxy/")already yields/. The set of paths the gateway can produce is closed —"/proxy" + <path starting with />is either/proxy/or/proxy/<non-empty>— and only the first was unrouted. Trailing slashes are not the criterion:/proxy/station/(remainderstation/) and/proxy//(remainder/) already matched the catch-all and keep their exact upstream paths.Responses.
rewrite_upstream_self_references()runs after hop-by-hop stripping inmap_upstream_responseandmap_websocket_handshake_rejection_response. It rewrites a header only when the value parses as a URI whose authority equals the upstream authority, replacing it withpath_and_query(/when absent). Relative values and absolute URLs to any other host — an OAuth hop, a CDN — are left untouched.Compatibility and operations
Locationnow receives a path-relative value — that address was never reachable off-node.docs/src/concepts/proxy.mdalready documentsANY /proxyas forwarding to/inside the sandbox, so it describes the fixed behavior and needs no change.Validation
make fmtmake clippy— scoped to the changed package, see belowmake test-unit— first command of the target only, see below/dev/kvmand network namespaces, unavailable in the container used for these checksmake -C services test— N/A, noservices/changemaketarget — N/A, no generated code touchedCommands and results:
The same six tests fail before and after the change (
privileges::tests::scoped_spawn_clears_child_capabilities_without_mutating_caller,sandbox::ublk::overlaybd::tests::compact_layers_mixed_input_and_configured_output, twosnapshot::p2p::tests::local_overlaybd_layers_*,snapshot::repository::backends::common::acr::source_image::tests::plans_descriptorless_sparse_overlaybd_delta_for_dense_export,snapshot::repository::backends::posixfs::artifacts::tests::import_built_artifacts_preserves_zfile_memory_lower_bytes). They need privileges and real devices that the unprivileged container does not provide; the delta of this branch is exactly the three new passing tests.Skipped checks and reasons: integration tests and the capability-runner steps of
make test-unitneed root,/dev/kvmand netns; the environment available for these checks is an unprivileged container.Beyond unit tests, both fixes were verified on a two-node k3s deployment (gateway + node DaemonSet, one microVM sandbox):
Risks and reviewer notes
/proxy/; a client that deliberately used header routing to reach a sandbox path literally named/proxy/now reaches/instead. That path was already unreachable through the explicit prefix form.src/api/proxy.rsonly, and they are independent: happy to split them into separate pull requests if you prefer to review or revert them separately.Checklist
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.