Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion deps/undici/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,16 @@ The `body` mixins are the most common way to format the request/response body. M
> The body returned from `undici.request` does not implement `.formData()`.

> [!WARNING]
> Calling `body.formData()` on a fetch response causes undici to buffer and parse the entire body. Since this is dictated by the spec, `body.formData()` must only be called on responses from trusted servers.
> The body mixins `.arrayBuffer()`, `.blob()`, `.bytes()`, `.json()`, `.text()`,
> and `.formData()` buffer the entire body in memory before returning. Where
> applicable, they also decode or parse the payload and retain that
> representation in memory. Calling these methods therefore means trusting that
> the response body is small enough to fit in the available memory. Do not use
> them for responses from untrusted or user-controlled sources. Instead, consume
> the response body as a stream and enforce an application-specific size limit:
> use `response.body` for fetch responses or the `body` returned by
> `undici.request()`. For streaming decoded text, use `body.textStream()` on a
> fetch `Request` or `Response`.

Example usage:

Expand Down
30 changes: 20 additions & 10 deletions deps/undici/src/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,26 @@ lead to a loss of confidentiality, integrity, or availability.
resources, that is not considered a vulnerability. Applications are
responsible for setting appropriate limits on response sizes.

#### Calling `body.formData()` on untrusted responses

* `body.formData()` buffers and parses the entire response body. Multipart
parsing has inherent security risks, especially when the body is supplied by
an untrusted or user-controlled server. Applications must only call
`body.formData()` on responses from trusted servers. For untrusted responses,
applications should use a dedicated streaming multipart parser and enforce
application-specific limits. Resource exhaustion or parser exposure caused by
calling `body.formData()` on untrusted responses is considered an application
responsibility, not a vulnerability in undici.
#### Calling body-consuming methods on untrusted responses

* The `body.arrayBuffer()`, `body.blob()`, `body.bytes()`, `body.formData()`,
`body.json()`, and `body.text()` methods buffer the entire response body in
memory before returning. Where applicable, they also decode or parse the
payload and retain that representation in memory. Calling one of these
methods means the application trusts that the response is small enough to
fit in the available memory. Applications must not use these methods on
responses from untrusted or user-controlled servers. They should instead
process the response with a streaming API, such as `Response.body`,
`body.textStream()`, or the `Readable` body returned by `undici.request()`,
and enforce application-specific size limits while streaming. Resource
exhaustion caused by buffering an untrusted response is considered an
application responsibility, not a vulnerability in undici.

* Multipart parsing has additional inherent security risks. Applications
processing untrusted multipart responses should use a dedicated streaming
multipart parser and enforce application-specific limits. Parser exposure
caused by calling `body.formData()` on an untrusted response is considered an
application responsibility, not a vulnerability in undici.

#### HTTP/1.1 keep-alive with untrusted servers

Expand Down
16 changes: 8 additions & 8 deletions deps/undici/src/docs/docs/api/Agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@ changes:
`Infinity`, no limit is enforced. Must be a number greater than `0`.
**Default:** `Infinity`.

`Agent` inherits all {PoolOptions} (and therefore all {ClientOptions}). The
per-origin {Pool} it creates uses the default unlimited `connections`, so
concurrent requests to the same origin are spread across separate {Client}
instances on separate sockets.
`Agent` inherits all {PoolOptions} (and therefore all {ClientOptions}). Each
origin gets a separate {Pool}, with `connections` acting as the maximum number
of clients that pool may create.

> [!NOTE]
> Because each concurrent request to an origin may use a different {Client},
> HTTP/2 multiplexing on a shared session does not apply unless `connections` is
> set to a small value (for example `connections: 1`). See {PoolOptions} and
> {ClientOptions} for the full set of inherited options such as `allowH2`
> For an h2-capable HTTPS origin, the per-origin pool waits for the first TLS
> connection to finish ALPN negotiation. If the server selects h2, concurrent
> requests share that session up to `maxConcurrentStreams`. If it selects
> HTTP/1.1, normal connection fan-out resumes up to `connections`. See
> {PoolOptions} and {ClientOptions} for inherited options such as `allowH2`
> (default `true`) and `maxConcurrentStreams` (default `100`).

