Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
permissions:
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale PRs
actions: write # for actions/stale to cache stale saved state
actions: write # for actions/stale to cache stale saved state
if: github.repository == 'nodejs/node'
runs-on: ubuntu-slim
steps:
Expand Down
36 changes: 35 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,22 @@ out/doc/apilinks.json: $(wildcard lib/*.js) tools/doc/node_modules | out/doc
) \
fi

doc/node.1:
$(error Please use 'make node.1' instead of 'make $@'.)

.PHONY: node.1
node.1: doc/api/cli.md tools/doc/node_modules
@if [ "$(shell $(node_use_openssl_and_icu))" != "true" ]; then \
echo "Skipping $@ (no crypto and/or no ICU)"; \
else \
$(call available-node, \
$(DOC_KIT) generate \
-v $(VERSION) \
--config-file tools/doc/man-page.doc-kit.config.mjs \
-o doc \
) \
fi

.PHONY: docopen
docopen: doc-only ## Open the documentation in a web browser.
@$(PYTHON) -mwebbrowser file://$(abspath $<)
Expand Down Expand Up @@ -1480,8 +1496,26 @@ tools/.mdlintstamp: tools/lint-md/node_modules/remark-parse/package.json $(LINT_
@$(call available-node,$(run-lint-md))
@touch $@

tools/.manpagelintstamp: doc/node.1 doc/api/cli.md tools/doc/node_modules
$(info Verifying that $< is up to date...)
@if [ "$(shell $(node_use_openssl_and_icu))" != "true" ]; then \
echo "Skipping $< verification (no crypto and/or no ICU)"; \
else \
$(RM) -r tools/doc/.manpagecheck && \
$(call available-node, \
$(DOC_KIT) generate \
-v $(VERSION) \
--config-file tools/doc/man-page.doc-kit.config.mjs \
) \
if ! diff -u $< tools/doc/.manpagecheck/node.1; then \
echo '$< is out of date; run `make node.1` to regenerate it.' >&2; \
exit 1; \
fi; \
fi
@touch $@

.PHONY: lint-md
lint-md: lint-js-doc | tools/.mdlintstamp ## Lint the markdown documents maintained by us in the codebase.
lint-md: lint-js-doc | tools/.mdlintstamp tools/.manpagelintstamp ## Lint the markdown documents maintained by us in the codebase.

run-format-md = tools/lint-md/lint-md.mjs --format $(LINT_MD_FILES)
.PHONY: format-md
Expand Down
8 changes: 8 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,14 @@ the community they pose.
* Defense-in-depth issues are never treated as Node.js security vulnerabilities,
do not receive CVEs, and are handled as regular bugs or hardening improvements.

#### Malicious protocol peers

* Node.js treats data from remote network peers as untrusted, and bugs in
parsers or protocol implementations may be security vulnerabilities.
* Node.js treats data from HTTP/1.1 keep-alive connections as trusted, meaning that a Node.js
client consuming unsolicited or misordered responses within the same HTTP/1.1 connection
reuse lifecycle are generally not considered Node.js vulnerabilities.

#### Malicious Third-Party Modules (CWE-1357)

* Code is trusted by Node.js. Therefore any scenario that requires a malicious
Expand Down
154 changes: 153 additions & 1 deletion deps/undici/src/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* [Building for externally shared node builtins](#external-builds)
* [Benchmarks](#benchmarks)
* [Documentation](#documentation)
* [Reproduction](#reproduction)
* [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin)
* [Moderation Policy](#moderation-policy)

Expand Down Expand Up @@ -58,7 +59,7 @@ npm i
> This requires [docker](https://www.docker.com/) installed on your machine.

```bash
npm run build-wasm
npm run build:wasm
```

#### Copy the sources to `undici`
Expand Down Expand Up @@ -198,6 +199,157 @@ cd docs && npm i && npm run serve

The documentation will be available at `http://localhost:3000`.

<a id="reproduction"></a>
### Reproduction

When reporting a bug, a high-quality reproduction helps maintainers diagnose and fix the issue quickly. Follow the guidelines below to create a minimal, standalone reproduction script.

#### What Makes a Good Reproduction

- **Standalone**: The script must be self-contained and run without external dependencies beyond `undici` and Node.js built-in modules.
- **Minimal**: Strip everything unrelated to the bug — no production configs, no frameworks, no unnecessary middleware.
- **Reproducible**: Run the script and confirm the bug occurs before submitting.
- **Specific**: Include the exact `undici` API call that triggers the issue (e.g., `Client`, `Pool`, `Agent`, `fetch`, `request`, `stream`, `pipeline`).
- **Run with Node's test runner**: Use `node --test` (Node 20+) or `node test/your-repro.js`.

#### Standalone Server Pattern

Use `createServer` from `node:http` (or `node:https` for TLS-related bugs) to run a local server inside the same script. This avoids external service dependencies and keeps the reproduction fully self-contained.

```javascript
'use strict'

const { test } = require('node:test')
const { createServer } = require('node:http')
const { once } = require('node:events')
const { Client } = require('undici')

// https://github.com/nodejs/undici/issues/XXXX

test('short description of the bug', { timeout: 60000 }, async (t) => {
t.plan(1)

// 1. Start a local HTTP server that reproduces the scenario
const server = createServer({ joinDuplicateHeaders: true }, async (req, res) => {
// Simulate the bug-triggering response
res.statusCode = 200
res.setHeader('content-length', 100)
res.end('hello'.repeat(20))
})
t.after(() => { server.closeAllConnections?.(); server.close() })

server.listen(0)
await once(server, 'listening')

// 2. Create the undici client pointing to the local server
const client = new Client(`http://localhost:${server.address().port}`)
t.after(() => client.close())

// 3. Perform the request that triggers the bug
const { body } = await client.request({ path: '/', method: 'GET' })

let responseBody = ''
for await (const chunk of body) {
responseBody += chunk
}

t.assert.strictEqual(responseBody, 'hello'.repeat(20))
})
```

Save the script as `test/repro-XXXX.js` and run it:

```bash
node --test test/repro-XXXX.js
```

#### Fetch-Based Reproduction

For bugs involving `fetch`, use the same standalone server pattern:

```javascript
'use strict'

const { test } = require('node:test')
const { createServer } = require('node:http')
const { once } = require('node:events')
const { fetch, Agent } = require('undici')

// https://github.com/nodejs/undici/issues/XXXX

test('short description of the fetch bug', { timeout: 60000 }, async (t) => {
t.plan(1)

const server = createServer({ joinDuplicateHeaders: true }, async (req, res) => {
res.statusCode = 200
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ ok: true }))
})
t.after(() => server.close())

server.listen(0)
await once(server, 'listening')

const agent = new Agent({ keepAliveTimeout: 1 })
t.after(() => agent.close())

const response = await fetch(`http://localhost:${server.address().port}/`, {
dispatcher: agent
})

await response.body.cancel()

t.assert.strictEqual(response.status, 200)
})
```

#### Using Pool or Agent

For bugs involving connection pooling or agent behavior:

```javascript
'use strict'

const { test } = require('node:test')
const { createServer } = require('node:http')
const { once } = require('node:events')
const { Pool } = require('undici')

// https://github.com/nodejs/undici/issues/XXXX

test('Pool bug reproduction', { timeout: 60000 }, async (t) => {
t.plan(1)

const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.end('ok')
})
t.after(() => server.close())

server.listen(0)
await once(server, 'listening')

const pool = new Pool(`http://localhost:${server.address().port}`)
t.after(() => pool.close())

const data = await pool.request({ path: '/', method: 'GET' })
await data.body.dump()

t.assert.strictEqual(data.statusCode, 200)
})
```

#### Adding the Reproduction to the Repository

If you are submitting a bug fix via a pull request, include a test file `test/issue-XXXX.js` that reproduces the bug and passes with the fix.

#### Checklist for Submitting a Bug Report

1. [ ] Write a standalone reproduction script.
2. [ ] Run it locally to confirm the bug is reproducible.
3. [ ] Attach the script (or a gist link) in the bug report.
4. [ ] Include the exact undici version and Node.js version.
5. [ ] Describe the expected vs. actual behavior.

<a id="developers-certificate-of-origin"></a>
## Developer's Certificate of Origin 1.1

Expand Down
5 changes: 3 additions & 2 deletions deps/undici/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,9 @@ Refs: https://tools.ietf.org/html/rfc7231#section-5.1.1
#### Pipelining

Undici will only use pipelining if configured with a `pipelining` factor
greater than `1`. Also it is important to pass `blocking: false` to the
request options to properly pipeline requests.
greater than `1`. Only enable pipelining when the remote server is trusted.
Also it is important to pass `blocking: false` to the request options to
properly pipeline requests.

Undici always assumes that connections are persistent and will immediately
pipeline requests, without checking whether the connection is persistent.
Expand Down
12 changes: 12 additions & 0 deletions deps/undici/src/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ lead to a loss of confidentiality, integrity, or availability.
calling `body.formData()` on untrusted responses is considered an application
responsibility, not a vulnerability in undici.

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

* HTTP/1.1 responses on a reused connection are ordered, but they do not carry a
request identifier. Once a request has been written on a reused connection,
a well-formed response sent by the server is the response to that next request
from the client's point of view. Undici avoids immediate reuse of
non-pipelined HTTP/1.1 connections when callers are already waiting to send
more work, but applications that require per-request connection isolation for
untrusted or user-controlled servers should disable reuse by using
`pipelining: 0` or `maxRequestsPerClient: 1`, or use a protocol with response
correlation such as HTTP/2.

#### Application Misconfiguration

* Issues arising from incorrect or insecure use of undici APIs (such as
Expand Down
Loading
Loading