Skip to content

Commit 8c1149b

Browse files
authored
fix(node): Handle stack traces with data URI filenames (#17218)
- Ref #17216 Because JavaScript can dynamically load code from text like this: ```ts const {dynamicFn} = await import(`data:application/javascript, export function dynamicFn() { throw new Error('Error from data-uri module'); };`); dynamicFn(); ``` You can get slack lines like this: ``` at dynamicFn (data:application/javascript,export function dynamicFn() { throw new Error('Error from data-uri module');};:1:38) ``` These can be huge, often base64 encoded strings. Currently we skip regex parsing on anything over 1k in length but that means we ignore the frame entirely. This PR: - Changes the stack parser so that if the line is over 1k, we still pass the first 1k for parsing - Adds initial check to the Node line parser that special cases truncated lines with data URIs in them
1 parent be4550a commit 8c1149b

File tree

4 files changed

+33
-6
lines changed

4 files changed

+33
-6
lines changed

packages/browser/test/tracekit/chromium.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ describe('Tracekit - Chrome Tests', () => {
617617
});
618618
});
619619

620-
it('should drop frames that are over 1kb', () => {
620+
it('should truncate frames that are over 1kb', () => {
621621
const LONG_STR = 'A'.repeat(1040);
622622

623623
const LONG_FRAME = {
@@ -637,6 +637,12 @@ describe('Tracekit - Chrome Tests', () => {
637637
stacktrace: {
638638
frames: [
639639
{ filename: 'http://localhost:5000/', function: '?', lineno: 50, colno: 19, in_app: true },
640+
{
641+
filename:
642+
'http://localhost:5000/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
643+
function: 'Foo.testMethod',
644+
in_app: true,
645+
},
640646
{ filename: 'http://localhost:5000/', function: 'aha', lineno: 39, colno: 5, in_app: true },
641647
],
642648
},

packages/core/src/utils/node-stack-trace.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,18 @@ export function filenameIsInApp(filename: string, isNative: boolean = false): bo
5353
export function node(getModule?: GetModuleFn): StackLineParserFn {
5454
const FILENAME_MATCH = /^\s*[-]{4,}$/;
5555
const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/;
56+
const DATA_URI_MATCH = /at (?:async )?(.+?) \(data:(.*?),/;
5657

5758
// eslint-disable-next-line complexity
5859
return (line: string) => {
60+
const dataUriMatch = line.match(DATA_URI_MATCH);
61+
if (dataUriMatch) {
62+
return {
63+
filename: `<data:${dataUriMatch[2]}>`,
64+
function: dataUriMatch[1],
65+
};
66+
}
67+
5968
const lineMatch = line.match(FULL_MATCH);
6069

6170
if (lineMatch) {

packages/core/src/utils/stacktrace.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ export function createStackParser(...parsers: StackLineParser[]): StackParser {
2323
const lines = stack.split('\n');
2424

2525
for (let i = skipFirstLines; i < lines.length; i++) {
26-
const line = lines[i] as string;
27-
// Ignore lines over 1kb as they are unlikely to be stack frames.
28-
// Many of the regular expressions use backtracking which results in run time that increases exponentially with
29-
// input size. Huge strings can result in hangs/Denial of Service:
26+
let line = lines[i] as string;
27+
// Truncate lines over 1kb because many of the regular expressions use
28+
// backtracking which results in run time that increases exponentially
29+
// with input size. Huge strings can result in hangs/Denial of Service:
3030
// https://github.com/getsentry/sentry-javascript/issues/2286
3131
if (line.length > 1024) {
32-
continue;
32+
line = line.slice(0, 1024);
3333
}
3434

3535
// https://github.com/getsentry/sentry-javascript/issues/5459

packages/core/test/lib/utils/stacktrace.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,4 +380,16 @@ describe('node', () => {
380380

381381
expect(node(input)).toEqual(expectedOutput);
382382
});
383+
384+
it('parses function name when filename is a data uri ', () => {
385+
const input =
386+
"at dynamicFn (data:application/javascript,export function dynamicFn() { throw new Error('Error from data-uri module');};:1:38)";
387+
388+
const expectedOutput = {
389+
function: 'dynamicFn',
390+
filename: '<data:application/javascript>',
391+
};
392+
393+
expect(node(input)).toEqual(expectedOutput);
394+
});
383395
});

0 commit comments

Comments
 (0)