anthropics / anthropics/sandbox-runtime

tls-terminate-proxy crashes the process (ERR_INVALID_CHAR) on a non-latin1 response header

Aperta
#490 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
TypeScript
Stelle
5.2k
Fork
439
Merge medio
2g 9m
PR unite (30g)
13

Descrizione

## 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.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.