Skip to content

chore: upgrade to 2.6.2-exodus.0 for RN 0.85#13

Open
raxodus wants to merge 6 commits into
v2.6.2from
exodus-2.6.2
Open

chore: upgrade to 2.6.2-exodus.0 for RN 0.85#13
raxodus wants to merge 6 commits into
v2.6.2from
exodus-2.6.2

Conversation

@raxodus
Copy link
Copy Markdown

@raxodus raxodus commented Apr 23, 2026

Summary

Rebase of Exodus patches onto upstream react-native-skia v2.6.2 (from v2.0.1).
Part of the RN 0.85 upgrade: ExodusMovement/exodus-mobile#38221.

Exodus Patches

Commit Type Description
d91da15c3 chore Remove upstream CI workflows and GitHub config
caf6becd6 chore @exodus/ scoping
8bc2a4dab security Object.create(null) for prototype pollution hardening

Fork Cleanup

Removed non-shipped directories to eliminate Copilot/CodeQL scan noise and reduce repo size (690 files, 76,592 lines deleted, ~111 MB):

Removed Reason
apps/ (~111 MB) Docs site, 4 example apps (paper, fabric, bare, web-app), test apps — triggered 5 Copilot findings (eval/property injection in example code, build script race conditions, log injection)
externals/ Git submodule references to Chromium's Skia and depot_tools — not needed for prebuilt binary distribution
.gitmodules Emptied (only contained the two externals/ submodule definitions)

Verified: packages/skia/package.json files field excludes both directories. Build-critical files intact: packages/skia/react-native-skia.podspec, packages/skia/android/build.gradle, packages/skia/android/CMakeLists.txt, packages/skia/scripts/install-libs.js, packages/skia/scripts/setup-canvaskit.js.

Upstream Changelog (v2.0.1 → v2.6.2)

351 commits across ~813 files changed. Key highlights:

  • Skia engine upgraded: m139 → m142 → m147 (Google Skia graphics library milestone bumps)
  • Prebuilt binary distribution migrated to npm: Native libs (.xcframework for Apple, .a for Android) split into separate npm packages (react-native-skia-android@147.1.0, react-native-skia-apple-ios@147.1.0, etc.) installed via a postinstall script (scripts/install-libs.js). Previously bundled directly in the package.
  • Skottie / Lottie animation support: New Skia.Skottie factory, CustomPropertyManager for dynamic property manipulation
  • New immutable Path API (Skia.PathBuilder): Replaces mutating path methods
  • WebGPU / Graphite (experimental): Dawn-based GPU backend behind SK_GRAPHITE=1 env flag; not enabled in standard builds
  • Reanimated 4 / react-native-worklets support: Peer dependency updated to >=3.19.1
  • iOS minimum deployment target raised: 13.0 → 14.0
  • JitPack repository removed from Android build.gradle (reduces supply-chain exposure)
  • Memory-pressure hints added to JSI host objects via setExternalMemoryPressure()
  • Out-of-bounds fix in TextPathCmd with unsupported glyphs
  • 54 Dependabot/dev-dependency security bumps (all dev/CI-only, not shipped)

Security Audit of Upstream Changes

Prototype Pollution Vectors

