From 699dfb5906a4f8142786db2165824f85a21342e1 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 22:59:02 +0000 Subject: [PATCH 1/2] fix(cloudflare): Skip comments when locating the `default` keyword in the Vite transform A comment between `export default` and the expression that contained the word `default` moved the overwrite past the keyword and produced invalid syntax. Co-Authored-By: Isaac Schlueter --- packages/cloudflare/src/vite/transform.ts | 13 ++++++++++++- packages/cloudflare/test/vite/transform.test.ts | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/vite/transform.ts b/packages/cloudflare/src/vite/transform.ts index e640de3a45d1..98ab7b17a5d3 100644 --- a/packages/cloudflare/src/vite/transform.ts +++ b/packages/cloudflare/src/vite/transform.ts @@ -396,7 +396,7 @@ function wrapDefaultExport(node: ExportDefaultNode, ctx: TransformContext, state // MagicString positions are always relative to the original source. Only the `export default` // keywords are replaced, so parentheses around the expression stay. const source = state.ms.original; - const keywordsEnd = node.start + source.slice(node.start, decl.start).lastIndexOf('default') + 'default'.length; + const keywordsEnd = exportDefaultKeywordsEnd(source, node.start); const declaredName = decl.type === 'ClassDeclaration' || decl.type === 'FunctionDeclaration' ? (decl as ClassDeclarationNode).id?.name @@ -425,6 +425,17 @@ function wrapDefaultExport(node: ExportDefaultNode, ctx: TransformContext, state state.autoWrapped.add(DEFAULT_EXPORT); } +/** + * Offset just past the `default` keyword of the `export default` statement at `start`. Comments can + * sit between `export`, `default` and the expression, and may themselves contain the word `default`. + */ +function exportDefaultKeywordsEnd(source: string, start: number): number { + const keywords = /export(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*default/y; + keywords.lastIndex = start; + keywords.exec(source); + return keywords.lastIndex; +} + function isAnonymousFunctionOrClass(node: BaseNode): boolean { if (node.type === 'ArrowFunctionExpression') return true; const isFunctionOrClass = [ diff --git a/packages/cloudflare/test/vite/transform.test.ts b/packages/cloudflare/test/vite/transform.test.ts index 76129b034923..c8516ea8ef77 100644 --- a/packages/cloudflare/test/vite/transform.test.ts +++ b/packages/cloudflare/test/vite/transform.test.ts @@ -362,6 +362,19 @@ describe('default export wrapping', () => { ); }); + it('wraps a default export with a comment that mentions `default`', () => { + const code = 'export /* the default */ default /* default handler */ { fetch() {} };'; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'var __SENTRY_DEFAULT_EXPORT__ = /* default handler */ { fetch() {} };', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__);', + ].join('\n'), + ); + }); + it('keeps the module binding of a named default class', () => { const code = ['export default class Handler { fetch() {} }', 'Handler.ready = true;'].join('\n'); From 68dc876af67f8dda608d0d1fce6b317e2a631ca7 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:02:49 +0000 Subject: [PATCH 2/2] fix(cloudflare): Scan for the `default` keyword without a backtracking regex CodeQL flagged the comment-skipping regex as prone to exponential backtracking. A linear scan over whitespace and comments finds the same offset. --- packages/cloudflare/src/vite/transform.ts | 17 +++++++++++++---- packages/cloudflare/test/vite/transform.test.ts | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/cloudflare/src/vite/transform.ts b/packages/cloudflare/src/vite/transform.ts index 98ab7b17a5d3..43add52af3cd 100644 --- a/packages/cloudflare/src/vite/transform.ts +++ b/packages/cloudflare/src/vite/transform.ts @@ -430,10 +430,19 @@ function wrapDefaultExport(node: ExportDefaultNode, ctx: TransformContext, state * sit between `export`, `default` and the expression, and may themselves contain the word `default`. */ function exportDefaultKeywordsEnd(source: string, start: number): number { - const keywords = /export(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*default/y; - keywords.lastIndex = start; - keywords.exec(source); - return keywords.lastIndex; + let index = start + 'export'.length; + while (index < source.length && !source.startsWith('default', index)) { + if (source.startsWith('//', index)) { + const lineEnd = source.indexOf('\n', index); + index = lineEnd === -1 ? source.length : lineEnd; + } else if (source.startsWith('/*', index)) { + const commentEnd = source.indexOf('*/', index + 2); + index = commentEnd === -1 ? source.length : commentEnd + 2; + } else { + index++; + } + } + return index + 'default'.length; } function isAnonymousFunctionOrClass(node: BaseNode): boolean { diff --git a/packages/cloudflare/test/vite/transform.test.ts b/packages/cloudflare/test/vite/transform.test.ts index c8516ea8ef77..bc99d7133f5b 100644 --- a/packages/cloudflare/test/vite/transform.test.ts +++ b/packages/cloudflare/test/vite/transform.test.ts @@ -375,6 +375,20 @@ describe('default export wrapping', () => { ); }); + it('wraps a default export with a line comment that mentions `default`', () => { + const code = ['export // the default', 'default // default handler', '{ fetch() {} };'].join('\n'); + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'var __SENTRY_DEFAULT_EXPORT__ = // default handler', + '{ fetch() {} };', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__);', + ].join('\n'), + ); + }); + it('keeps the module binding of a named default class', () => { const code = ['export default class Handler { fetch() {} }', 'Handler.ready = true;'].join('\n');