Skip to content

v9.0: native hardening, Zip Slip guard, aligned cross-platform progress, CI updates#356

Merged
plrthink merged 49 commits into
masterfrom
harden-native-modules
Jul 17, 2026
Merged

v9.0: native hardening, Zip Slip guard, aligned cross-platform progress, CI updates#356
plrthink merged 49 commits into
masterfrom
harden-native-modules

Conversation

@plrthink

@plrthink plrthink commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Hardens the Android and iOS native modules, fixes the TypeScript declarations, modernizes CI — and aligns progress reporting across platforms. Because several behavior changes are observable to existing consumers, this is released as v9.0.0. See MIGRATION.md for upgrade guidance.

Breaking changes

  • Progress semantics, aligned across platforms: unzip/unzipWithPassword progress is now byte-weighted per entry on both platforms (previously byte-level with within-file granularity on Android, and effectively start/end-only on iOS). zip/zipWithPassword report per-file counts; on iOS, the files-array variants now emit intermediate progress events at all (previously only 0% and 100%).
  • iOS unzip event filePath now carries the zip entry name (e.g. folder/file.txt) instead of the full destination path.
  • Android operations are serialized on a managed single-thread executor instead of one raw thread per call. Progress events are posted on the main thread, so the final 100% event may now arrive after the promise resolves.
  • Zip Slip protection (Android): extraction now rejects entries that would escape the destination directory (unzip, unzipWithPassword, unzipAssets).

Hardening and fixes

  • Android: replace raw Thread usage with a managed ExecutorService shut down from invalidate(); close all zip/stream handles with try-with-resources; close the AssetFileDescriptor in unzipAssets; flush output streams in StreamUtil.copy; non-string elements in the zip files array now reject the promise instead of crashing the app.
  • Android: Zip Slip validation extracted into a testable ZipSecurity utility, plus JUnit regression tests for ZipSecurity and the files-array conversion.
  • iOS: nullability annotations in RNZipArchive.h; drop the now-unused SSZipArchiveDelegate conformance; unzip rewritten on SSZipArchive's progressHandler API so per-entry progress and filePath updates actually work.
  • TypeScript: fix invalid default-parameter syntax in index.d.ts and move the react-native import to the top level.
  • Build: align Android minSdkVersion fallback with the documented API 23 minimum.
  • CI: Android/iOS build workflows for both playground apps; Expo added to the E2E matrix; publish workflow updated to modern action versions and Node 20; Android emulator ANR-dialog suppression (hide_error_dialogs + Maestro polling); pin playground-rn native dependencies to match playground-expo (fixes codegen failure from floating react-native-screens 4.26.x).

Verification

  • All 8 CI checks green (Android/iOS builds + Maestro E2E for both playground apps)
  • npm test passes (27/27); npm run lint passes
  • Android library compiles (:react-native-zip-archive:compileReleaseJavaWithJavac); Android unit tests pass (:react-native-zip-archive:testReleaseUnitTest, 8 tests)
  • TypeScript strict consumer typecheck against index.d.ts passes
  • iOS full PlaygroundRN workspace build passes (xcodebuild)

Notes

  • The iOS getUncompressedSize contract is preserved: it still resolves -1 on error to maintain backward compatibility.
  • package.json is bumped to 9.0.0 in this PR, so merging to master publishes to npm immediately.

Open in Devin Review

plrthink added 2 commits June 22, 2026 18:43
… CI updates

- Replace raw Thread usage with ExecutorService in Android module
- Close ZipFile/stream handles with try-with-resources on all paths
- Extract Zip Slip validation into ZipSecurity utility and add JUnit tests
- Emit progress events on the main thread
- Fix TypeScript declarations (optional params, import placement)
- Add iOS nullability annotations
- Align Android minSdkVersion fallback with docs (23)
- Update CI workflows to modern action versions and Node 20

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread android/src/main/java/com/rnziparchive/RNZipArchiveModule.java
Comment thread android/src/main/java/com/rnziparchive/RNZipArchiveModule.java
Comment thread android/src/main/java/com/rnziparchive/RNZipArchiveModule.java
plrthink added 27 commits June 22, 2026 19:02
…ndroid

- Re-add Expo matrix entries to e2e.yml for iOS and Android
- Rename e2e jobs to E2E iOS/Android (rn|expo) to match branch protection
- Add Android build workflow for RN and Expo
- Update iOS build workflow to build both RN and Expo
- Disambiguate artifact names by app in matrix jobs
Expo Modules Core uses @MainActor/Sendable which require Swift 5.5+
and fail under Xcode 16 strict concurrency when compiled as Swift 5.0.
Apply SWIFT_VERSION=5.5 and SWIFT_STRICT_CONCURRENCY=minimal to the
iOS build commands so both RN and Expo simulator builds pass.
SWIFT_VERSION=5.5 was rejected/ignored by Xcode 16, leaving ExpoModulesCore
in Swift 5 mode where @mainactor is unknown. Use 6.0 (matching the podspec)
along with SWIFT_STRICT_CONCURRENCY=minimal to keep RN builds relaxed.
Swift 5 mode on the Xcode 16 / Swift 6 compiler supports @mainactor
but treats strict concurrency diagnostics as warnings, avoiding the
hard errors seen in react-native-screens and expo-modules-core under
Swift 6 complete concurrency checking.
macos-latest uses Xcode 16.4 / Swift 6, which enforces strict concurrency
as errors for Expo's dependencies. macos-14 defaults to Xcode 15.4 with
Swift 5.10, avoiding those hard errors while keeping @mainactor support
via SWIFT_VERSION=5.5.
- Keep iOS jobs on macos-latest (Xcode 16 / Swift 6)
- Update react-native-screens 4.25.1 -> 4.25.2 in playground-expo
With react-native-screens bumped to 4.25.2, retry Swift 6.0 language
mode and SWIFT_STRICT_CONCURRENCY=minimal to see if the RNScreens
strict-concurrency errors are resolved.
react-native-screens 4.25.2 still fails under Swift 6 on macos-latest.
Use macos-14 with Xcode 15.4 / Swift 5.10 as the pragmatic workaround
until upstream dependencies are Swift-6 clean.
React Native 0.83.9 requires Xcode >= 16.1, so we must stay on
macos-latest. Use a Podfile post-install hook to pin:
- ExpoModulesCore -> Swift 6.0 (needs @mainactor support)
- RNScreens -> Swift 5.0 (still has Swift 6 strict-concurrency issues)

