From 0a790169e43d50674f3fb6ba515b66166f2a7170 Mon Sep 17 00:00:00 2001 From: "sentry[bot]" <39604003+sentry[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 19:27:37 +0000 Subject: [PATCH 1/3] fix(node-core): Guard against undefined util.getSystemErrorMap --- packages/node-core/src/integrations/systemError.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node-core/src/integrations/systemError.ts b/packages/node-core/src/integrations/systemError.ts index 14c0c23ffa54..b2580e870201 100644 --- a/packages/node-core/src/integrations/systemError.ts +++ b/packages/node-core/src/integrations/systemError.ts @@ -22,6 +22,10 @@ function isSystemError(error: unknown): error is SystemError { // Appears this is the recommended way to check for Node.js SystemError // https://github.com/nodejs/node/issues/46869 + if (typeof util.getSystemErrorMap !== 'function') { + return false; + } + return util.getSystemErrorMap().has(error.errno); } From 5e1f03b6e32326f4412a62fe1da18ab2710a9ca2 Mon Sep 17 00:00:00 2001 From: Hector Date: Wed, 6 May 2026 13:24:55 +0100 Subject: [PATCH 2/3] Add tests --- .../test/integrations/systemError.test.ts | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 packages/node-core/test/integrations/systemError.test.ts diff --git a/packages/node-core/test/integrations/systemError.test.ts b/packages/node-core/test/integrations/systemError.test.ts new file mode 100644 index 000000000000..52317b692bea --- /dev/null +++ b/packages/node-core/test/integrations/systemError.test.ts @@ -0,0 +1,126 @@ +import * as util from 'node:util'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { systemErrorIntegration } from '../../src/integrations/systemError'; + +describe('systemErrorIntegration', () => { + const originalGetSystemErrorMap = util.getSystemErrorMap; + + beforeEach(() => { + Object.defineProperty(util, 'getSystemErrorMap', { + value: originalGetSystemErrorMap, + configurable: true, + writable: true, + }); + }); + + afterEach(() => { + Object.defineProperty(util, 'getSystemErrorMap', { + value: originalGetSystemErrorMap, + configurable: true, + writable: true, + }); + }); + + function createClient(sendDefaultPii = false): any { + return { getOptions: () => ({ sendDefaultPii }) }; + } + + function setSystemErrorMap(value: unknown): void { + Object.defineProperty(util, 'getSystemErrorMap', { + value, + configurable: true, + writable: true, + }); + } + + it('returns the event unchanged when util.getSystemErrorMap is undefined (e.g. Bun)', () => { + setSystemErrorMap(undefined); + + const integration = systemErrorIntegration(); + const error = Object.assign(new Error('boom'), { errno: -2, path: '/some/path' }); + const event: any = { exception: { values: [{ value: error.message }] } }; + + const result = integration.processEvent!(event, { originalException: error }, createClient()); + + expect(result).toBe(event); + expect(result.contexts?.node_system_error).toBeUndefined(); + }); + + it('adds node_system_error context for a real SystemError', () => { + const errno = -2; + setSystemErrorMap(() => new Map([[errno, ['ENOENT', 'no such file or directory']]])); + + const integration = systemErrorIntegration(); + const error = Object.assign(new Error("ENOENT: no such file or directory, open '/secret/path'"), { + errno, + path: '/secret/path', + }); + const event: any = { exception: { values: [{ value: error.message }] } }; + + const result = integration.processEvent!(event, { originalException: error }, createClient()); + + expect(result.contexts?.node_system_error).toEqual({ errno }); + expect(result.exception.values[0].value).not.toContain('/secret/path'); + }); + + it('keeps path in context when sendDefaultPii is true', () => { + const errno = -2; + setSystemErrorMap(() => new Map([[errno, ['ENOENT', 'no such file or directory']]])); + + const integration = systemErrorIntegration(); + const error = Object.assign(new Error('boom'), { errno, path: '/secret/path' }); + const event: any = { exception: { values: [{ value: error.message }] } }; + + const result = integration.processEvent!(event, { originalException: error }, createClient(true)); + + expect(result.contexts?.node_system_error).toEqual({ errno, path: '/secret/path' }); + }); + + it('keeps path in context when includePaths option is true', () => { + const errno = -2; + setSystemErrorMap(() => new Map([[errno, ['ENOENT', 'no such file or directory']]])); + + const integration = systemErrorIntegration({ includePaths: true }); + const error = Object.assign(new Error('boom'), { errno, path: '/secret/path' }); + const event: any = { exception: { values: [{ value: error.message }] } }; + + const result = integration.processEvent!(event, { originalException: error }, createClient()); + + expect(result.contexts?.node_system_error).toEqual({ errno, path: '/secret/path' }); + }); + + it('returns the event unchanged when the error has no errno', () => { + setSystemErrorMap(() => new Map([[-2, ['ENOENT', 'no such file or directory']]])); + + const integration = systemErrorIntegration(); + const error = new Error('not a system error'); + const event: any = {}; + + const result = integration.processEvent!(event, { originalException: error }, createClient()); + + expect(result.contexts?.node_system_error).toBeUndefined(); + }); + + it('returns the event unchanged when originalException is not an Error', () => { + setSystemErrorMap(() => new Map([[-2, ['ENOENT', 'no such file or directory']]])); + + const integration = systemErrorIntegration(); + const event: any = {}; + + const result = integration.processEvent!(event, { originalException: 'not an error' }, createClient()); + + expect(result.contexts?.node_system_error).toBeUndefined(); + }); + + it('returns the event unchanged when errno is not in the system error map', () => { + setSystemErrorMap(() => new Map([[-2, ['ENOENT', 'no such file or directory']]])); + + const integration = systemErrorIntegration(); + const error = Object.assign(new Error('unknown'), { errno: 99999 }); + const event: any = {}; + + const result = integration.processEvent!(event, { originalException: error }, createClient()); + + expect(result.contexts?.node_system_error).toBeUndefined(); + }); +}); From 642d3126c9daab7a9d1b9d6b1c6086b2d2943f70 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Mon, 11 May 2026 11:19:32 +0200 Subject: [PATCH 3/3] fixup! fix(node-core): Guard against undefined util.getSystemErrorMap --- .../node-core/src/integrations/systemError.ts | 7 +- .../test/integrations/systemError.test.ts | 121 ++++++++++-------- 2 files changed, 73 insertions(+), 55 deletions(-) diff --git a/packages/node-core/src/integrations/systemError.ts b/packages/node-core/src/integrations/systemError.ts index b2580e870201..29c4410a01f9 100644 --- a/packages/node-core/src/integrations/systemError.ts +++ b/packages/node-core/src/integrations/systemError.ts @@ -20,12 +20,15 @@ function isSystemError(error: unknown): error is SystemError { return false; } - // Appears this is the recommended way to check for Node.js SystemError - // https://github.com/nodejs/node/issues/46869 + // Workaround for Bun where getSystemErrorMap doesn't exist + // Can be removed once Bun supports getSystemErrorMap + // https://github.com/oven-sh/bun/issues/22872 if (typeof util.getSystemErrorMap !== 'function') { return false; } + // Appears this is the recommended way to check for Node.js SystemError + // https://github.com/nodejs/node/issues/46869 return util.getSystemErrorMap().has(error.errno); } diff --git a/packages/node-core/test/integrations/systemError.test.ts b/packages/node-core/test/integrations/systemError.test.ts index 52317b692bea..3d29be490104 100644 --- a/packages/node-core/test/integrations/systemError.test.ts +++ b/packages/node-core/test/integrations/systemError.test.ts @@ -1,125 +1,140 @@ -import * as util from 'node:util'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { Client, Event } from '@sentry/core'; +import type * as nodeUtil from 'node:util'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { systemErrorIntegration } from '../../src/integrations/systemError'; -describe('systemErrorIntegration', () => { - const originalGetSystemErrorMap = util.getSystemErrorMap; +const mocks = vi.hoisted(() => ({ + getSystemErrorMap: vi.fn() as ReturnType | undefined, +})); + +vi.mock('node:util', async importOriginal => { + const actual = (await importOriginal()) as typeof nodeUtil; + mocks.getSystemErrorMap = vi.fn(actual.getSystemErrorMap); + return { + ...actual, + get getSystemErrorMap() { + return mocks.getSystemErrorMap; + }, + }; +}); - beforeEach(() => { - Object.defineProperty(util, 'getSystemErrorMap', { - value: originalGetSystemErrorMap, - configurable: true, - writable: true, - }); - }); +import * as util from 'node:util'; +describe('systemErrorIntegration', () => { afterEach(() => { - Object.defineProperty(util, 'getSystemErrorMap', { - value: originalGetSystemErrorMap, - configurable: true, - writable: true, - }); + vi.mocked(util.getSystemErrorMap).mockRestore(); }); - function createClient(sendDefaultPii = false): any { - return { getOptions: () => ({ sendDefaultPii }) }; - } - - function setSystemErrorMap(value: unknown): void { - Object.defineProperty(util, 'getSystemErrorMap', { - value, - configurable: true, - writable: true, - }); + function createClient(sendDefaultPii = false): Client { + return { + getOptions: () => ({ sendDefaultPii }), + } as unknown as Client; } it('returns the event unchanged when util.getSystemErrorMap is undefined (e.g. Bun)', () => { - setSystemErrorMap(undefined); + const originalFn = mocks.getSystemErrorMap; + mocks.getSystemErrorMap = undefined; - const integration = systemErrorIntegration(); - const error = Object.assign(new Error('boom'), { errno: -2, path: '/some/path' }); - const event: any = { exception: { values: [{ value: error.message }] } }; + try { + const integration = systemErrorIntegration(); + const error = Object.assign(new Error('boom'), { errno: -2, path: '/some/path' }); + const event = { exception: { values: [{ value: error.message }] } } as Event; - const result = integration.processEvent!(event, { originalException: error }, createClient()); + const result = integration.processEvent!(event, { originalException: error }, createClient()) as Event; - expect(result).toBe(event); - expect(result.contexts?.node_system_error).toBeUndefined(); + expect(result).toBe(event); + expect(result.contexts?.node_system_error).toBeUndefined(); + } finally { + mocks.getSystemErrorMap = originalFn; + } }); it('adds node_system_error context for a real SystemError', () => { const errno = -2; - setSystemErrorMap(() => new Map([[errno, ['ENOENT', 'no such file or directory']]])); + vi.mocked(util.getSystemErrorMap).mockReturnValue( + new Map([[errno, ['ENOENT', 'no such file or directory']]]), + ); const integration = systemErrorIntegration(); const error = Object.assign(new Error("ENOENT: no such file or directory, open '/secret/path'"), { errno, path: '/secret/path', }); - const event: any = { exception: { values: [{ value: error.message }] } }; + const event = { exception: { values: [{ value: error.message }] } } as Event; - const result = integration.processEvent!(event, { originalException: error }, createClient()); + const result = integration.processEvent!(event, { originalException: error }, createClient()) as Event; expect(result.contexts?.node_system_error).toEqual({ errno }); - expect(result.exception.values[0].value).not.toContain('/secret/path'); + expect(result.exception?.values?.[0]?.value).not.toContain('/secret/path'); }); it('keeps path in context when sendDefaultPii is true', () => { const errno = -2; - setSystemErrorMap(() => new Map([[errno, ['ENOENT', 'no such file or directory']]])); + vi.mocked(util.getSystemErrorMap).mockReturnValue( + new Map([[errno, ['ENOENT', 'no such file or directory']]]), + ); const integration = systemErrorIntegration(); const error = Object.assign(new Error('boom'), { errno, path: '/secret/path' }); - const event: any = { exception: { values: [{ value: error.message }] } }; + const event = { exception: { values: [{ value: error.message }] } } as Event; - const result = integration.processEvent!(event, { originalException: error }, createClient(true)); + const result = integration.processEvent!(event, { originalException: error }, createClient(true)) as Event; expect(result.contexts?.node_system_error).toEqual({ errno, path: '/secret/path' }); }); it('keeps path in context when includePaths option is true', () => { const errno = -2; - setSystemErrorMap(() => new Map([[errno, ['ENOENT', 'no such file or directory']]])); + vi.mocked(util.getSystemErrorMap).mockReturnValue( + new Map([[errno, ['ENOENT', 'no such file or directory']]]), + ); const integration = systemErrorIntegration({ includePaths: true }); const error = Object.assign(new Error('boom'), { errno, path: '/secret/path' }); - const event: any = { exception: { values: [{ value: error.message }] } }; + const event = { exception: { values: [{ value: error.message }] } } as Event; - const result = integration.processEvent!(event, { originalException: error }, createClient()); + const result = integration.processEvent!(event, { originalException: error }, createClient()) as Event; expect(result.contexts?.node_system_error).toEqual({ errno, path: '/secret/path' }); }); it('returns the event unchanged when the error has no errno', () => { - setSystemErrorMap(() => new Map([[-2, ['ENOENT', 'no such file or directory']]])); + vi.mocked(util.getSystemErrorMap).mockReturnValue( + new Map([[-2, ['ENOENT', 'no such file or directory']]]), + ); const integration = systemErrorIntegration(); const error = new Error('not a system error'); - const event: any = {}; + const event = {} as Event; - const result = integration.processEvent!(event, { originalException: error }, createClient()); + const result = integration.processEvent!(event, { originalException: error }, createClient()) as Event; - expect(result.contexts?.node_system_error).toBeUndefined(); + expect(result?.contexts?.node_system_error).toBeUndefined(); }); it('returns the event unchanged when originalException is not an Error', () => { - setSystemErrorMap(() => new Map([[-2, ['ENOENT', 'no such file or directory']]])); + vi.mocked(util.getSystemErrorMap).mockReturnValue( + new Map([[-2, ['ENOENT', 'no such file or directory']]]), + ); const integration = systemErrorIntegration(); - const event: any = {}; + const event = {} as Event; - const result = integration.processEvent!(event, { originalException: 'not an error' }, createClient()); + const result = integration.processEvent!(event, { originalException: 'not an error' }, createClient()) as Event; expect(result.contexts?.node_system_error).toBeUndefined(); }); it('returns the event unchanged when errno is not in the system error map', () => { - setSystemErrorMap(() => new Map([[-2, ['ENOENT', 'no such file or directory']]])); + vi.mocked(util.getSystemErrorMap).mockReturnValue( + new Map([[-2, ['ENOENT', 'no such file or directory']]]), + ); const integration = systemErrorIntegration(); const error = Object.assign(new Error('unknown'), { errno: 99999 }); - const event: any = {}; + const event = {} as Event; - const result = integration.processEvent!(event, { originalException: error }, createClient()); + const result = integration.processEvent!(event, { originalException: error }, createClient()) as Event; expect(result.contexts?.node_system_error).toBeUndefined(); });