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.<anonymous> (.../@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:
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:
res.writeHead(upRes.statusCode ?? 502, sanitizeResponseHeaders(stripHopByHop(upRes.headers)));
with
res.writeHead(upRes.statusCode ?? 502, sanitizeResponseHeaders(stripHopByHop(upRes.headers)));
with
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.
Summary
@anthropic-ai/sandbox-runtime0.0.73'stls-terminate-proxycrashes 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-dispositioncarries a UTF-8 (non-ASCII) filename.Version
@anthropic-ai/sandbox-runtime0.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}/resourcesfiledownload returns:
Stack
Root cause
dist/sandbox/tls-terminate-proxy.js:419:upRes.headersis forwarded verbatim intores.writeHead. Node's HTTP server rejects any header value with a code point > 0xFF (ERR_INVALID_CHAR). The throw happens inside thehttpsRequest(..., upRes => {...})response callback, which fires on a later event-loop tick — after the asyncforwardUpstreamhas already returned — soforwardUpstreamGuarded'sforwardUpstream(...).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 anuncaughtExceptionand, with no process-level handler in thehost, 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-encodefilename*), sowriteHeadnever sees a value it rejects. Concretely, wrap line 419:with
with
Alternatively, wrap the
writeHeadin try/catch and write a 502 so the proxy survives and the client gets an explicit error instead of a hang.Notes
filename*=UTF-8''…for non-ASCII filenames. But many real servers (Feishu here) emit raw UTF-8 incontent-disposition,and a proxy that crashes the whole process on one non-conforming upstream header is a robustness bug.