No new Object.assign, __proto__, or constructor[ patterns in production JS/TS source (packages/skia/src/). The diff contains only Object.keys, Object.values, and Object.entries calls, which are safe. Upstream does not apply Object.create(null) hardening — the Exodus 8bc2a4dab patch remains necessary. ✅

Command Injection

packages/skia/scripts/utils.ts exports $() (calling execSync()) and runAsync() (using spawn(..., { shell: true })). These are developer build scripts only — not included in published npm files and never executed at runtime. The published install-libs.js postinstall script uses only fs.cpSync / fs.readdirSync / require.resolve — no shell execution.

Android build.gradle and iOS react-native-skia.podspec pass build-time config via CMake -D flags. No user-controlled strings flow into shell commands. ✅

Network Requests

Two fetch() sites exist in lib source — both are web-only (.web.ts suffix, not bundled in React Native builds):

  1. JsiSkDataFactory.ts (fromURI) — present since before v2.0.1; unchanged
  2. SVG.web.ts (useSVG hook) — new; accepts app-code-supplied URL

No new runtime network requests in React Native builds. ✅

Binary Blobs / Non-reproducible Artifacts

Significant structural change: Prebuilt binaries moved from bundled-in-package to four separate npm packages:

  • react-native-skia-android@147.1.0
  • react-native-skia-apple-ios@147.1.0
  • react-native-skia-apple-macos@147.1.0
  • react-native-skia-apple-tvos@147.1.0

Published with npm OIDC provenance ("provenance": true). The install-libs.js postinstall copies binaries via fs.cpSync — no network calls at postinstall. No binary blobs committed directly to the git repository.

⚠️ Ensure Exodus fork pins exact binary package versions. Binaries cannot be source-audited but are covered by npm provenance attestation.

ReDoS Patterns

No new RegExp(userInput) or dynamically constructed regular expressions in production source. ✅

Unsafe Dynamic Code Execution

surface.eval() appears extensively but is confined to __tests__/e2e/ test files only — a test harness API, not callable at runtime. GLSL .eval(pos) in shader source files are compile-time constants for Skia's runtime shader API, not JS eval. No eval(), new Function(), or setTimeout(string) in production source. ✅

New Dependencies

Runtime dependencies:

Package Version Purpose Risk
react-native-skia-android 147.1.0 Prebuilt Android Skia libs Binary; pin version
react-native-skia-apple-ios 147.1.0 Prebuilt iOS Skia xcframeworks Binary; pin version
react-native-skia-apple-macos 147.1.0 Prebuilt macOS Skia xcframeworks Binary; pin version
react-native-skia-apple-tvos 147.1.0 Prebuilt tvOS Skia xcframeworks Binary; pin version
canvaskit-wasm 0.40.0 → 0.41.0 Web CanvasKit WASM Patch bump; web-only

All other new deps are dev/CI-only (jest 30, eslint 9, tsx, @blazediff/core).

Findings Summary

Category Status Notes
Prototype Pollution ✅ Clean No new vectors; Exodus hardening patch remains necessary
Command Injection ✅ Clean execSync/spawn only in dev build scripts
Network Requests ✅ Clean Web-only fetch; no new RN runtime network calls
Binary Blobs ⚠️ Review Binaries migrated to 4 npm packages; verify version pinning and provenance
ReDoS Patterns ✅ Clean No dynamic RegExp in production code
Unsafe Dynamic Code ✅ Clean surface.eval() is test-only
New Dependencies ⚠️ Review 4 binary sub-packages; all other additions are dev-only

Test Plan

  • Update src/package.json in exodus-mobile-upgrade worktree
  • Verify react-native-skia-{android,apple-ios}@147.1.0 in lockfile
  • yarn ios:base builds
  • yarn android:base builds
  • Skia-rendered graphics display correctly
  • Custom Skia effects work

raxodus added 3 commits April 23, 2026 16:35
Harden dictionary objects against prototype pollution:
- Recorder.ts: animatedProps
- utils.ts: materialize() result
- NativeSkiaModule.web.ts: views, deferedPictures, deferedOnSize
Rename package from @Shopify to @Exodus, update repository URLs,
set publishConfig to restricted, set version to 2.6.2-exodus.0
Comment thread apps/example/ios/enable-catalyst.js Outdated
}

if (content !== modified) {
fs.writeFileSync(projectPath, modified, 'utf8');
Comment thread apps/example/src/Tests/deserialize.ts Outdated
const assets: any = {};
if (value.assets) {
Object.keys(value.assets).forEach((key) => {
assets[key] = Skia.Data.fromBytes(new Uint8Array(value.assets[key]));
Comment thread apps/example/src/Tests/deserialize.ts Outdated
// eslint-disable-next-line no-eval
return eval(
`(function Main(){ const {Skia} = this; return (${value.source}); })`
`(function Main(){ const {Skia, TileMode} = this; return (${value.source}); })`
Comment thread packages/skia/scripts/utils.ts Outdated

// Run installation
install().catch((error) => {
console.error(`\n❌ Installation failed: ${error.message}\n`);
@raxodus raxodus changed the base branch from exodus-2.0.1 to v2.6.2 April 23, 2026 15:00
raxodus and others added 3 commits April 24, 2026 13:52
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JsiHostObject::get fell back to runtime.global().eval("Symbol.for('Symbol.dispose');")
for the dispose-symbol lookup on every property miss. With a Hermes build that
disables the JS eval builtin (e.g. @exodus/hermes-engine V1's eval-disable patch),
the first miss throws an uncaught jsi::JSError and crashes the app on boot —
reproducibly seen when Reanimated 4 worklets access `_isReanimatedSharedValue`
on a Skia HostObject during MapperRegistry setup.

Replace the eval invocation with a direct JSI traversal of `Symbol.for`. The
behaviour is equivalent (Symbol.for("Symbol.dispose") returns the same well-known
symbol regardless of how it is reached) and the path no longer depends on the JS
eval builtin being available.

Remove the now-unused `RNJsi::eval` helper (no other consumers in this file or
across the package).

Bump to 2.6.2-exodus.1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants