Skip to content

chore: upgrade to 15.15.4-exodus.0 for RN 0.85#14

Open
raxodus wants to merge 12 commits intov15.15.4from
exodus-15.15.4
Open

chore: upgrade to 15.15.4-exodus.0 for RN 0.85#14
raxodus wants to merge 12 commits intov15.15.4from
exodus-15.15.4

Conversation

@raxodus
Copy link
Copy Markdown

@raxodus raxodus commented Apr 23, 2026

Summary

Major version rebase of Exodus security patches onto upstream react-native-svg v15.15.4 (from ~v9.8.x).
The Exodus patches focus heavily on SVG parsing security: prop whitelisting, null-prototype hardening,
regex hardening, and svg-safe validation integration.
Part of the RN 0.85 upgrade: ExodusMovement/exodus-mobile#38221.

Exodus Patches

Commit Type Description
a8a53a88 chore @exodus/ scoping
f53a90d6 security Sanitize Android log messages to remove untrusted input
1a76d0c6 security Harden idPattern regex with strict anchored match
680324e2 security Integrate @exodus/svg-safe validation for XML parsing
3d64f485 security Prop whitelist and sanitizeProps for SVG XML parsing
94838542 security Null-prototype hardening for SVG parsing

Fork Cleanup

Removed non-shipped directories to eliminate Copilot/CodeQL scan noise and reduce repo size (635 files, 147,347 lines deleted):

Removed Reason
.github/ CI workflows — not shipped
apps/ (14 MB) Example/test apps — not shipped
e2e/ End-to-end tests — not shipped
__tests__/ Unit tests — not shipped
screenshots/ Test screenshots — not shipped
windows/ Windows platform support — not shipped
scripts/* (except rnsvg_utils.rb) Dev scripts (codegen-*.js, format-java.js, metal.js) — not shipped; triggered Copilot findings

Kept: scripts/rnsvg_utils.rb — referenced by RNSVG.podspec and listed in package.json files field.
Also removed windows from package.json files field since the directory was deleted.

Verified: Build-critical files intact: RNSVG.podspec, android/build.gradle, android/src/main/jni/CMakeLists.txt, package.json, scripts/rnsvg_utils.rb.

ReDoS Security Fixes

3 additional commits hardening shipped code against Regular Expression Denial of Service:

extractPolyPoints.ts — split regex

Before: /(?:\s+|\s*,\s*)/g — overlapping alternation where \s+ and \s*,\s* both match whitespace, causing catastrophic backtracking on repeated \t characters.
After: .split(/[\s,]+/).filter(Boolean) — single character class with no alternation or nested quantifiers.

extractTransform.ts — comma split

Before: /\s*,\s*/\s* quantifiers around literal comma, flagged for backtracking risk on whitespace-heavy input.
After: .split(',').map((s) => s.trim()) — no regex at all. Splits on literal comma, trims each result.

xml.tsx — locate() line extraction

Before: /(^|\n).*$/ and /.*(\n|$)/ in the locate() error-reporting function — .* with alternation anchors creates backtracking on long lines.
After: Pure string operations using lastIndexOf('\n') + slice and indexOf('\n') + slice. Zero regex in the security-sensitive XML parser path.

Note: The existing Exodus patch at 1a76d0c6 (idPattern hardening) only modified src/lib/util.ts, not src/xml.tsx, so the xml.tsx fix was still needed.

Remaining accepted finding: PathParser.java double→float narrowing — upstream Java code, standard graphics precision behavior (Android Canvas APIs use float). No fix needed.

Upstream Changelog (~v9.8.x → v15.15.4)

~860 commits across 6 major versions. Key changes:

  • v10: Native view support in masks; marker element; getBBox/getCTM/getScreenCTM APIs; CSS inline styles
  • v11–v12: TypeScript rewrite of JS layer; ForeignObject support; context-fill/context-stroke; strokeDasharray with Animated
  • v13: New Architecture (Fabric) support for Android and iOS; codegenNativeComponent adoption; color processing moved to native side
  • v14: RN 0.73–0.74 support; visionOS support; iOS minimum bumped to 12.4
  • v15.0–v15.14: RN 0.78+ required (dropped Paper); SVG filter pipeline (FeGaussianBlur, FeColorMatrix, FeBlend, FeFlood, FeMerge, FeOffset, FeComposite, FeDropShadow); CSS variable support; maskUnits/mask-type; 16 KB page size for Android; memory leak fixes
  • v15.15.x: Use-after-free crash fix for RCTImage on iOS; buffer dep removed in favor of atob for base64 decoding; shadow node metrics removed (revert); macOS build fixes

