Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ jobs:
macos-15-intel, # macOS x64
windows-latest,
]
node: [18, 20, 22, 24]
node: [18, 20, 22, 24, 25]
steps:
- name: Check out current commit
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
Expand Down
38 changes: 14 additions & 24 deletions module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -467,18 +467,13 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
capture_from_isolate, result.poll_state.c_str(),
NewStringType::kNormal);
if (!stateStr.IsEmpty()) {
v8::MaybeLocal<v8::Value> maybeStateVal =
v8::JSON::Parse(current_context, stateStr.ToLocalChecked());
v8::Local<v8::Value> stateVal;
if (maybeStateVal.ToLocal(&stateVal)) {
threadObj
->Set(current_context,
String::NewFromUtf8(capture_from_isolate, "pollState",
NewStringType::kInternalized)
.ToLocalChecked(),
stateVal)
.Check();
}
threadObj
->Set(current_context,
String::NewFromUtf8(capture_from_isolate, "pollState",
NewStringType::kInternalized)
.ToLocalChecked(),
stateStr.ToLocalChecked())
.Check();
}
}

Expand All @@ -487,18 +482,13 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args) {
capture_from_isolate, result.stack_trace.async_state.c_str(),
NewStringType::kNormal);
if (!stateStr.IsEmpty()) {
v8::MaybeLocal<v8::Value> maybeStateVal =
v8::JSON::Parse(current_context, stateStr.ToLocalChecked());
v8::Local<v8::Value> stateVal;
if (maybeStateVal.ToLocal(&stateVal)) {
threadObj
->Set(current_context,
String::NewFromUtf8(capture_from_isolate, "asyncState",
NewStringType::kInternalized)
.ToLocalChecked(),
stateVal)
.Check();
}
threadObj
->Set(current_context,
String::NewFromUtf8(capture_from_isolate, "asyncState",
NewStringType::kInternalized)
.ToLocalChecked(),
stateStr.ToLocalChecked())
.Check();
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
},
"dependencies": {
"detect-libc": "^2.0.4",
"node-abi": "^3.73.0"
"node-abi": "^3.89.0"
},
"devDependencies": {
"@sentry-internal/eslint-config-sdk": "^9.22.0",
Expand Down
14 changes: 6 additions & 8 deletions scripts/check-build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function clean(err) {
return err.toString().trim();
}

function recompileFromSource() {
async function recompileFromSource() {
console.log('Compiling from source...');
let spawn = child_process.spawnSync('node-gyp', ['configure'], {
stdio: ['inherit', 'inherit', 'pipe'],
Expand All @@ -31,6 +31,8 @@ function recompileFromSource() {
console.log(clean(spawn.stderr));
return;
}

await import('./copy-target.mjs');
}

if (fs.existsSync(binaries.target)) {
Expand All @@ -44,14 +46,10 @@ if (fs.existsSync(binaries.target)) {
} else {
console.log(e);
}
try {
recompileFromSource();
} catch (e) {
console.log('Failed to compile from source');
throw e;
}

await recompileFromSource();
}
} else {
console.log('No precompiled binary found');
recompileFromSource();
await recompileFromSource();
}
17 changes: 15 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ interface Native {
registerThread(threadName: string): void;
registerThread(storage: AsyncStorageArgs, threadName: string): void;
threadPoll(enableLastSeen?: boolean, pollState?: object): void;
captureStackTrace<A = unknown, P = unknown>(): Record<string, Thread<A, P>>;
captureStackTrace(): Record<string, Thread<string, string>>;
getThreadsLastSeen(): Record<string, number>;
}

Expand Down Expand Up @@ -230,7 +230,20 @@ export function threadPoll(enableLastSeen: boolean = true, pollState?: object):
* Captures stack traces for all registered threads.
*/
export function captureStackTrace<A = unknown, P = unknown>(): Record<string, Thread<A, P>> {
return native.captureStackTrace<A, P>();
const result = native.captureStackTrace();

// Parse the asyncState and pollState from JSON strings back into objects
const transformedResult: Record<string, Thread<A, P>> = {};
for (const [key, value] of Object.entries(result)) {
const thread: Thread<A, P> = {
frames: value.frames,
...(value.asyncState && { asyncState: JSON.parse(value.asyncState) }),
...(value.pollState && { pollState: JSON.parse(value.pollState) }),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unhandled JSON.parse can crash entire stack capture

Medium Severity

The JSON.parse calls for asyncState and pollState have no error handling. The previous C++ code using v8::JSON::Parse gracefully skipped a property if parsing failed, preserving the rest of the thread data. Now, if JSON.parse throws for any thread's state, the entire captureStackTrace() call fails and all thread data (including stack frames for every thread) is lost. Since this function is used in watchdog/monitoring contexts, a total failure means monitoring silently stops working. Wrapping these in try-catch to match the old resilient behavior would be prudent.

Fix in Cursor Fix in Web

};
transformedResult[key] = thread;
}

return transformedResult;
}

/**
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2291,10 +2291,10 @@ negotiator@^1.0.0:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==

node-abi@^3.73.0:
version "3.75.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764"
integrity sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==
node-abi@^3.89.0:
version "3.89.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.89.0.tgz#eea98bf89d4534743bbbf2defa9f4f9bd3bdccfd"
integrity sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==
dependencies:
semver "^7.3.5"

Expand Down
Loading