Repository navigation
fix(core): Strip callback options before the native SDK starts - #6847
Conversation
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
A JS function crosses the bridge as a native callback that only accepts an array. sentry-cocoa reads `beforeSendLog` from the options dictionary and calls it with a `SentryLog`, which crashes the app. Remove every function value from the options before `initNativeSdk`, instead of a fixed list of names. This also covers `beforeSendSpan` and any callback the JavaScript SDK adds later. Fixes #6842 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| // filter out all the options that would crash native. | ||
| /* oxlint-disable typescript-eslint(no-unused-vars) */ | ||
| const { | ||
| beforeSend, | ||
| beforeBreadcrumb, | ||
| beforeSendTransaction, | ||
| beforeSendMetric, | ||
| integrations, | ||
| ignoreErrors, | ||
| logsOrigin, | ||
| profilingOptions, | ||
| androidProfilingOptions, | ||
| onNativeLog, | ||
| ...filteredOptions | ||
| } = options; | ||
| const { integrations, ignoreErrors, logsOrigin, profilingOptions, androidProfilingOptions, ...remainingOptions } = | ||
| options; | ||
| /* oxlint-enable typescript-eslint(no-unused-vars) */ | ||
|
|
||
| const filteredOptions = withoutFunctionValues(remainingOptions); | ||
|
|
||
| // Move profilingOptions into _experiments | ||
| // Support deprecated androidProfilingOptions for backwards compatibility | ||
| const resolvedProfilingOptions = profilingOptions ?? androidProfilingOptions; |
There was a problem hiding this comment.
Bug: The withoutFunctionValues function only performs a shallow filter, failing to remove functions from nested objects like mobileReplayOptions, which can cause a native crash.
Severity: HIGH
Suggested Fix
Modify the withoutFunctionValues function to recursively traverse the options object and remove any nested functions. This will ensure that objects like mobileReplayOptions are properly sanitized before being passed to the native bridge.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/core/src/js/wrapper.ts#L330-L341
Potential issue: The `withoutFunctionValues` function is intended to strip function
properties from an options object before it's passed to the native layer. However, it
only performs a shallow filter. If an option like `mobileReplayOptions` is passed, which
is an object that can contain a function property like `beforeErrorSampling`, this
nested function is not removed. The `typeof` check for `mobileReplayOptions` returns
"object", so the entire object, including the nested function, is passed through. This
will cause a crash in the native SDK when it tries to process the function.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Sounds valid but I think it is preexisting
antonis
left a comment
There was a problem hiding this comment.
LGTM 🚀
Added the ready-to-merge label for a full CI check before merging.
📲 Install BuildsAndroid
|
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 0b5a379+dirty | 472.78 ms | 533.16 ms | 60.38 ms |
| bf168a4+dirty | 418.21 ms | 489.74 ms | 71.53 ms |
| 1a2e7e0+dirty | 416.61 ms | 445.46 ms | 28.85 ms |
| a2585ce+dirty | 426.36 ms | 483.26 ms | 56.90 ms |
| 4e0b819+dirty | 420.56 ms | 470.08 ms | 49.52 ms |
| 9c84b9a+dirty | 520.74 ms | 539.38 ms | 18.64 ms |
| 2c735cc+dirty | 414.09 ms | 438.47 ms | 24.38 ms |
| 5c1e987+dirty | 423.52 ms | 471.64 ms | 48.12 ms |
| 04207c4+dirty | 459.19 ms | 518.54 ms | 59.35 ms |
| 68ae91b+dirty | 416.44 ms | 477.56 ms | 61.12 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 0b5a379+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| bf168a4+dirty | 49.74 MiB | 55.09 MiB | 5.35 MiB |
| 1a2e7e0+dirty | 49.74 MiB | 54.82 MiB | 5.07 MiB |
| a2585ce+dirty | 49.74 MiB | 55.36 MiB | 5.61 MiB |
| 4e0b819+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 9c84b9a+dirty | 49.74 MiB | 55.36 MiB | 5.62 MiB |
| 2c735cc+dirty | 43.75 MiB | 48.08 MiB | 4.33 MiB |
| 5c1e987+dirty | 43.75 MiB | 48.08 MiB | 4.33 MiB |
| 04207c4+dirty | 43.75 MiB | 48.12 MiB | 4.37 MiB |
| 68ae91b+dirty | 49.74 MiB | 54.79 MiB | 5.05 MiB |
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 8448c07+dirty | 3820.88 ms | 1217.17 ms | -2603.71 ms |
| c1f3a96+dirty | 3848.54 ms | 1218.81 ms | -2629.73 ms |
| 44abcc2+dirty | 3836.54 ms | 1223.33 ms | -2613.21 ms |
| e5bb5f6+dirty | 3826.14 ms | 1212.24 ms | -2613.90 ms |
| df5d108+dirty | 1225.90 ms | 1220.14 ms | -5.76 ms |
| 1d3572b+dirty | 3872.73 ms | 1235.00 ms | -2637.73 ms |
| ae37560+dirty | 3832.10 ms | 1219.09 ms | -2613.02 ms |
| 68672fc+dirty | 3841.58 ms | 1228.89 ms | -2612.69 ms |
| f170ec3+dirty | 3822.26 ms | 1218.33 ms | -2603.93 ms |
| ca9d079+dirty | 3835.63 ms | 1218.68 ms | -2616.95 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 8448c07+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| c1f3a96+dirty | 5.15 MiB | 6.87 MiB | 1.72 MiB |
| 44abcc2+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| e5bb5f6+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| df5d108+dirty | 3.38 MiB | 4.73 MiB | 1.35 MiB |
| 1d3572b+dirty | 4.98 MiB | 6.56 MiB | 1.58 MiB |
| ae37560+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 68672fc+dirty | 5.15 MiB | 6.71 MiB | 1.55 MiB |
| f170ec3+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
| ca9d079+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7436d0f+dirty | 429.58 ms | 452.52 ms | 22.94 ms |
| b0d3373+dirty | 412.17 ms | 452.84 ms | 40.67 ms |
| 7ac3378+dirty | 410.67 ms | 442.60 ms | 31.92 ms |
| 53a3f8e+dirty | 422.00 ms | 436.96 ms | 14.96 ms |
| e763471+dirty | 538.31 ms | 574.44 ms | 36.13 ms |
| c004dae+dirty | 404.60 ms | 430.67 ms | 26.07 ms |
| bfba737+dirty | 425.19 ms | 465.98 ms | 40.79 ms |
| f3215d3+dirty | 396.53 ms | 436.66 ms | 40.13 ms |
| 23598c3+dirty | 371.92 ms | 420.65 ms | 48.74 ms |
| ad66da3+dirty | 411.49 ms | 449.38 ms | 37.89 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7436d0f+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| b0d3373+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 7ac3378+dirty | 43.94 MiB | 48.99 MiB | 5.05 MiB |
| 53a3f8e+dirty | 50.56 MiB | 56.46 MiB | 5.90 MiB |
| e763471+dirty | 49.74 MiB | 54.85 MiB | 5.11 MiB |
| c004dae+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| bfba737+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| f3215d3+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 23598c3+dirty | 43.94 MiB | 49.02 MiB | 5.08 MiB |
| ad66da3+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
iOS (new) Performance metrics 🚀
|
📢 Type of change
📜 Description
A JS function crosses the bridge as a native callback that only accepts an
NSArray *.initNativeSdkremoved a fixed list of callback names, but it did not removebeforeSendLogorbeforeSendSpan. sentry-cocoa readsbeforeSendLogfrom the options dictionary and calls it with aSentryLog, which crashes the app. The SDK now removes every function value from the options before it starts the native SDK.💡 Motivation and Context
Fixes #6842. A fixed list of names goes stale each time the JavaScript SDK adds a new callback option, so the new filter works by value type instead.
💚 How did you test it?
Added unit tests for
beforeSendLog, forbeforeSendSpan, and for the general rule that no function reachesinitNativeSdk. Ranyarn test,yarn lint,yarn circularDepCheck, andyarn api-report:check.📝 Checklist
sendDefaultPIIis enabled.🤖 Generated with Claude Code