Report privately through GitHub's Report a vulnerability form on this repository, which opens a draft advisory only the maintainers can see. Please do not open a public issue for anything with a security impact.
Useful things to include, roughly in order of how much they help: what an attacker gains, a
reproduction (a test against the in-memory harness in test/ is ideal — no network needed), the
affected version, and which of the properties below you believe is broken.
You should get an acknowledgement within 5 days. If a fix is warranted it goes out as a patch release with an advisory naming the reporter, unless you would rather not be named.
It implements TLS 1.2 and 1.3, X.509 path validation, and OCSP stapling checks in JavaScript, because the runtime cannot perform a TLS handshake over a socket that is already open — which is what a proxy CONNECT tunnel is. That means the usual reason to trust a TLS implementation, that it is one of a handful everybody uses, does not apply here. Reports are genuinely welcome.
The properties worth attacking:
- Certificate validation fails closed. An unverified chain, an expired or not-yet-valid
certificate, a hostname that does not match, a missing intermediate, a signature that does not
check out, a pin that does not match, a revoked certificate under
require-staple— every one must throw a typed error. Anything that returns aResponseinstead is a vulnerability. - No silent downgrade. A version, cipher suite, group, or ALPN protocol the client did not offer must be refused, naming the value the server chose. Never negotiated down, never retried weaker.
- Trust policies never share a connection. The pool key covers every field the verifier reads, so a connection validated under one policy can never serve a request under another.
- Credentials stay put. Proxy credentials must not appear in errors, logs, or the
tunnelfetchresponse detail. Session tickets are keyed to the full pool key and are dropped onclose(). - A peer cannot make this client allocate without bound. Response heads, proxy replies, and
bodies all have caps that are enforced before the bytes are read, and every one of them has a
finite DEFAULT.
maxBodyBytesdefaulted toInfinitythrough 1.5.0, which made this claim false for any caller who had not set it: a 53-byte brotli body reaching 32 MB is measured, not hypothetical. It is 32 MiB from 1.6.0. A caller may still passInfinity, and that is then their decision rather than this package's default. - Ambiguity is an error. Conflicting framing, a truncated body, an unknown record type, a header the parser cannot agree on — all must throw rather than guess.
- Anything that requires a proxy you already control to be malicious and the caller to have set
trust: { mode: 'none' }. That mode requires a second explicit flag precisely because it turns verification off. - Denial of service against a server you point this at. That is your relationship with that server.
- Traffic analysis and TLS fingerprinting. The client presents curl's fingerprint by default and makes no claim to be indistinguishable from anything.
- Vulnerabilities in decoders you register yourself via
decoders. That code is yours; the package takes no dependencies of its own.
Every peer-facing parser — the TLS record layer and handshake messages, X.509, OCSP, HTTP/1.1 heads, chunked bodies, HTTP/2 frames, HPACK — is fuzzed against the property above — any input either parses or throws a
TunnelFetchError. To search a corner nobody has:
FUZZ_ITERATIONS=5000000 FUZZ_SEED=$RANDOM node --test test/fuzz/fuzz.test.jsA failure prints the seed and the exact case in base64. If you find one, that message is the whole report — please send it.
The latest minor release receives security fixes. This package has not yet had an external audit; if you are considering one, or have run one, please get in touch.