Remove the global SWIFT_VERSION/SWIFT_STRICT_CONCURRENCY xcodebuild
overrides so the per-target settings take effect.
The per-target Swift version fix belongs on Xcode 16 / macos-latest,
not macos-14. Restore the runner.
@plrthink
plrthink force-pushed the harden-native-modules branch from c37c7ab to 5446876 Compare June 23, 2026 17:17
@plrthink
plrthink force-pushed the harden-native-modules branch from 6e0862d to 9ce1c80 Compare June 23, 2026 22:30
plrthink added 5 commits June 30, 2026 08:54
The GitHub Actions Android emulator (API 29, software GPU) sometimes shows
a system-level "System UI isn't responding" dialog shortly after launch.
The Expo dev client is heavy enough to trigger it; the RN Android E2E does
not. The dialog is unrelated to react-native-zip-archive logic.

A one-shot Maestro conditional can miss the dialog because it appears after
the check. Replace it with an Android-only polling loop that taps "Wait"
while the playground screen is not yet visible.

Also give the emulator 4 cores and a 1024 MB heap to reduce UI strain.
cursoragent and others added 5 commits June 30, 2026 03:25
The E2E Android (expo) job failed because a system-level "System UI
isn't responding" dialog blocked Maestro from seeing the home screen.
The one-shot dismiss handler never fired for two reasons:

1. The YAML used a curly apostrophe (U+2019) but Android renders a
   straight apostrophe (U+0027), so the visibility check was skipped.
2. The dialog often appears mid-wait, after the single pre-check runs.

Replace the one-shot handler with a repeat-while loop that taps Wait
whenever the ANR dialog is visible, and re-add adb settings to suppress
ANR prompts on the CI emulator.

Co-authored-by: Perry <plrthink@gmail.com>
fix(e2e): dismiss Android ANR dialog blocking Expo E2E on CI
Replace non-standard adb settings keys (show_anr_messages,
anr_dialogs_disabled, anr_show_background) that may be no-ops on the
API 29 emulator with Settings.Global.HIDE_ERROR_DIALOGS, the AOSP key
used by CTS ANR tests to suppress system error dialogs.

Addresses Devin review on PR #358.

Co-authored-by: Perry <plrthink@gmail.com>
fix(e2e): use documented hide_error_dialogs ADB setting
Align progress semantics across platforms: unzip/unzipWithPassword now
report cumulative uncompressed bytes per entry, zip/zipWithPassword
report per-file counts.

- Android: weight unzip progress by FileHeader uncompressed sizes via a
  shared totalUncompressedSize helper; add missing terminal 100% event
  to unzipWithPassword
- iOS: extract via SSZipArchive progressHandler variant, accumulating
  zipInfo.uncompressed_size against payloadSizeForArchiveAtPath with a
  per-entry fallback; restore per-entry filePath updates
- iOS: pass progress handler in zipFiles/zipFilesWithPassword so they
  emit intermediate progress instead of only 0% and 100%
- Drop unused SSZipArchiveDelegate conformance and dead delegate method
- Document progress semantics in README
@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

plrthink added 2 commits July 17, 2026 12:41
playground-rn has no lockfile and used floating ranges, so CI resolved
react-native-screens 4.26.x (released 2026-07-16) whose codegen spec
fails RN codegen validation (setToolbarMenuElementOptions). Pin
react-native to exact 0.83.9 and screens/gesture-handler/
safe-area-context to the same tilde ranges as playground-expo.
- MIGRATION.md: add v8.x to v9.0 guide covering progress semantics,
  iOS filePath format, serialized operations, and Zip Slip rejection
- CHANGELOG.md: 9.0.0 unreleased entry for the hardening PR
@plrthink plrthink changed the title harden: managed executor, resource cleanup, Zip Slip guard, TS types, CI updates v9.0: native hardening, Zip Slip guard, aligned cross-platform progress, CI updates Jul 17, 2026
readableArrayToStringList called Dynamic.asString() on non-string
elements, which throws UnexpectedNativeTypeException outside any
try/catch and crashes the app. Validate element types up front and
reject the promise with a clear message; add JUnit regression tests.
@plrthink
plrthink merged commit 3dc58e2 into master Jul 17, 2026
8 checks passed
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