anthropics / anthropics/sandbox-runtime
tls-terminate-proxy crashes the process (ERR_INVALID_CHAR) on a non-latin1 response header
- Lenguaje dominante
- TypeScript
- Estrellas
- 5.2k
- Forks
- 439
- Merge medio
- 2 d 9 min
- PR fusionados (30 d)
- 13
Descripción
## Summary
`@anthropic-ai/sandbox-runtime` 0.0.73's `tls-terminate-proxy` crashes the entire host process when an upstream returns a response header value containing a code point > 0xFF. A common trigger: a Feishu (Lark)
file download whose `content-disposition` carries a UTF-8 (non-ASCII) filename.
## Version
`@anthropic-ai/sandbox-runtime` 0.0.73 (latest on npm as of this report).
## Repro
Sandboxed bash makes an HTTPS request through SRT's egress (terminated-TLS proxy) to an endpoint that returns a response header with a non-latin1 value. Example: a Feishu `im/v1/messages/{id}/resources` file
download returns:
```
content-disposition: attachment; filename="中文名.pdf"
```
## Stack
```
TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["content-disposition"]
at storeHeader (node:_http_outgoing:628:5)
at processHeader (node:_http_outgoing:623:3)
at ServerResponse._storeHeader (node:_http_outgoing:492:11)
at ServerResponse.writeHead (node:_http_server:439:8)
at ClientRequest. (.../@anthropic-ai/sandbox-runtime@0.0.73/.../dist/sandbox/tls-terminate-proxy.js:419:13)
at Object.onceWrapper (node:events:634:26)
...
```
## Root cause
`dist/sandbox/tls-terminate-proxy.js:419`:
```js
res.writeHead(upRes.statusCode ?? 502, stripHopByHop(upRes.headers));
```
`upRes.headers` is forwarded verbatim into `res.writeHead`. Node's HTTP server rejects any header value with a code point > 0xFF (`ERR_INVALID_CHAR`). The throw happens **inside the `httpsRequest(..., upRes =>
{...})` response callback**, which fires on a later event-loop tick — *after* the async `forwardUpstream` has already returned — so `forwardUpstreamGuarded`'s `forwardUpstream(...).catch(...)` cannot catch it,
and the `upRes.on('error')` / `upstream.on('error')` listeners only handle stream error events, not a synchronous throw. The throw escapes as an `uncaughtException` and, with no process-level handler in the
host, Node exits the process (exit code 1).
In our deployment SRT runs in-process inside a single-process sidecar daemon, so one such download kills the daemon and takes down every concurrent sandboxed-bash call on that pod until the container restarts
(~30s). Observed 3 restarts in production.
## Suggested fix
Sanitize response header values before `writeHead` — replace code points > 0xFF (e.g. with `?`, or RFC 5987-encode `filename*`), so `writeHead` never sees a value it rejects. Concretely, wrap line 419:
```js
res.writeHead(upRes.statusCode ?? 502, sanitizeResponseHeaders(stripHopByHop(upRes.headers)));
```
with
```js
res.writeHead(upRes.statusCode ?? 502, sanitizeResponseHeaders(stripHopByHop(upRes.headers)));
```
with
```js
function sanitizeResponseHeaders(headers) {
const out = {};
for (const key of Object.keys(headers)) {
const v = headers[key];
out[key] = Array.isArray(v) ? v.map(sanitizeHeaderValue) : sanitizeHeaderValue(v);
}
return out;
}
function sanitizeHeaderValue(v) {
return typeof v === 'string' ? v.replace(/[^\x00-\xFF]/g, '?') : v;
}
```
Alternatively, wrap the `writeHead` in try/catch and write a 502 so the proxy survives and the client gets an explicit error instead of a hang.
## Notes
- HTTP/1.1 headers are technically latin1; a well-behaved origin should use RFC 5987 `filename*=UTF-8''…` for non-ASCII filenames. But many real servers (Feishu here) emit raw UTF-8 in `content-disposition`,
and a proxy that crashes the whole process on one non-conforming upstream header is a robustness bug.
- Worth checking: the same verbatim-forward pattern may exist anywhere else response headers are written back through the proxy.
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Evaluación
Este issue todavía no se ha evaluado.