diff --git a/library/package-lock.json b/library/package-lock.json index 396ae5ae2..6163f9100 100644 --- a/library/package-lock.json +++ b/library/package-lock.json @@ -100,7 +100,7 @@ "undici-v4": "npm:undici@^4.0.0", "undici-v5": "npm:undici@^5.0.0", "undici-v6": "npm:undici@^6.0.0", - "undici-v7": "npm:undici@7.10.0", + "undici-v7": "npm:undici@7.16.0", "xml-js": "^1.6.11", "xml2js": "^0.6.2", "zod": "^3.25.63" @@ -2990,9 +2990,9 @@ } }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -3189,9 +3189,9 @@ } }, "node_modules/@npmcli/run-script/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -3960,9 +3960,9 @@ } }, "node_modules/@sigstore/sign/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -5146,9 +5146,9 @@ } }, "node_modules/@tapjs/fixture/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -5464,9 +5464,9 @@ } }, "node_modules/@tapjs/run/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -5711,9 +5711,9 @@ } }, "node_modules/@tapjs/test/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -8993,24 +8993,28 @@ } }, "node_modules/express-v5/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-v5/node_modules/cookie": { @@ -9034,9 +9038,9 @@ } }, "node_modules/express-v5/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9044,6 +9048,10 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-v5/node_modules/qs": { @@ -9078,42 +9086,29 @@ "node": ">= 0.10" } }, - "node_modules/express-v5/node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/express/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie": { @@ -9137,9 +9132,9 @@ } }, "node_modules/express/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9147,6 +9142,10 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/qs": { @@ -9181,23 +9180,6 @@ "node": ">= 0.10" } }, - "node_modules/express/node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -10348,9 +10330,9 @@ } }, "node_modules/hono": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.2.tgz", - "integrity": "sha512-p6fyzl+mQo6uhESLxbF5WlBOAJMDh36PljwlKtP5V1v09NxlqGru3ShK+4wKhSuhuYf8qxMmrivHOa/M7q0sMg==", + "version": "4.10.8", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.8.tgz", + "integrity": "sha512-DDT0A0r6wzhe8zCGoYOmMeuGu3dyTAE40HHjwUsWFTEy5WxK1x2WDSsBPlEXgPbRIFY6miDualuUDbasPogIww==", "dev": true, "license": "MIT", "engines": { @@ -11358,13 +11340,13 @@ } }, "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, "license": "MIT", "dependencies": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -13426,9 +13408,9 @@ } }, "node_modules/npm-registry-fetch/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -14011,9 +13993,9 @@ } }, "node_modules/pacote/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -14979,9 +14961,9 @@ } }, "node_modules/read-package-json/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -15171,9 +15153,9 @@ } }, "node_modules/resolve-import/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -17785,9 +17767,9 @@ } }, "node_modules/sync-content/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -18302,9 +18284,9 @@ } }, "node_modules/tshy/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -18502,9 +18484,9 @@ } }, "node_modules/tuf-js/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -18785,9 +18767,9 @@ }, "node_modules/undici-v7": { "name": "undici", - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", "dev": true, "license": "MIT", "engines": { diff --git a/library/package.json b/library/package.json index d768eb177..267519b11 100644 --- a/library/package.json +++ b/library/package.json @@ -144,7 +144,7 @@ "undici-v4": "npm:undici@^4.0.0", "undici-v5": "npm:undici@^5.0.0", "undici-v6": "npm:undici@^6.0.0", - "undici-v7": "npm:undici@7.10.0", + "undici-v7": "npm:undici@7.16.0", "xml-js": "^1.6.11", "xml2js": "^0.6.2", "zod": "^3.25.63" diff --git a/library/sinks/Undici.tests.ts b/library/sinks/Undici.tests.ts index 188d2631e..0e422cffb 100644 --- a/library/sinks/Undici.tests.ts +++ b/library/sinks/Undici.tests.ts @@ -67,11 +67,12 @@ export async function createUndiciTests(undiciPkgName: string, port: number) { }, }); - const { request, fetch } = require( + const { request, fetch, Client, Agent } = require( undiciPkgName ) as typeof import("undici-v6"); await request("https://ssrf-redirects.testssandbox.com"); + t.same(agent.getHostnames().asArray(), [ { hostname: "ssrf-redirects.testssandbox.com", @@ -232,6 +233,47 @@ export async function createUndiciTests(undiciPkgName: string, port: number) { await request(`http://localhost:${port}/api/internal`); } ); + + agent.getHostnames().clear(); + + const client = new Client(`http://localhost:${port}`); + await client.request({ + path: "/api/test", + method: "GET", + }); + await client.request({ + path: "/api/test2", + method: "GET", + }); + t.same(agent.getHostnames().asArray(), [ + { + hostname: "localhost", + port: port, + hits: 2, + }, + ]); + + agent.getHostnames().clear(); + + const undiciAgent = new Agent(); + await undiciAgent.request({ + origin: `http://localhost:${port}`, + path: "/api/test", + method: "GET", + }); + await undiciAgent.request({ + origin: new URL(`http://localhost:${port}`), + path: "/api/test", + method: "GET", + }); + + t.same(agent.getHostnames().asArray(), [ + { + hostname: "localhost", + port: port, + hits: 2, + }, + ]); } ); diff --git a/library/sinks/Undici.ts b/library/sinks/Undici.ts index 8892297dc..6ab032f3b 100644 --- a/library/sinks/Undici.ts +++ b/library/sinks/Undici.ts @@ -13,6 +13,13 @@ import { wrapDispatch } from "./undici/wrapDispatch"; import { wrapExport } from "../agent/hooks/wrapExport"; import { getHostnameAndPortFromArgs } from "./undici/getHostnameAndPortFromArgs"; import type { PartialWrapPackageInfo } from "../agent/hooks/WrapPackageInfo"; +import { + getUrlFromRequest, + type UndiciRequest, +} from "./undici/getUrlFromRequest"; +import { getPortFromURL } from "../helpers/getPortFromURL"; +import { createWrappedFunction, wrap } from "../helpers/wrap"; +import { isPlainObject } from "../helpers/isPlainObject"; const methods = [ "request", @@ -139,6 +146,68 @@ export class Undici implements Wrapper { } } + instrumentDiagnosticChannels(exports: any, pkgInfo: PartialWrapPackageInfo) { + // Undici does not publish to the channels if no subscribers are present + exports.channels.create.subscribe(() => {}); + exports.channels.headers.subscribe(() => {}); + + wrapExport(exports.channels.create, "publish", pkgInfo, { + kind: undefined, + inspectArgs: (args, agent) => { + if ( + args.length === 0 || + !args[0] || + typeof args[0] !== "object" || + !("request" in args[0]) + ) { + return; + } + const url = getUrlFromRequest(args[0].request as UndiciRequest); + if (!url) { + return; + } + + const attack = this.inspectHostname( + agent, + url.hostname, + getPortFromURL(url), + "[method]" + ); + if (attack) { + return attack; + } + }, + }); + + wrapExport(exports.channels.headers, "publish", pkgInfo, { + kind: undefined, + inspectArgs: (args, agent) => { + // Todo add SSRF redirect protection here! + //console.error("headers args:", args); + }, + }); + + return exports; + } + + modifyBuildConnectorArgs(args: unknown[], agent: Agent) { + if (!isPlainObject(args[0])) { + return args; + } + + // Todo: Make more stable, modify if options already have a lookup function + + args[0].lookup = inspectDNSLookupCalls( + lookup, + agent, + "undici", + // We don't know the method here, so we just use "undici.[method]" + "undici.[method]" + ); + + return args; + } + wrap(hooks: Hooks) { if (!isVersionGreaterOrEqual("16.8.0", getSemverNodeVersion())) { // Undici requires Node.js 16.8+ (due to web streams) @@ -147,7 +216,42 @@ export class Undici implements Wrapper { hooks .addPackage("undici") - .withVersion("^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0") + .withVersion("^6.0.0 || ^7.0.0") + .onFileRequire("lib/core/diagnostics.js", (exports, pkgInfo) => + this.instrumentDiagnosticChannels(exports, pkgInfo) + ) + .onFileRequire("lib/core/connect.js", (exports, pkgInfo) => { + return wrapExport(exports, undefined, pkgInfo, { + kind: undefined, + modifyArgs: (args, agent) => + this.modifyBuildConnectorArgs(args, agent), + }); + }) + .addFileInstrumentation({ + path: "lib/core/diagnostics.js", + functions: [], + accessLocalVariables: { + names: ["module.exports"], + cb: (vars, pkgInfo) => + this.instrumentDiagnosticChannels(vars[0], pkgInfo), + }, + }) + .addFileInstrumentation({ + path: "lib/core/connect.js", + functions: [ + { + name: "buildConnector", + nodeType: "FunctionDeclaration", + operationKind: undefined, + modifyArgs: (args, agent) => + this.modifyBuildConnectorArgs(args, agent), + }, + ], + }); + + hooks + .addPackage("undici") + .withVersion("^4.0.0 || ^5.0.0") .onRequire((exports, pkgInfo) => this.patchExports(exports, pkgInfo)) .addFileInstrumentation({ path: "./index.js", diff --git a/library/sinks/Undici2.tests.ts b/library/sinks/Undici2.tests.ts index c260af1b5..abacf6ff6 100644 --- a/library/sinks/Undici2.tests.ts +++ b/library/sinks/Undici2.tests.ts @@ -15,6 +15,9 @@ import { Undici } from "./Undici"; // Async needed because `require(...)` is translated to `await import(..)` when running tests in ESM mode export async function createUndiciTests(undiciPkgName: string, port: number) { const calls: Record = {}; + + const undiciMajorVersion = Number(undiciPkgName.split("-v")[1]); + wrap(dns, "lookup", function lookup(original) { return function lookup() { const hostname = arguments[0]; @@ -127,6 +130,7 @@ export async function createUndiciTests(undiciPkgName: string, port: number) { const error1 = await t.rejects(() => request(`http://localhost:${port}/api/internal`) ); + t.ok(error1 instanceof Error); if (error1 instanceof Error) { t.same( error1.message, @@ -147,6 +151,7 @@ export async function createUndiciTests(undiciPkgName: string, port: number) { const error2 = await t.rejects(() => request(new URL(`http://localhost:${port}/api/internal`)) ); + t.ok(error2 instanceof Error); if (error2 instanceof Error) { t.same( error2.message, @@ -198,7 +203,7 @@ export async function createUndiciTests(undiciPkgName: string, port: number) { if (error instanceof Error) { t.same( error.message, - "Zen has blocked a server-side request forgery: undici.request(...) originating from routeParams.param" + "Zen has blocked a server-side request forgery: undici.[method](...) originating from routeParams.param" ); } } @@ -285,10 +290,13 @@ export async function createUndiciTests(undiciPkgName: string, port: number) { ); logger.clear(); - setGlobalDispatcher(new UndiciAgent({})); - t.same(logger.getMessages(), [ - "undici.setGlobalDispatcher(..) was called, we can't guarantee protection!", - ]); + + if (undiciMajorVersion < 7) { + setGlobalDispatcher(new UndiciAgent({})); + t.same(logger.getMessages(), [ + "undici.setGlobalDispatcher(..) was called, we can't guarantee protection!", + ]); + } } ); diff --git a/library/sinks/undici/getUrlFromRequest.ts b/library/sinks/undici/getUrlFromRequest.ts new file mode 100644 index 000000000..43ce79c89 --- /dev/null +++ b/library/sinks/undici/getUrlFromRequest.ts @@ -0,0 +1,27 @@ +import { tryParseURL } from "../../helpers/tryParseURL"; + +// The real type is not exported by undici +export type UndiciRequest = { + origin?: string | URL; + method?: string; + path?: string; + headers?: string[] | Record; +}; + +export function getUrlFromRequest(req: UndiciRequest): URL | undefined { + if (typeof req.origin === "string") { + if (typeof req.path === "string") { + return tryParseURL(req.origin + req.path); + } + return tryParseURL(req.origin); + } + + if (req.origin instanceof URL) { + if (typeof req.path === "string") { + return tryParseURL(req.origin.href + req.path); + } + return req.origin; + } + + return undefined; +}