From b329d57767cb6ec046bb1ecf9293b450c831d720 Mon Sep 17 00:00:00 2001 From: Yen Su <49794855+yens1@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:08:36 -0700 Subject: [PATCH] fix(core): Harden axios error handling against non-string error stack (#35073) Co-authored-by: Claude Fable 5 --- package.json | 1 + .../__tests__/axios-error-stack.test.ts | 36 ++++++++++ patches/axios.patch | 65 +++++++++++++++++ pnpm-lock.yaml | 69 ++++++++++--------- 4 files changed, 138 insertions(+), 33 deletions(-) create mode 100644 packages/core/src/execution-engine/node-execution-context/utils/request-helpers/__tests__/axios-error-stack.test.ts create mode 100644 patches/axios.patch diff --git a/package.json b/package.json index 25caa375586..842afc1b134 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,7 @@ "@babel/traverse": "^7.23.2" }, "patchedDependencies": { + "axios": "patches/axios.patch", "bull@4.16.4": "patches/bull@4.16.4.patch", "pdfjs-dist@5.4.296": "patches/pdfjs-dist@5.4.296.patch", "pkce-challenge@5.0.0": "patches/pkce-challenge@5.0.0.patch", diff --git a/packages/core/src/execution-engine/node-execution-context/utils/request-helpers/__tests__/axios-error-stack.test.ts b/packages/core/src/execution-engine/node-execution-context/utils/request-helpers/__tests__/axios-error-stack.test.ts new file mode 100644 index 00000000000..fbeb5cb967e --- /dev/null +++ b/packages/core/src/execution-engine/node-execution-context/utils/request-helpers/__tests__/axios-error-stack.test.ts @@ -0,0 +1,36 @@ +import type { AxiosStatic } from 'axios'; +import axios from 'axios'; +import { createRequire } from 'node:module'; + +// The `import` above loads axios's lib/ build; production code `require`s +// dist/node/axios.cjs instead. Both are patched, so test both. +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- require() is untyped +const axiosCjs: AxiosStatic = createRequire(__filename)('axios'); + +/** + * Guards `patches/axios.patch`. Without it, axios crashes on a non-string + * `error.stack` (caused by an overridden `Error.prepareStackTrace`) and replaces + * the real request error with its own TypeError. + */ +describe('axios error handling with overridden Error.prepareStackTrace', () => { + // eslint-disable-next-line @typescript-eslint/unbound-method -- saved only to restore, never invoked + const original = Error.prepareStackTrace; + + beforeEach(() => { + Error.prepareStackTrace = () => ({}); + }); + + afterEach(() => { + Error.prepareStackTrace = original; + }); + + it.each([ + ['import (lib build)', axios], + ['require (dist/node build)', axiosCjs], + ])('should surface the original request error via %s', async (_name, axiosInstance) => { + const failure = Object.assign(new Error('boom'), { code: 'ETEST' }); + await expect( + axiosInstance.get('http://unit.test', { adapter: async () => await Promise.reject(failure) }), + ).rejects.toBe(failure); + }); +}); diff --git a/patches/axios.patch b/patches/axios.patch new file mode 100644 index 00000000000..c438c8d2d18 --- /dev/null +++ b/patches/axios.patch @@ -0,0 +1,65 @@ +diff --git a/dist/axios.js b/dist/axios.js +index f1142b7..d254086 100644 +--- a/dist/axios.js ++++ b/dist/axios.js +@@ -4692,7 +4692,7 @@ + + // slice off the Error: ... line + stack = function () { +- if (!dummy.stack) { ++ if (typeof dummy.stack !== 'string') { + return ''; + } + var firstNewlineIndex = dummy.stack.indexOf('\n'); +diff --git a/dist/browser/axios.cjs b/dist/browser/axios.cjs +index 916b539..219403c 100644 +--- a/dist/browser/axios.cjs ++++ b/dist/browser/axios.cjs +@@ -4558,7 +4558,7 @@ class Axios { + + // slice off the Error: ... line + const stack = (() => { +- if (!dummy.stack) { ++ if (typeof dummy.stack !== 'string') { + return ''; + } + +diff --git a/dist/esm/axios.js b/dist/esm/axios.js +index 19dc35f..89a4559 100644 +--- a/dist/esm/axios.js ++++ b/dist/esm/axios.js +@@ -4556,7 +4556,7 @@ let Axios$1 = class Axios { + + // slice off the Error: ... line + const stack = (() => { +- if (!dummy.stack) { ++ if (typeof dummy.stack !== 'string') { + return ''; + } + +diff --git a/dist/node/axios.cjs b/dist/node/axios.cjs +index 46078b3..9ef4bbd 100644 +--- a/dist/node/axios.cjs ++++ b/dist/node/axios.cjs +@@ -5428,7 +5428,7 @@ class Axios { + + // slice off the Error: ... line + const stack = (() => { +- if (!dummy.stack) { ++ if (typeof dummy.stack !== 'string') { + return ''; + } + const firstNewlineIndex = dummy.stack.indexOf('\n'); +diff --git a/lib/core/Axios.js b/lib/core/Axios.js +index 7fc529e..97705a5 100644 +--- a/lib/core/Axios.js ++++ b/lib/core/Axios.js +@@ -47,7 +47,7 @@ class Axios { + + // slice off the Error: ... line + const stack = (() => { +- if (!dummy.stack) { ++ if (typeof dummy.stack !== 'string') { + return ''; + } + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2841a213de2..afb4e41267c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -803,6 +803,9 @@ patchedDependencies: assert@2.1.0: hash: a73271b303dca8c10a0c9caf77d2083b52032b6a887227563bee3ce3dbf5e9d2 path: patches/assert@2.1.0.patch + axios: + hash: 149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49 + path: patches/axios.patch bull@4.16.4: hash: a4b6d56db16fe5870646929938466d6a5c668435fd1551bed6a93fffb597ba42 path: patches/bull@4.16.4.patch @@ -1170,7 +1173,7 @@ importers: version: 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) fast-glob: specifier: 'catalog:' version: 3.3.3 @@ -1242,7 +1245,7 @@ importers: version: 1.25.0 axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) csv-parse: specifier: 'catalog:' version: 6.2.1 @@ -1470,7 +1473,7 @@ importers: version: link:../utils axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) cache-manager: specifier: 'catalog:' version: 5.2.3 @@ -1586,7 +1589,7 @@ importers: version: 4.0.7 axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) dotenv: specifier: 'catalog:' version: 17.2.3 @@ -1734,7 +1737,7 @@ importers: version: link:../utils axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -3280,7 +3283,7 @@ importers: version: 2.5.7 axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) basic-auth: specifier: 'catalog:' version: 2.0.1 @@ -3492,7 +3495,7 @@ importers: version: 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@7.0.2) axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) eslint: specifier: 'catalog:' version: 9.29.0(jiti@2.6.1) @@ -4609,7 +4612,7 @@ importers: version: 6.141.0 axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) concurrently: specifier: ^8.2.0 version: 8.2.0 @@ -4832,7 +4835,7 @@ importers: version: 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) nock: specifier: 'catalog:' version: 14.0.14 @@ -5490,7 +5493,7 @@ importers: version: link:../../../@n8n/utils axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) flatted: specifier: 3.4.2 version: 3.4.2 @@ -5889,7 +5892,7 @@ importers: version: 1.1.4 axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) bowser: specifier: 2.11.0 version: 2.11.0 @@ -6317,7 +6320,7 @@ importers: version: 1.11.0 axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) basic-auth: specifier: 'catalog:' version: 2.0.1 @@ -6977,7 +6980,7 @@ importers: version: 0.16.1 axios: specifier: 1.18.0 - version: 1.18.0(debug@4.4.3) + version: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) callsites: specifier: 'catalog:' version: 3.1.0 @@ -22984,7 +22987,7 @@ snapshots: '@1password/connect@1.4.2': dependencies: - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) debug: 4.4.3(supports-color@8.1.1) lodash.clonedeep: 4.5.0 slugify: 1.6.6 @@ -25537,7 +25540,7 @@ snapshots: '@codspeed/core@5.2.0': dependencies: - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) find-up: 6.3.0 form-data: 4.0.6 node-gyp-build: 4.8.4 @@ -25602,8 +25605,8 @@ snapshots: '@commander-js/extra-typings': 13.1.0(commander@13.1.0) '@currents/commit-info': 1.0.1-beta.0 async-retry: 1.3.3 - axios: 1.18.0(debug@4.4.3) - axios-retry: 4.5.0(axios@1.18.0) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) + axios-retry: 4.5.0(axios@1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)) chalk: 4.1.2 commander: 13.1.0 date-fns: 2.30.0 @@ -25652,7 +25655,7 @@ snapshots: '@daytona/api-client@0.187.0': dependencies: - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) transitivePeerDependencies: - debug - supports-color @@ -25672,7 +25675,7 @@ snapshots: '@opentelemetry/sdk-node': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) busboy: 1.6.0 dotenv: 17.4.2 expand-tilde: 2.0.2 @@ -25690,7 +25693,7 @@ snapshots: '@daytona/toolbox-api-client@0.187.0': dependencies: - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) transitivePeerDependencies: - debug - supports-color @@ -27331,7 +27334,7 @@ snapshots: '@n8n/sandbox-client@0.0.4': dependencies: - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) transitivePeerDependencies: - debug - supports-color @@ -28585,8 +28588,8 @@ snapshots: '@rudderstack/rudder-sdk-node@3.0.5': dependencies: - axios: 1.18.0(debug@4.4.3) - axios-retry: 4.5.0(axios@1.18.0) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) + axios-retry: 4.5.0(axios@1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)) component-type: 2.0.0 join-component: 1.1.0 lodash.clonedeep: 4.5.0 @@ -28870,7 +28873,7 @@ snapshots: '@slack/types': 2.20.1 '@types/node': 20.19.41 '@types/retry': 0.12.0 - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) eventemitter3: 5.0.1 form-data: 4.0.6 is-electron: 2.2.2 @@ -31962,12 +31965,12 @@ snapshots: axe-core@4.7.2: {} - axios-retry@4.5.0(axios@1.18.0): + axios-retry@4.5.0(axios@1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)): dependencies: - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) is-retry-allowed: 2.2.0 - axios@1.18.0(debug@4.3.4): + axios@1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.3.4): dependencies: follow-redirects: 1.16.0(debug@4.3.4) form-data: 4.0.6 @@ -31977,7 +31980,7 @@ snapshots: - debug - supports-color - axios@1.18.0(debug@4.4.3): + axios@1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3): dependencies: follow-redirects: 1.16.0(debug@4.4.3) form-data: 4.0.6 @@ -35504,7 +35507,7 @@ snapshots: '@types/debug': 4.1.12 '@types/node': 20.19.41 '@types/tough-cookie': 4.0.0 - axios: 1.18.0(debug@4.3.4) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.3.4) camelcase: 6.3.0 debug: 4.3.4 dotenv: 16.4.5 @@ -35515,7 +35518,7 @@ snapshots: jsonwebtoken: 9.0.3 load-esm: 1.0.3 mime-types: 2.1.35 - retry-axios: 2.6.0(axios@1.18.0) + retry-axios: 2.6.0(axios@1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)) tough-cookie: 4.1.3 transitivePeerDependencies: - supports-color @@ -39550,9 +39553,9 @@ snapshots: retimer@3.0.0: {} - retry-axios@2.6.0(axios@1.18.0): + retry-axios@2.6.0(axios@1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)): dependencies: - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) retry-request@7.0.2(encoding@0.1.13): dependencies: @@ -40309,7 +40312,7 @@ snapshots: asn1.js: 5.4.1 asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1) asn1.js-rfc5280: 3.0.0 - axios: 1.18.0(debug@4.4.3) + axios: 1.18.0(patch_hash=149e256a2a7b632497650b32816716ced972ab02a5ab00fbd8a5158a51722c49)(debug@4.4.3) big-integer: 1.6.52 bignumber.js: 9.1.2 binascii: 0.0.2