Security Audit of Upstream Changes

Prototype Pollution Vectors

Upstream v15 baseline: The tags object in src/xml.tsx was a plain { [tag: string]: ComponentType } dict and parsed props were initialized with {} — both open to prototype pollution if an attacker could inject __proto__ or constructor as an SVG tag name or attribute.

Upstream v15.0.0→v15.15.4: No upstream mitigations added. The tags dict was refactored into src/xmlTags.ts (typed as const), which narrows the type but does not set a null prototype.

Exodus patches address this: 94838542 applies Object.setPrototypeOf(tags, null) and changes all prop/style object literals to Object.create(null). This is correct and complete for the JS parsing layer. ✅ Mitigated.

Command Injection

No exec, spawn, child_process, or shell invocations in the runtime library source (src/). Native layers use only Android SDK drawing APIs and Apple CoreGraphics — no subprocess execution. ✅ Clean.

Network Requests

src/utils/fetchData.ts makes a fetch() call in fetchUriData() when a URI is passed to SvgUri — expected behavior for a URI-based SVG loader. The URI is app-supplied (not from parsed SVG content). The @exodus/svg-safe validation gate runs before parsing.

Notable improvement: v15.15.3 removed the buffer npm dependency in favor of atob() for base64 data-URI decoding, eliminating a transitive Node.js polyfill dependency. ✅ Acceptable.

Binary Blobs / Non-reproducible Artifacts

Three PEG-generated parser files are checked-in generated artifacts from pegjs/peggy, reproducible from .peg source files in the repo. No prebuilt native blobs found in src/. ✅ Clean.

ReDoS Patterns

The old idPattern regex /#([^)]+)'?\)?$/ was an unbounded character class with no anchoring — a ReDoS candidate on crafted inputs. Exodus patch 1a76d0c6 replaces it with /^(?:url\()?#([a-zA-Z0-9\-_]+)\)?$/, which is fully anchored, uses a strict character class, and has no backtracking ambiguity.

Other regexes audited: validNameCharacters, whitespace, RGB_RGBA_PATTERN, fontRegExp — all safe patterns with no user-controlled repetition. No new RegExp(userInput) patterns found. ✅ Mitigated.

Unsafe Dynamic Code Execution

No eval(), new Function(), or Function(string) calls in src/. PEG parsers use statically compiled rule functions, not dynamic code evaluation. ✅ Clean.

New Dependencies

Package Type Added in Assessment
warn-once@0.1.1 runtime v15.x Dev-warning helper; no network, no eval; low risk
@exodus/svg-safe runtime Exodus patch Internal validation library; adds security gate before parse
css-select@^5.1.0 runtime v15.0 baseline CSS selector engine for SvgCss; no network
css-tree@^1.1.3 runtime v15.0 baseline CSS parser; no network

Findings Summary

Category Status Notes
Prototype Pollution ✅ Mitigated Exodus patches apply null-prototype hardening to tags dict and all parsed object literals
Command Injection ✅ Clean No subprocess execution in library source
Network Requests ✅ Acceptable fetch() is app-URI-driven; buffer dep removed; @exodus/svg-safe validates before parsing
Binary Blobs ✅ Clean PEG-generated parsers reproducible from source
ReDoS ✅ Mitigated Old idPattern replaced with strict anchored pattern by Exodus patch; 3 additional ReDoS fixes applied
Unsafe Dynamic Execution ✅ Clean No eval/new Function in runtime source
New Dependencies ✅ Acceptable warn-once is benign; @exodus/svg-safe is a security addition
Log Injection (Android) ✅ Mitigated Exodus patch strips untrusted data from FLog messages
DOCTYPE Parsing ⚠️ Note Upstream fix routes DOCTYPE to a handler that skips to > — no external entity expansion (custom parser has no XML entity resolver)

