Skip to content

Commit 0d0f2ac

Browse files
GujiasshPangjiping
authored andcommitted
fix(server): filter proxy trailer headers
1 parent 3934ddf commit 0d0f2ac

2 files changed

Lines changed: 51 additions & 4 deletions

File tree

server/src/api/lifecycle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"proxy-authenticate",
4949
"proxy-authorization",
5050
"te",
51-
"trailers",
51+
"trailer",
5252
"transfer-encoding",
5353
"upgrade",
5454
}

server/tests/test_routes_proxy.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020

2121

2222
class _FakeStreamingResponse:
23-
def __init__(self, status_code: int = 200, headers: dict | None = None, chunks: list[bytes] | None = None):
23+
def __init__(
24+
self, status_code: int = 200, headers: dict | None = None, chunks: list[bytes] | None = None
25+
):
2426
self.status_code = status_code
25-
self.headers = headers or {}
27+
self.headers = httpx.Headers(headers or {})
2628
self._chunks = chunks or []
2729

2830
async def aiter_bytes(self):
@@ -81,8 +83,10 @@ def get_endpoint(sandbox_id: str, port: int, resolve_internal: bool = False) ->
8183
**auth_headers,
8284
"Authorization": "Bearer top-secret",
8385
"Cookie": "sid=secret",
84-
"Connection": "keep-alive",
86+
"Connection": "keep-alive, X-Hop-Temp",
8587
"Upgrade": "h2c",
88+
"Trailer": "X-Checksum",
89+
"X-Hop-Temp": "drop-me",
8690
"X-Trace": "trace-1",
8791
}
8892

@@ -105,11 +109,54 @@ def get_endpoint(sandbox_id: str, port: int, resolve_internal: bool = False) ->
105109
assert "host" not in lowered_headers
106110
assert "connection" not in lowered_headers
107111
assert "upgrade" not in lowered_headers
112+
assert "trailer" not in lowered_headers
108113
assert "authorization" not in lowered_headers
109114
assert "cookie" not in lowered_headers
115+
assert "x-hop-temp" not in lowered_headers
110116
assert lowered_headers.get("x-trace") == "trace-1"
111117

112118

119+
def test_proxy_filters_response_hop_by_hop_headers(
120+
client: TestClient,
121+
auth_headers: dict,
122+
monkeypatch,
123+
) -> None:
124+
class StubService:
125+
@staticmethod
126+
def get_endpoint(sandbox_id: str, port: int, resolve_internal: bool = False) -> Endpoint:
127+
assert resolve_internal is True
128+
return Endpoint(endpoint="10.57.1.91:40109")
129+
130+
monkeypatch.setattr(lifecycle, "sandbox_service", StubService())
131+
132+
fake_client = _FakeAsyncClient()
133+
fake_client.response = _FakeStreamingResponse(
134+
status_code=200,
135+
headers={
136+
"x-backend": "yes",
137+
"Connection": "keep-alive, X-Hop-Temp",
138+
"Keep-Alive": "timeout=5",
139+
"Trailer": "X-Checksum",
140+
"X-Hop-Temp": "drop-me",
141+
},
142+
chunks=[b"proxy-ok"],
143+
)
144+
client.app.state.http_client = fake_client
145+
146+
response = client.get(
147+
"/v1/sandboxes/sbx-123/proxy/44772/healthz",
148+
headers=auth_headers,
149+
)
150+
151+
assert response.status_code == 200
152+
assert response.content == b"proxy-ok"
153+
assert response.headers.get("x-backend") == "yes"
154+
assert response.headers.get("connection") is None
155+
assert response.headers.get("keep-alive") is None
156+
assert response.headers.get("trailer") is None
157+
assert response.headers.get("x-hop-temp") is None
158+
159+
113160
def test_proxy_rejects_websocket_upgrade(
114161
client: TestClient,
115162
auth_headers: dict,

0 commit comments

Comments
 (0)