From 25fcf298285a15b6f5c76cfcbc67d82b896d341a Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Wed, 30 Sep 2026 17:09:34 +0200 Subject: [PATCH 1/3] fix(error-tracking): match single-quoted debug-id in minified bundles Some minifiers wrap the ddDebugId value in single quotes (e.g. `ddDebugId:''` instead of `ddDebugId:""`). The previous regex only accepted double quotes around the value, so any bundle emitted with single quotes failed extraction and triggered `No debug ID found in any minified file. Aborting upload.` during sourcemap upload (issue #528). Loosen the value quotes to a ["'"] character class so both quote styles are accepted, while keeping the existing flexibility around the key itself. --- .../src/sourcemaps/debugId.test.ts | 20 +++++++++++++++++++ .../error-tracking/src/sourcemaps/debugId.ts | 9 +++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts b/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts index 73c3b6a62..6c64a1324 100644 --- a/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts +++ b/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts @@ -38,6 +38,26 @@ describe('extractDebugId', () => { await expect(extractDebugId(filePath)).resolves.toBe(debugId); }); + test('Should extract the debug ID when the value is wrapped in single quotes (some minifiers emit single-quoted strings)', async () => { + const filePath = path.join(tempDir, 'single-quoted-value.min.js'); + outputFileSync( + filePath, + `!function(){}({service:"app",version:"1.0.0",ddDebugId:'${debugId}'},"DD_SOURCE_CODE_CONTEXT");`, + ); + + await expect(extractDebugId(filePath)).resolves.toBe(debugId); + }); + + test('Should extract the debug ID when the key is quoted and the value is single-quoted', async () => { + const filePath = path.join(tempDir, 'mixed-quotes.min.js'); + outputFileSync( + filePath, + `!function(){}({"service":"app","version":"1.0.0","ddDebugId":'${debugId}'},"DD_SOURCE_CODE_CONTEXT");`, + ); + + await expect(extractDebugId(filePath)).resolves.toBe(debugId); + }); + test('Should stop reading after finding the debug ID in the first chunk', async () => { const filePath = path.join(tempDir, 'first-chunk.min.js'); const literal = `ddDebugId:"${debugId}"`; diff --git a/packages/plugins/error-tracking/src/sourcemaps/debugId.ts b/packages/plugins/error-tracking/src/sourcemaps/debugId.ts index 85ac74c71..c37677406 100644 --- a/packages/plugins/error-tracking/src/sourcemaps/debugId.ts +++ b/packages/plugins/error-tracking/src/sourcemaps/debugId.ts @@ -8,10 +8,11 @@ import fsp from 'fs/promises'; // (see packages/plugins/rum/src/getSourceCodeContextSnippet.ts). The key is quoted in source // (`JSON.stringify(context)`) but minifiers like terser strip quotes from object keys that are // valid identifiers, so the built output can have either `"ddDebugId":"..."` or `ddDebugId:"..."`. -// Reading it back out of the file we're about to upload means we never have to trust a filename -// as a coordination key between the RUM plugin and this one, so it stays correct across any -// bundler renaming step. -const DEBUG_ID_RX = /"?ddDebugId"?:"([0-9a-fA-F-]{36})"/; +// The value can also end up wrapped in single quotes depending on the minifier, so both `"` and `'` +// are accepted around it. Reading it back out of the file we're about to upload means we never have +// to trust a filename as a coordination key between the RUM plugin and this one, so it stays +// correct across any bundler renaming step. +const DEBUG_ID_RX = /"?ddDebugId"?:["']([0-9a-fA-F-]{36})["']/; // Read progressively so the common case only needs the first KiB, while still supporting // bundlers or transforms that place the injected snippet later in the artifact. From eff3b00ed4ec85db1dc14faaf38c5d507c005f5f Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Wed, 30 Sep 2026 17:18:37 +0200 Subject: [PATCH 2/3] fix(error-tracking): also accept backticks around the ddDebugId value Broaden the value quote class in DEBUG_ID_RX to also include backticks, since some minifiers can emit the injected value as a template literal-style string (`ddDebugId:\`\``) which the previous regex would not match. Add a regression test covering the new backticked case. --- .../error-tracking/src/sourcemaps/debugId.test.ts | 12 ++++++++++++ .../plugins/error-tracking/src/sourcemaps/debugId.ts | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts b/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts index 6c64a1324..3a560c674 100644 --- a/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts +++ b/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts @@ -58,6 +58,18 @@ describe('extractDebugId', () => { await expect(extractDebugId(filePath)).resolves.toBe(debugId); }); + test('Should extract the debug ID when the value is wrapped in backticks (some minifiers use template-literal-style strings)', async () => { + const filePath = path.join(tempDir, 'backticked-value.min.js'); + outputFileSync( + filePath, + '!function(){}' + + `({service:"app",version:"1.0.0",ddDebugId:\`${debugId}\`},` + + '"DD_SOURCE_CODE_CONTEXT");', + ); + + await expect(extractDebugId(filePath)).resolves.toBe(debugId); + }); + test('Should stop reading after finding the debug ID in the first chunk', async () => { const filePath = path.join(tempDir, 'first-chunk.min.js'); const literal = `ddDebugId:"${debugId}"`; diff --git a/packages/plugins/error-tracking/src/sourcemaps/debugId.ts b/packages/plugins/error-tracking/src/sourcemaps/debugId.ts index c37677406..5d2e01dd5 100644 --- a/packages/plugins/error-tracking/src/sourcemaps/debugId.ts +++ b/packages/plugins/error-tracking/src/sourcemaps/debugId.ts @@ -8,11 +8,11 @@ import fsp from 'fs/promises'; // (see packages/plugins/rum/src/getSourceCodeContextSnippet.ts). The key is quoted in source // (`JSON.stringify(context)`) but minifiers like terser strip quotes from object keys that are // valid identifiers, so the built output can have either `"ddDebugId":"..."` or `ddDebugId:"..."`. -// The value can also end up wrapped in single quotes depending on the minifier, so both `"` and `'` -// are accepted around it. Reading it back out of the file we're about to upload means we never have -// to trust a filename as a coordination key between the RUM plugin and this one, so it stays -// correct across any bundler renaming step. -const DEBUG_ID_RX = /"?ddDebugId"?:["']([0-9a-fA-F-]{36})["']/; +// The value can also end up wrapped in single quotes or backticks depending on the minifier, so all +// three of `"`, `'` and `` ` `` are accepted around it. Reading it back out of the file we're +// about to upload means we never have to trust a filename as a coordination key between the RUM +// plugin and this one, so it stays correct across any bundler renaming step. +const DEBUG_ID_RX = /"?ddDebugId"?:["'`]([0-9a-fA-F-]{36})["'`]/; // Read progressively so the common case only needs the first KiB, while still supporting // bundlers or transforms that place the injected snippet later in the artifact. From 2e5213e10f787c4605b9995592e3944ca698729c Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Fri, 2 Oct 2026 08:30:55 +0200 Subject: [PATCH 3/3] fix(error-tracking): require matching quotes around ddDebugId value Capture the opening quote and backreference it so a mismatched closing quote (e.g. ddDebugId:'") is no longer extracted as a debug ID. --- .../error-tracking/src/sourcemaps/debugId.test.ts | 10 ++++++++++ .../plugins/error-tracking/src/sourcemaps/debugId.ts | 11 ++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts b/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts index 3a560c674..a29e6c3e6 100644 --- a/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts +++ b/packages/plugins/error-tracking/src/sourcemaps/debugId.test.ts @@ -70,6 +70,16 @@ describe('extractDebugId', () => { await expect(extractDebugId(filePath)).resolves.toBe(debugId); }); + test('Should return undefined when the opening and closing quotes do not match', async () => { + const filePath = path.join(tempDir, 'mismatched-quotes.min.js'); + outputFileSync( + filePath, + `!function(){}({service:"app",version:"1.0.0",ddDebugId:'${debugId}"},"DD_SOURCE_CODE_CONTEXT");`, + ); + + await expect(extractDebugId(filePath)).resolves.toBeUndefined(); + }); + test('Should stop reading after finding the debug ID in the first chunk', async () => { const filePath = path.join(tempDir, 'first-chunk.min.js'); const literal = `ddDebugId:"${debugId}"`; diff --git a/packages/plugins/error-tracking/src/sourcemaps/debugId.ts b/packages/plugins/error-tracking/src/sourcemaps/debugId.ts index 5d2e01dd5..3f234ff3f 100644 --- a/packages/plugins/error-tracking/src/sourcemaps/debugId.ts +++ b/packages/plugins/error-tracking/src/sourcemaps/debugId.ts @@ -9,10 +9,11 @@ import fsp from 'fs/promises'; // (`JSON.stringify(context)`) but minifiers like terser strip quotes from object keys that are // valid identifiers, so the built output can have either `"ddDebugId":"..."` or `ddDebugId:"..."`. // The value can also end up wrapped in single quotes or backticks depending on the minifier, so all -// three of `"`, `'` and `` ` `` are accepted around it. Reading it back out of the file we're -// about to upload means we never have to trust a filename as a coordination key between the RUM -// plugin and this one, so it stays correct across any bundler renaming step. -const DEBUG_ID_RX = /"?ddDebugId"?:["'`]([0-9a-fA-F-]{36})["'`]/; +// three of `"`, `'` and `` ` `` are accepted around it. The opening quote is captured and +// backreferenced so the closing quote must match it. Reading it back out of the file we're about to +// upload means we never have to trust a filename as a coordination key between the RUM plugin and +// this one, so it stays correct across any bundler renaming step. +const DEBUG_ID_RX = /"?ddDebugId"?:(["'`])([0-9a-fA-F-]{36})\1/; // Read progressively so the common case only needs the first KiB, while still supporting // bundlers or transforms that place the injected snippet later in the artifact. @@ -23,7 +24,7 @@ export const DEBUG_ID_SEARCH_CHUNK_BYTES = 1024; const DEBUG_ID_SEARCH_OVERLAP_CHARACTERS = 64; const matchDebugId = (fileContent: string): string | undefined => { - return DEBUG_ID_RX.exec(fileContent)?.[1]; + return DEBUG_ID_RX.exec(fileContent)?.[2]; }; // Search in fixed-size reads and stop as soon as the debug ID is found. Only a small overlap is