### `agent.closed`
Expand Down
15 changes: 9 additions & 6 deletions deps/undici/src/docs/docs/api/DiagnosticsChannel.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,24 +120,26 @@ diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) =>
added: v6.3.0
-->

Published after the response headers have been received.
Published after the response headers have been received. This includes a
successful CONNECT or protocol upgrade response.

* `message` {Object}
* `request` {Object} The same object published by
[`'undici:request:create'`][].
* `response` {Object} The response being received.
* `statusCode` {number} The HTTP status code.
* `statusText` {string} The HTTP status message.
* `headers` {Buffer[]} The raw response headers as an array of buffers,
alternating between header name and value.
* `headers` {Buffer[]|Object} HTTP/1.1 response headers are an array of
buffers alternating between header name and value. HTTP/2 response
headers are an object.

```mjs
import diagnosticsChannel from 'node:diagnostics_channel'

diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => {
console.log('statusCode', response.statusCode)
console.log(response.statusText)
console.log(response.headers.map((x) => x.toString()))
console.log(response.headers)
})
```

Expand Down Expand Up @@ -168,8 +170,9 @@ diagnosticsChannel.channel('undici:request:bodyChunkReceived').subscribe(({ requ
added: v6.3.0
-->

Published after the response body and trailers have been received, that is, once
the response has fully completed.
Published once the response has fully completed. After an upgraded socket has
been passed to the request handler, this event is published with an empty
`trailers` array.

* `message` {Object}
* `request` {Object} The same object published by
Expand Down
9 changes: 9 additions & 0 deletions deps/undici/src/docs/docs/api/Dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,15 @@ example, calling `text()` after `json()` throws a `TypeError`. The body also
provides `dump({ limit })`, which discards up to `limit` bytes (default
`131072`) without destroying the socket.

> [!WARNING]
> The `arrayBuffer()`, `blob()`, `bytes()`, `json()`, and `text()` methods buffer
> the entire body in memory before returning. Where applicable, they also decode
> or parse the payload and retain that representation in memory. Calling these
> methods therefore means trusting that the body is small enough to fit in the
> available memory. Do not use them for bodies received from untrusted or
> user-controlled sources. Instead, process `body` as a `Readable` stream and
> enforce an application-specific size limit.

The body is always a `Readable`, even when empty. Deserializing an empty body
with `json()` throws. To guard against this, verify the status code is not `204`
and the `content-type` header starts with `application/json` before calling
Expand Down
25 changes: 25 additions & 0 deletions deps/undici/src/docs/docs/api/Errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,31 @@
* `headers` {Object|string[]|null} The response headers. (optional)
* `body` {Object|string|null} The response body. (optional)

## Class: `ProxyConnectionError`

<!-- YAML
added: v8.10.1
changes:
- version: v8.10.1
pr-url: https://github.com/nodejs/undici/pull/5707

Check warning on line 455 in deps/undici/src/docs/docs/api/Errors.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added to fail the request instead of retrying forever when the proxy connection is torn down.
-->

* Extends: {UndiciError}

A connection to the proxy failed in a way that cannot be recovered on the
same connection, so the request fails instead of being retried.

* `name` {string} Always `'ProxyConnectionError'`.
* `code` {string} Always `'UND_ERR_PRX_CONN'`.
* `cause` {Error} The underlying error that caused the proxy connection to fail.

### `new ProxyConnectionError(cause[, message[, options]])`

* `cause` {Error} The underlying error. (optional)
* `message` {string} The error message. (optional)
* `options` {Object} Additional `Error` options merged with `cause`. (optional)

## Class: `SecureProxyConnectionError`

<!-- YAML
Expand Down
10 changes: 10 additions & 0 deletions deps/undici/src/docs/docs/api/Fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,16 @@ properties for reading a body. Each consuming method reads the body once; after
the body has been consumed, [`bodyUsed`](#bodybodyused) becomes `true` and
calling another consuming method throws a `TypeError`.

> [!WARNING]
> The `arrayBuffer()`, `blob()`, `bytes()`, `formData()`, `json()`, and `text()`
> methods buffer the entire body in memory before returning. Where applicable,
> they also decode or parse the payload and retain that representation in
> memory. Calling these methods therefore means trusting that the body is small
> enough to fit in the available memory. Do not use them for bodies received
> from untrusted or user-controlled sources. Instead, process `body.body` or
> `body.textStream()` incrementally and enforce an application-specific size
> limit.

### `body.arrayBuffer()`

<!-- YAML
Expand Down
7 changes: 4 additions & 3 deletions deps/undici/src/docs/docs/api/Interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,10 @@ Automatically decompresses response bodies encoded with `gzip`, `x-gzip`,
skipped. **Default:** `[204, 304]`.
* `skipErrorResponses` {boolean} When `true`, responses with a status code
>= 400 are not decompressed. **Default:** `true`.
* `maxSize` {number} Maximum decompressed response size in bytes. The request
fails with a `ResponseExceededMaxSizeError` if the decoded body exceeds
this limit. **Default:** `67108864` (64 MiB).
* `maxSize` {number} Maximum decompressed response size in bytes for each
decompression stage. The request fails with a
`ResponseExceededMaxSizeError` if a stage exceeds this limit. Set to `0` to
disable the limit. **Default:** `0`.

**Returns:** {Dispatcher.DispatcherComposeInterceptor}

Expand Down
11 changes: 5 additions & 6 deletions deps/undici/src/docs/docs/api/Pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,11 @@ connector shared by every pooled client.

> [!NOTE]
> `Pool` inherits all {ClientOptions}, including `allowH2` and
> `maxConcurrentStreams`. With the default unlimited `connections`, the pool
> opens a new client - and therefore a new TCP/TLS socket - per concurrent
> dispatch, which defeats HTTP/2 multiplexing over a shared session. To benefit
> from h2 multiplexing on a single session, cap `connections` (for example
> `connections: 1`) so that concurrent requests share a session up to
> `maxConcurrentStreams`.
> `maxConcurrentStreams`. For an h2-capable HTTPS origin, the pool waits for
> the first TLS connection to finish ALPN negotiation before opening more
> clients. If the server selects h2, concurrent requests share that session up
> to `maxConcurrentStreams`. If it selects HTTP/1.1, the pool resumes normal
> connection fan-out up to `connections`.

```mjs
import { Pool } from 'undici'
Expand Down
11 changes: 10 additions & 1 deletion deps/undici/src/docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,16 @@ The `body` mixins are the most common way to format the request/response body. M
> The body returned from `undici.request` does not implement `.formData()`.

> [!WARNING]
> Calling `body.formData()` on a fetch response causes undici to buffer and parse the entire body. Since this is dictated by the spec, `body.formData()` must only be called on responses from trusted servers.
> The body mixins `.arrayBuffer()`, `.blob()`, `.bytes()`, `.json()`, `.text()`,
> and `.formData()` buffer the entire body in memory before returning. Where
> applicable, they also decode or parse the payload and retain that
> representation in memory. Calling these methods therefore means trusting that
> the response body is small enough to fit in the available memory. Do not use
> them for responses from untrusted or user-controlled sources. Instead, consume
> the response body as a stream and enforce an application-specific size limit:
> use `response.body` for fetch responses or the `body` returned by
> `undici.request()`. For streaming decoded text, use `body.textStream()` on a
> fetch `Request` or `Response`.

Example usage:

Expand Down
3 changes: 1 addition & 2 deletions deps/undici/src/lib/api/readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const { addAbortListener } = require('node:events')
const { Readable } = require('node:stream')
const { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require('../core/errors')
const util = require('../core/util')
const { ReadableStreamFrom } = require('../core/util')

const kConsume = Symbol('kConsume')
const kReading = Symbol('kReading')
Expand Down Expand Up @@ -246,7 +245,7 @@ class BodyReadable extends Readable {
*/
get body () {
if (!this[kBody]) {
this[kBody] = ReadableStreamFrom(this)
this[kBody] = ReadableStream.from(this)
if (this[kConsume]) {
// TODO: Is this the best way to force a lock?
this[kBody].getReader() // Ensure stream is locked.
Expand Down
25 changes: 23 additions & 2 deletions deps/undici/src/lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,18 +368,39 @@ class Request {
}
}

onRequestUpgrade (statusCode, headers, socket) {
/**
* @param {number} statusCode
* @param {Buffer[]|string[]|import('../../types/header.d.ts').IncomingHttpHeaders} headers
* @param {import('node:stream').Duplex} socket
* @param {string} [statusText]
*/
onRequestUpgrade (statusCode, headers, socket, statusText = '') {
this.onFinally()

assert(!this.aborted)
assert(!this.completed)

if (channels.headers.hasSubscribers) {
channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
}

const controller = this[kController]
if (controller) {
controller.rawHeaders = headers
}

const parsedHeaders = Array.isArray(headers) ? parseHeaders(headers) : headers

return this[kHandler].onRequestUpgrade?.(controller, statusCode, parsedHeaders, socket)
const result = this[kHandler].onRequestUpgrade?.(controller, statusCode, parsedHeaders, socket)

if (!this.aborted) {
this.completed = true
if (channels.trailers.hasSubscribers) {
channels.trailers.publish({ request: this, trailers: [] })
}
}

return result
}

onResponseEnd (trailers) {
Expand Down
39 changes: 0 additions & 39 deletions deps/undici/src/lib/core/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,44 +644,6 @@ function getSocketInfo (socket) {
}
}

/**
* @param {Iterable} iterable
* @returns {ReadableStream}
*/
function ReadableStreamFrom (iterable) {
// We cannot use ReadableStream.from here because it does not return a byte stream.

let iterator
return new ReadableStream(
{
start () {
iterator = iterable[Symbol.asyncIterator]()
},
pull (controller) {
return iterator.next().then(({ done, value }) => {
if (done) {
return queueMicrotask(() => {
controller.close()
controller.byobRequest?.respond(0)
})
} else {
const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
if (buf.byteLength) {
return controller.enqueue(new Uint8Array(buf))
} else {
return this.pull(controller)
}
}
})
},
cancel () {
return iterator.return()
},
type: 'bytes'
}
)
}

/**
* The object should be a FormData instance and contains all the required
* methods.
Expand Down Expand Up @@ -1026,7 +988,6 @@ module.exports = {
destroy,
bodyLength,
deepClone,
ReadableStreamFrom,
isBuffer,
assertRequestHandler,
getSocketInfo,
Expand Down
15 changes: 11 additions & 4 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,14 @@ function lazyllhttp () {

let mod

// We disable wasm SIMD on ppc64 as it seems to be broken on Power 9 architectures.
let useWasmSIMD = process.arch !== 'ppc64'
// We disable wasm SIMD on older versions of Node.js on ppc64 that are broken on Power >=9 architectures.
let useWasmSIMD = true
if (process.arch === 'ppc64') {
const [major, minor] = process.versions.node.split('.').map(n => parseInt(n, 10))
if (major < 24 || (major === 24 && minor < 12)) {
useWasmSIMD = false
}
}
// The Env Variable UNDICI_NO_WASM_SIMD allows explicitly overriding the default behavior
if (process.env.UNDICI_NO_WASM_SIMD === '1') {
useWasmSIMD = false
Expand Down Expand Up @@ -561,7 +567,7 @@ class Parser {
* @param {Buffer} head
*/
onUpgrade (head) {
const { upgrade, client, socket, headers, statusCode } = this
const { upgrade, client, socket, headers, statusCode, statusText } = this

assert(upgrade)
assert(client[kSocket] === socket)
Expand Down Expand Up @@ -596,8 +602,9 @@ class Parser {
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))

try {
request.onRequestUpgrade(statusCode, headers, socket)
request.onRequestUpgrade(statusCode, headers, socket, statusText)
} catch (err) {
util.errorRequest(client, request, err)
util.destroy(socket, err)
}

Expand Down
Loading
Loading