Sample report for a fictional app, generated with npx nativekeel plan --html report.html. Back to NativeKeel
acme-app health report
Start here
- Revoke the AWS access key ID in
src/App.js:6at the provider today. Removing it from code is not enough: every installed copy of the app still contains it. (Stop the leaks) - Set
targetSdkVersion = 36andcompileSdkVersion = 36inandroid/build.gradle. (Meet the Google Play target SDK) @react-native-community/push-notification-ios(no New Architecture support, unmaintained, native). Options:@react-native-firebase/messaging,expo-notifications.npm uninstall @react-native-community/push-notification-ios && npm install @react-native-firebase/messaging(Replace blocking and abandoned packages)
The full, ordered plan: npx nativekeel plan.
Security OWASP MASVS
Finding counts, not passed controls. Static checks of code, config and dependencies. They find the common, detectable mistakes; they do not replace a penetration test of the running app and its backend.
Findings
- CriticalReact Native 0.77.1 is no longer supported
Latest is 0.88.0. You are 11 minor versions behind; only the latest 3 minors receive fixes.
- CriticalNew Architecture is disabled (android + ios)
The legacy architecture was removed in 0.82. This app cannot upgrade past 0.81 until it migrates.
- CriticaltargetSdk 34 hides the app from users on newer Android versions
Since 2026-08-31 Google Play requires API 35+ for published apps to stay visible, and API 36+ to publish any update.
- CriticalAWS access key ID shipped inside the app (src/App.js:6)
Value AKIA…0E. Anyone who downloads the app can extract it. Revoke it now, then move the call to a server.
- Highreact-native-fs: unmaintained
No fixes will come for future React Native, iOS or Android releases. Alternatives: @dr.pogodin/react-native-fs, expo-file-system.
- High@react-native-community/push-notification-ios: no New Architecture support, unmaintained
Likely upgrade blocker (it may still run through the interop layer). Plan a replacement or a maintained fork. Alternatives: @react-native-firebase/messaging, expo-notifications.
- HighiOS AppDelegate is missing RCTAppDependencyProvider (ios/Acme/AppDelegate.mm)
Required since React Native 0.77. Without it, library Fabric components fall back to the legacy interop layer and send RCTEventEmitter events, which are not registered with the New Architecture: "RCTEventEmitter.receiveEvent() ... has not been registered as callable", logged in Debug, a launch crash in Release. It will surface as soon as the New Architecture is turned on. Add the import and
self.dependencyProvider = [RCTAppDependencyProvider new];before calling super (Swift and per-version snippets: https://nativekeel.com/fix/rcteventemitter-receiveevent-not-registered). - HighRelease signing password committed (android/gradle.properties)
MYAPP_RELEASE_STORE_PASSWORD. Anyone with repository access has them. Read them from ~/.gradle/gradle.properties or environment variables instead, and change them if the keystore was ever shared.
- Mediumreact-native-fast-image 8.6.3: New Architecture support needs 10.0.0
React Native Directory lists it without New Architecture support, but 10.0.0 ships a codegen spec. Update to 10.0.0 (check its changelog for breaking changes) instead of replacing it.
- MediumFlipper is still integrated
Flipper was removed from React Native in 0.74 and its native libraries are not 16 KB aligned. Remove the Flipper dependencies and initialization code before upgrading; use React Native DevTools instead.
- MediumPodfile loads @react-native-community/cli-platform-ios/native_modules
React Native provides use_native_modules! itself. The CLI used from React Native 0.80 no longer ships this file, so pod install fails with "cannot load such file". Delete the require_relative line.
- MediumXcode project lacks -DFOLLY_CFG_NO_COROUTINES (ios/Acme.xcodeproj/project.pbxproj)
OTHER_CPLUSPLUSFLAGS comes from an older template. From React Native 0.80 an Objective-C++ AppDelegate can fail to compile with "'folly/coro/Coroutine.h' file not found". Add "-DFOLLY_CFG_NO_COROUTINES=1" and "-DFOLLY_HAVE_CLOCK_GETTIME=1" next to "-DFOLLY_USE_LIBCPP=1" in both build configurations, as in the current template.
- MediumCleartext HTTP is allowed in the release manifest
android:usesCleartextTraffic="true" lets the app send data unencrypted. Keep it only in src/debug/AndroidManifest.xml (Metro needs it in development), or allow specific domains with a network security config.
- Medium1 icon-only button has no accessibility label (src/Header.js:8)
These touchables contain only an icon or image and set no accessibilityLabel. VoiceOver and TalkBack announce them as an unnamed "button", so a screen reader user cannot tell back from close from delete. Add accessibilityLabel describing the action ("Close", "Delete photo") and accessibilityRole="button". (WCAG 2.2: 1.1.1 Non-text Content, 4.1.2 Name, Role, Value)
- LowMainApplication.java calls getDefaultReactHost with two arguments (android/app/src/main/java/com/acme/MainApplication.java)
From React Native 0.80 this fails to compile from Java ("no suitable method found for getDefaultReactHost(Context,ReactNativeHost)"). Pass null as the third JSRuntimeFactory argument, or convert MainApplication to Kotlin like the template.
- LowiOS privacy manifest is generated at pod install, not committed
React Native 0.75+ creates PrivacyInfo.xcprivacy during
pod installwith the required-reason APIs of React Native and your pods. Commit it, then add what only you know: collected data types and tracking. App Store Connect checks those too. - Low1 change in React Native 0.87 affects this app (1 breaks code)
React headers imported without
React/stop the iOS build (ios/Acme/AppDelegate.h:1): Write#import <React/RCTBridge.h>instead of#import "RCTBridge.h"or#import <RCTBridge.h>; for a library, update it or patch it (patch-package). - Info1 patch-package patch: react-native-svg
Each was written against one library version and one React Native version. After every upgrade step check that each still applies and is still needed; a patch that adapted a library to an older React Native can break it on a newer one.
- InfominSdk 23 is below what current React Native supports (24)
Upgrading raises the minimum to Android API 24. Check how many of your users are on older Android versions first.
- InfoiOS deployment target 13.4 is below what current React Native supports (15.1)
Upgrading raises the minimum to iOS 15.1. Check how many of your users are on older iOS versions first.
- Info1 of 1 touchables have no accessibilityRole (src/Header.js:8)
TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback and Pressable do not tell VoiceOver or TalkBack that they are buttons: the user hears the text but not that it can be pressed. Set accessibilityRole="button" (or "link", "tab", "switch" where that fits), ideally once in your own Button component. This is missing in most React Native apps (94% of 676 open-source apps with touchables), so it is listed as a note.
4 packages are a major version behind (low)
| Package | Installed | Latest | |
|---|---|---|---|
| react-native-reanimated | 3.17.5 | 4.7.1 | native |
| react-native-gesture-handler | 2.25.0 | 3.4.0 | native |
| @react-navigation/drawer | 6.6.2 | 7.14.4 | |
| @react-native-community/slider | 4.5.7 | 5.2.1 | native |
Upgrade plan scope: Extra large
1. Stop the leaks
Leaked keys cost money and data from the moment someone finds them. Nothing else in this plan is as urgent.
- Revoke the AWS access key ID in
src/App.js:6at the provider today. Removing it from code is not enough: every installed copy of the app still contains it. - Move every call that needs a secret behind your own backend (a Cloud Function, API route or edge function). The app should only hold keys that are designed to be public.
- Move the signing passwords out of
android/gradle.propertiesinto~/.gradle/gradle.propertiesor CI environment variables; change them if the keystore ever left your control. - The old values stay in git history. Rotation is what makes them useless; rewriting history (e.g.
git filter-repo) is optional cleanup.
2. Set up a safety net
A build that compiles and launches can still crash after login or hide a button. On a real upgrade, UI flows caught three such bugs that build and launch checks missed.
- Before changing anything, write a few UI flows (Maestro is the quickest: maestro.mobile.dev): launch, sign-in with a test account, and every tab or main screen.
- Keep test credentials in environment variables, never in the repo. Never let a flow press a button that writes to production (sign-up, purchase, posting).
- Assert that no error banner or red screen appears, not only that the app is running: a native module that fails to load can leave the process alive behind a red screen.
- Run the flows on both platforms on the current version, then again at the end of every phase below.
3. Meet the Google Play target SDK
Below the required level Google Play rejects updates or hides the app from new users.
- Set
targetSdkVersion = 36andcompileSdkVersion = 36inandroid/build.gradle. - Read the Android behavior changes for every API level between 34 and 36; edge-to-edge display and foreground service types are the usual surprises.
- Build a release bundle and test on a device running the newest Android version.
4. Meet App Store and Google Play requirements
These block your next store submission regardless of anything else in this plan.
- Run
pod install, commit the generatedPrivacyInfo.xcprivacy, and add your collected data types and tracking to it (React Native only fills in the required-reason APIs).
5. Close security gaps
Attackers read app bundles, network traffic and logs. These are the ways in this app hands them something.
- Move
android:usesCleartextTraffic="true"toandroid/app/src/debug/AndroidManifest.xml, or allow only specific hosts with a network security config.
6. Remove legacy tooling
Leftovers from older templates break on newer React Native versions; removing them first shrinks the upgrade diff.
- When moving to React Native 0.80+, change
DefaultReactHost.getDefaultReactHost(context, host)inandroid/app/src/main/java/com/acme/MainApplication.javato passnullas a third argument. - In
ios/Acme.xcodeproj/project.pbxproj, add-DFOLLY_CFG_NO_COROUTINES=1and-DFOLLY_HAVE_CLOCK_GETTIME=1to OTHER_CPLUSPLUSFLAGS (Debug and Release) before moving to React Native 0.80. - Delete
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'fromios/Podfile;use_native_modules!comes from React Native. - Remove Flipper: delete the
com.facebook.flipperdependencies andFLIPPER_VERSIONon Android,use_flipper!/ Flipper config in the Podfile, and theReactNativeFlipperinitialization code. Use React Native DevTools instead.
7. Replace blocking and abandoned packages
These packages decide how far you can upgrade. Swap them while the app still runs on its current version, so every problem has one cause.
@react-native-community/push-notification-ios(no New Architecture support, unmaintained, native). Options:@react-native-firebase/messaging,expo-notifications.npm uninstall @react-native-community/push-notification-ios && npm install @react-native-firebase/messagingreact-native-fs(unmaintained, native). Options:@dr.pogodin/react-native-fs,expo-file-system.npm uninstall react-native-fs && npm install @dr.pogodin/react-native-fs
8. Update native modules on the current React Native version
Newer releases of native modules usually add New Architecture support. Pick the newest release that still supports your current React Native version; check each changelog for its minimum.
react-native-fast-image8.6.3 → the newest release that supports React Native 0.77 (latest is 10.0.0)react-native-reanimated3.17.5 → 3.18.x (the newest for React Native 0.77; later versions come with the React Native steps below)@react-native-community/slider4.5.7 → the newest release that supports React Native 0.77 (latest is 5.2.1)
9. Turn on the New Architecture on your current version
React Native 0.82 removed the legacy architecture. Switching now, before changing the React Native version, keeps the two kinds of breakage apart.
- In
ios/Acme/AppDelegate.mm, add#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>andself.dependencyProvider = [RCTAppDependencyProvider new];before calling super. Without it the switch causes a launch crash in Release. - Android: set
newArchEnabled=trueinandroid/gradle.properties. - iOS: set
"newArchEnabled": "true"inios/Podfile.properties.json(or runRCT_NEW_ARCH_ENABLED=1 bundle exec pod install). - Clean everything (
./gradlew clean,watchman watch-del-all, deleteios/buildandios/Pods). Switching architecture without a clean build fails on stale codegen output (e.g. missingNative…Specclasses). - Build debug and release on both platforms.
- Click through every screen that uses a native module. Interop-layer problems show up at runtime, not at build time.
10. Raise platform minimums
Current React Native no longer supports older OS versions. Check your analytics for how many users this drops before you ship.
- Android minSdk → 24
- iOS deployment target → 15.1
11. Upgrade React Native 0.77.1 → 0.88.0
Hopping a few minors at a time gives small diffs you can actually review and bisect when something breaks.
- 0.77.1 → 0.80.0: apply the diff from https://react-native-community.github.io/upgrade-helper/?from=0.77.1&to=0.80.0, move
react-native-screens4.14–4.18.x andreact-native-reanimated3.19.x (or 4.2.x, a major migration) andreact-native-gesture-handler2.28–2.31.x, reinstall pods, build both platforms, commit. - 0.80.0 → 0.83.0: apply the diff from https://react-native-community.github.io/upgrade-helper/?from=0.80.0&to=0.83.0, move
react-native-screens4.25.x andreact-native-reanimated4.6.x (a major migration: read its migration guide first), reinstall pods, build both platforms, commit. - 0.83.0 → 0.86.0: apply the diff from https://react-native-community.github.io/upgrade-helper/?from=0.83.0&to=0.86.0, move
react-native-screens4.26+ andreact-native-reanimated4.8.x andreact-native-gesture-handler2.32+ (2.x line) (or 3.0+, a major migration), reinstall pods, build both platforms, commit. - 0.86.0 → 0.88.0: apply the diff from https://react-native-community.github.io/upgrade-helper/?from=0.86.0&to=0.88.0, reinstall pods, build both platforms, commit.
- Bump
react,@react-native/*packages and the Metro/Babel config to the versions the Upgrade Helper shows for the target. - After every hop, re-check
patches/react-native-svg+15.15.5.patch: still applies (npx patch-package), still needed, not undoing what the new version expects. - If
pod installreports that a React Native dependency (fast_float, hermes-engine, …) differs from Podfile.lock, deleteios/Podsandios/Podfile.lockand install again; library pods keep the versions their podspecs pin. - Update the Gradle version by editing
android/gradle/wrapper/gradle-wrapper.properties:./gradlew wrapperfails because the new React Native Gradle plugin already needs the new Gradle.
12. What changes in React Native 0.87 for this app
Release notes list everything; these are the changes that touch this app's own code and settings, so you can fix them in the same commit as the upgrade instead of finding them in a crash report.
- React headers imported without
React/stop the iOS build (ios/Acme/AppDelegate.h:1). Write#import <React/RCTBridge.h>instead of#import "RCTBridge.h"or#import <RCTBridge.h>; for a library, update it or patch it (patch-package). [release notes](https://reactnative.dev/blog/2026/08/11/react-native-0.87)
13. Make it usable with a screen reader and large text
Unlabeled buttons and fixed text sizes lock out people who use VoiceOver, TalkBack or larger text. In the EU, the European Accessibility Act applies to shopping and consumer banking apps since June 2025.
- Add
accessibilityLabel(what the button does) andaccessibilityRole="button"to the icon-only buttons:src/Header.js:8. Check each with VoiceOver or TalkBack. - Add
accessibilityRole="button"(or the role that fits) to touchables, ideally in your shared Button component. - Go through the main flows once with VoiceOver and once with TalkBack, and once at the largest text size.
14. Update JavaScript-only packages
These do not block the upgrade. Do them last, one major at a time, following each migration guide.
@react-navigation/drawer6.6.2 → 7.14.4
15. Verify before you ship
An upgrade is done when the release build works on real devices, not when it compiles.
- Release builds on both platforms (
./gradlew bundleRelease, Xcode Archive). - Test on a low-end Android device and on the newest iOS and Android versions.
- Ship to internal testing / TestFlight first and watch crash-free sessions for a few days before a staged rollout.
- Run
npx nativekeelagain: the report should have no critical or high findings.