Test Plan

  • Update src/package.json in exodus-mobile-upgrade worktree
  • yarn ios:base builds
  • yarn android:base builds
  • SVG icons render correctly
  • SVG XML parsing works with @exodus/svg-safe validation
  • No regression in SVG prop handling
  • SvgXml with DOCTYPE-bearing SVG content does not crash

raxodus added 6 commits April 23, 2026 16:35
- Object.setPrototypeOf(tags, null) to prevent prototype pollution via tag names
- Object.create(null) for parsed props and style objects
- Log unknown SVG tags instead of silently returning null
Add propWhitelist Set with all known safe SVG props. Apply sanitizeProps()
to SvgAst root props, astToReact child props, and SvgUri override props.
Unknown props are logged and stripped to prevent prop injection attacks.
Validate SVG XML source with @exodus/svg-safe before parsing.
On validation failure, log error and return null instead of parsing
potentially malicious SVG content.
Replace permissive /#([^)]+)'\''?\)?$/ with strict anchored pattern
/^(?:url\()?#([a-zA-Z0-9\-_]+)\)?$/ that only matches valid SVG ID
references (url(#id) or #id with alphanumeric/dash/underscore chars).
Strip mHref values from UseView FLog.w messages and mClipPath/mClipRule
from VirtualView FLog.w messages to prevent log injection.
Rename package, update repository URLs to ExodusForks,
set publishConfig to restricted, version 15.15.4-exodus.0,
remove upstream CI workflows and issue templates.
// Bezier Curve Approximation
float arc = ea - sa;
if (arc < 0 && clockwise) {
arc += Math.PI * 2;
Comment thread e2e/readFailedCases.ts Outdated
return JSON.parse(data);
} else {
const emptyObject = {};
fs.writeFileSync(filePath, JSON.stringify(emptyObject, null, 2), 'utf8');
Comment thread src/lib/extract/extractPolyPoints.ts Outdated
Comment on lines +7 to +9
return (polyPoints as string)
.replace(/[^eE]-/, ' -')
.split(/(?:\s+|\s*,\s*)/g)
Comment thread src/lib/extract/extractTransform.ts Outdated
if (typeof universal === 'number') {
x = y = universal;
} else if (typeof universal === 'string') {
const coords = universal.split(/\s*,\s*/);
Comment thread src/xml.tsx Outdated
const beforeExec = /(^|\n).*$/.exec(before);
const beforeLine = (beforeExec && beforeExec[0]) || '';
const after = source.slice(i);
const afterExec = /.*(\n|$)/.exec(after);
Comment thread scripts/codegen-utils.js Outdated

function exec(command) {
console.log(`[${ERROR_PREFIX}]> ` + command);
execSync(command);
Comment thread scripts/format-java.js Outdated
fileName !== undefined ? `${spotlessApply} ${fileArgument}` : spotlessApply;

// reformat code
execSync(command, writeToConsoleOnError);
Comment thread scripts/format-java.js Outdated
// file passed by lint-staged is now reformatted
if (fileName !== undefined) {
// so stage this file again after formatting
execSync(`git add ${fileName}`, writeToConsoleOnError);
Comment thread scripts/metal.js Outdated
const FILTERS_DIR = path.resolve(ROOT_DIR, 'apple/Filters/MetalCI');

function exec(command) {
execSync(command);
@raxodus raxodus changed the base branch from exodus-fork to v15.15.4 April 23, 2026 15:00
raxodus and others added 6 commits April 24, 2026 13:52
… __tests__, screenshots, windows)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace /(?:\s+|\s*,\s*)/g (overlapping alternation vulnerable to
catastrophic backtracking on repeated whitespace) with /[\s,]+/ split
plus filter(Boolean). Single character class, no alternation, no
nested quantifiers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace /\s*,\s*/ regex split with split(',').map(s => s.trim()) —
no regex at all. Eliminates potential backtracking on whitespace-heavy
input while producing identical results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace /(^|\n).*$/ and /.*(\n|$)/ in locate() with pure string
operations (lastIndexOf/indexOf + slice). Zero regex, zero
backtracking risk in the security-sensitive SVG XML parser path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… cast

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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