Dev.to Security ๐Ÿ” Cybersecurity ๐Ÿ‘ 0 ๐Ÿ“– 6 min read

What should a React Native app fix first? I asked 871 open-source apps

A health report with 30 findings tells you what is wrong. It doesn't tell you where to start, which findings depend on each other, or how to check that a fix actually worked. Since the start of October I've been buildin

A health report with 30 findings tells you what is wrong. It doesn't tell you where to start, which findings depend on each other, or how to check that a fix actually worked.

Since the start of October I've been building NativeKeel, a free CLI that checks React Native and Expo apps for upgrade blockers, store rejections, crash risks and security problems. Its newest command answers the "where do I start" question:

npx nativekeel improve

It doesn't add new detectors. It takes the findings the scanner already produces, puts them in order, and gives each one an action, what to check before changing anything, how to verify the change, and how to recover if it goes wrong. Nothing in your project runs; it reads files.

To see what it says in practice, I ran it on the 871 open-source React Native and Expo apps I use to test the tool (12 October 2026, NativeKeel 0.1.70).

What it looks like

This is the start of the plan for a fictional sample app:

1. [critical / security] AWS access key ID shipped inside the app (src/App.js:6)
   Action: Deactivate the IAM access key matching the AWS access key ID in
   src/App.js:6 immediately, then replace legitimate use through protected
   credentials. Remove the value from the app and review AWS activity logs;
   deleting source alone does not revoke the key.
   Verify: Confirm revocation or rotation in provider records, inspect access
   logs for misuse, and verify legitimate operations with the replacement.
   Recovery: Keep the exposed credential revoked. Restore legitimate service
   using a protected replacement; never roll back to the leaked value.

2. [critical / delivery] targetSdk 34 hides the app from users on newer Android versions
   Action: Set `targetSdkVersion = 36` and `compileSdkVersion = 36` in
   `android/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.

The order is simple and stated in the output: malware first, then severity, then security, stability, performance, store delivery, upgrade work and dependencies. There is no score and no estimated benefit.

What comes first in 871 apps

Apps
At least one critical item 403 (46%)
At least one critical or high item 823 (94%)

The first item on each app's plan:

First item Apps
iOS: not on the UIScene life cycle yet (Xcode 27 won't launch it) 321
Expo SDK no longer supported 186
Android: targetSdk too low for Google Play to show the app 130
A dependency version with a known critical or high vulnerability 84
New Architecture off 18
An AI provider key shipped in the app 10
Everything else 122

Most apps don't have a security emergency at the top. They have a platform deadline. The UIScene change I wrote about last time is first for 321 apps; for another 130 it's Google Play's target SDK rule, which already hides apps below API 35 from users on newer Android versions.

The security part

266 of the 871 apps have at least one critical or high security item. The most common, by number of apps:

Finding Apps
A dependency version with a known critical or high advisory 143
Android components without android:exported (a build error from targetSdk 31) 110
Release signing passwords committed to the repository 21
An AI provider key that ships inside the app 12
A release keystore committed to the repository 10
Supabase tables without row level security 4
Firestore rules that let anyone write 3

These are source findings, not proof that someone broke into these apps. A committed keystore matters only if it is the real release key; a Supabase table without row level security matters only if the anon key can reach it. The plan says so in each item's "Check" line, and it never treats zero findings as a sign that an app is secure.

Exposed credentials are the one place where the plan doesn't wait for more evidence. 43 apps have at least one item marked for containment: signing passwords (21), AI keys (12), keystores (10), private keys (4), and a few secrets in Expo or .env files. For those, the action is to revoke or rotate first, and the recovery step never restores the leaked value.

For AI keys the order is different on purpose: move the call behind your own server endpoint, then revoke the key. Revoking first would break the feature for everyone who already has the app installed. Until the key is revoked, the plan suggests a spending limit at the provider.

What I stopped claiming

While building this I went back over the wording of older checks, and some of it promised more than a static scan can know:

  • "This package is unused, remove it." It now says "review possible unused dependency". Dynamic imports, generated code and native auto-registration can all hide a real use from a source scan. If the installed dependency tree can't be read completely, no package is called unused at all.
  • "console.log slows down your release build." Maybe it does. But a Babel plugin or a development guard you didn't mention may strip it, and nothing was measured. Performance findings now say what was seen in the source and leave out speed or size gains nobody measured.
  • "Whole-library lodash import adds hundreds of KB." Same problem. Your bundler may already handle it. The finding now asks you to look at the release bundle before changing anything.

A tool that runs on someone else's code should be modest about what it knows. A false "remove it" costs more than a missed opportunity.

A bug I found while writing this

Rescanning the 871 apps for this post, 3 of them failed outright. Since 0.1.64, NativeKeel refused to scan a project if any package.json dependency name wasn't a valid package name. That check exists for a good reason: package names end up in file paths and commands, so a hostile name must not get there. But the 3 failures were ordinary: a typo ("@types/": "react-native-async-storage/async-storage") and workspace aliases like "@global-theme": "workspace:*".

0.1.69 skips such entries with a warning instead of refusing the scan. They still never reach a path, a command or a registry request.

Other things that are new since the first post

  • npx nativekeel security: only the data theft, account access and code execution risks, with the attacker's prerequisites and a verification step for each.
  • Native Android checks that follow incoming Intent data into WebViews, SQL and file paths, and archive entries into file writes.
  • A warning before you run a project that contains planted malware (a VS Code task that runs when you open the folder, code hidden after a long run of spaces in babel.config.js, JavaScript disguised as a font). The scan found it in 4 of the 871 repositories.
  • npx nativekeel explain "<error>": what a build, launch or store error means and where your project is affected.
  • npx nativekeel verify: after an upgrade, what got fixed and what is new compared with the last commit.
  • An MCP server, plus plugins for Claude Code and Codex, so an AI assistant can run the same checks.

Try it

npx nativekeel improve            # plain text in the terminal
npx nativekeel improve --out IMPROVE.md
npx nativekeel improve --json

No install, no account, and your code doesn't leave your machine. If a finding is wrong for your app, I'd like to know: false alarms are what I fix first. Issues are open at github.com/AlicanAkyol/nativekeel.

๐Ÿ“ฐ Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.