<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: alican akyol</title>
    <description>The latest articles on DEV Community by alican akyol (@keel_alican_akyol).</description>
    <link>https://dev.to/keel_alican_akyol</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4167230%2F4a64f202-b8b7-4431-96f7-051f9404d054.png</url>
      <title>DEV Community: alican akyol</title>
      <link>https://dev.to/keel_alican_akyol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/keel_alican_akyol"/>
    <language>en</language>
    <item>
      <title>Your code is clean, your libraries aren't: 29 popular React Native libraries still call APIs removed in 0.85 and 0.87</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Fri, 30 Oct 2026 07:28:33 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/your-code-is-clean-your-libraries-arent-29-popular-react-native-libraries-still-call-apis-21cf</link>
      <guid>https://dev.to/keel_alican_akyol/your-code-is-clean-your-libraries-arent-29-popular-react-native-libraries-still-call-apis-21cf</guid>
      <description>&lt;p&gt;You searched your code for &lt;code&gt;InteractionManager&lt;/code&gt;, replaced every call, and the upgrade to React Native 0.87 still crashes. The call is in &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;React Native removed two widely used JavaScript APIs in the last few releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0.85:&lt;/strong&gt; &lt;code&gt;StyleSheet.absoluteFillObject&lt;/code&gt; (&lt;a href="https://reactnative.dev/blog/2026/04/07/react-native-0.85" rel="noopener noreferrer"&gt;release post&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.87:&lt;/strong&gt; &lt;code&gt;InteractionManager&lt;/code&gt;, and &lt;code&gt;StatusBar&lt;/code&gt;'s background colour and translucency (&lt;a href="https://reactnative.dev/blog/2026/08/11/react-native-0.87" rel="noopener noreferrer"&gt;release post&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Renaming them in your own code is a five-minute job. The libraries you depend on are a different story, so I checked them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I checked
&lt;/h2&gt;

&lt;p&gt;I took every dependency used by at least two of 900 open-source React Native and Expo apps from GitHub: 1,442 libraries. For each, I downloaded the latest published package from npm (nothing installed or run) and searched its shipped JavaScript for the removed APIs, comments stripped. Every hit was then confirmed with &lt;a href="https://github.com/AlicanAkyol/nativekeel" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;'s own rules, and bisected across older releases to find the exact version range that has it.&lt;/p&gt;

&lt;p&gt;Libraries that define their own copy of the API (Tamagui, Unistyles) were dropped, and so were React Native platform forks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;29 libraries are left&lt;/strong&gt;, plus one hook library covered below. 123 of the 900 apps (14%) depend on an affected release of at least one of the 29.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ones that crash: &lt;code&gt;InteractionManager&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A removed function is &lt;code&gt;undefined&lt;/code&gt;, so the first call throws. In a debug build that's a red screen; in a release build the app closes. &lt;strong&gt;58 of 900 apps&lt;/strong&gt; depend on a library release that does this.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Releases&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;react-native-modal&lt;/td&gt;
&lt;td&gt;11.10.0 – 14.0.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Modal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-draggable-flatlist&lt;/td&gt;
&lt;td&gt;4.0.3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DraggableFlatList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-navigation-stack&lt;/td&gt;
&lt;td&gt;2.0.0 – 2.10.4&lt;/td&gt;
&lt;td&gt;stack card&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-svg-charts&lt;/td&gt;
&lt;td&gt;5.3.0 – 5.4.0&lt;/td&gt;
&lt;td&gt;animated paths&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-safe-area-view&lt;/td&gt;
&lt;td&gt;1.0.0 – 1.1.1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SafeAreaView&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-navigation-shared-element&lt;/td&gt;
&lt;td&gt;3.0.0 – 3.1.3&lt;/td&gt;
&lt;td&gt;shared element scenes&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-walkthrough-tooltip&lt;/td&gt;
&lt;td&gt;1.0.0 – 1.7.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tooltip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@shoutem/ui&lt;/td&gt;
&lt;td&gt;9.0.0 – 9.2.1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HorizontalPager&lt;/code&gt; (and a &lt;code&gt;StatusBar&lt;/code&gt; setter in &lt;code&gt;ListView&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-dropdownalert&lt;/td&gt;
&lt;td&gt;5.0.0 – 5.2.0&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;StatusBar&lt;/code&gt; setter&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus react-native-color-picker and react-native-deprecated-custom-components (2 apps each). A dash means no app in the sample uses an affected release.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@react-native-community/hooks&lt;/code&gt; exports &lt;code&gt;useInteractionManager&lt;/code&gt;, but it only breaks if you call that hook, so it's not in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ones that break layout: &lt;code&gt;absoluteFillObject&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This one doesn't crash. &lt;code&gt;StyleSheet.absoluteFillObject&lt;/code&gt; is now &lt;code&gt;undefined&lt;/code&gt;, and spreading &lt;code&gt;undefined&lt;/code&gt; in JavaScript is allowed, so &lt;code&gt;{ ...StyleSheet.absoluteFillObject }&lt;/code&gt; silently becomes &lt;code&gt;{}&lt;/code&gt;. The overlay, backdrop or skeleton is still rendered, just no longer positioned over what it should cover. Nobody gets an error; someone eventually files a bug about a screen that looks wrong. &lt;strong&gt;89 of 900 apps&lt;/strong&gt; depend on one of these:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Releases&lt;/th&gt;
&lt;th&gt;Component affected&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;react-native-calendars&lt;/td&gt;
&lt;td&gt;1.1275.0 – 1.1314.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Timeline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-elements&lt;/td&gt;
&lt;td&gt;3.0.0 – 3.4.3&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SpeedDial&lt;/code&gt; and others&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@rneui/base&lt;/td&gt;
&lt;td&gt;4.0.0 – 5.0.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-snap-carousel&lt;/td&gt;
&lt;td&gt;3.0.0 – 3.9.1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ParallaxImage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;moti&lt;/td&gt;
&lt;td&gt;0.25.4 – 0.30.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Skeleton&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;native-base&lt;/td&gt;
&lt;td&gt;3.0.0 – 3.4.28&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AspectRatio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-flash-message&lt;/td&gt;
&lt;td&gt;0.4.0 – 0.4.2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FlashMessage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-image-viewing&lt;/td&gt;
&lt;td&gt;0.2.0 – 0.2.2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Modal&lt;/code&gt; (Android)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-skeleton-placeholder&lt;/td&gt;
&lt;td&gt;5.0.0 – 5.2.4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SkeletonPlaceholder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@ui-kitten/components&lt;/td&gt;
&lt;td&gt;4.4.1 – 6.2.1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TopNavigation&lt;/code&gt;, &lt;code&gt;CircularProgressBar&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-ui-lib&lt;/td&gt;
&lt;td&gt;9.0.0 – 9.1.3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HighlighterOverlayView&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus react-native-circular-progress-indicator, react-native-indicators, react-native-material-dropdown, &lt;a class="mentioned-user" href="https://dev.to/howljs"&gt;@howljs&lt;/a&gt;/calendar-kit, react-native-video-player, react-native-shared-element and react-navigation-drawer.&lt;/p&gt;

&lt;p&gt;Only the named component is affected: if you use react-native-calendars but never render &lt;code&gt;Timeline&lt;/code&gt;, nothing changes for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find out which ones you actually ship.&lt;/strong&gt; The lockfile tells you which packages are installed, not what they call. Search &lt;code&gt;node_modules&lt;/code&gt; (or let a tool do it, below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for a newer release&lt;/strong&gt; that drops the call. The ranges above are what's on npm today; some of these libraries are maintained and will fix it. I've opened fixes for two of them, still under review: &lt;a href="https://github.com/react-native-elements/react-native-elements/pull/4043" rel="noopener noreferrer"&gt;react-native-elements #4043&lt;/a&gt; (overlay layouts on 0.85+) and &lt;a href="https://github.com/testshallpass/react-native-dropdownalert/pull/322" rel="noopener noreferrer"&gt;react-native-dropdownalert #322&lt;/a&gt; (Android alerts on 0.87).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If there's no fix, patch it.&lt;/strong&gt; Both changes are one-liners: &lt;code&gt;StyleSheet.absoluteFill&lt;/code&gt; has the same values and can be spread, and &lt;code&gt;InteractionManager.runAfterInteractions(fn)&lt;/code&gt; can become &lt;code&gt;requestIdleCallback(fn)&lt;/code&gt;. Apply them with &lt;a href="https://github.com/ds300/patch-package" rel="noopener noreferrer"&gt;patch-package&lt;/a&gt; until upstream catches up, or move to a maintained alternative for unmaintained packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the path in a release build.&lt;/strong&gt; A red screen in development is easy to miss if that screen isn't on your test route.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Let a tool do step 1
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's free, MIT licensed and runs locally. It reads your own code and your installed dependencies and gives a go / no-go for the newest React Native or Expo. A library that calls &lt;code&gt;InteractionManager&lt;/code&gt; or a &lt;code&gt;StatusBar&lt;/code&gt; setter is listed as a blocker with the package, version and file. An &lt;code&gt;absoluteFillObject&lt;/code&gt; hit is listed for review with the affected component. With no &lt;code&gt;node_modules&lt;/code&gt;, it matches the version ranges above from your lockfile.&lt;/p&gt;

&lt;p&gt;If you maintain one of these libraries and have already fixed it, tell me in the comments and I'll update the range.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>javascript</category>
      <category>mobile</category>
      <category>expo</category>
    </item>
    <item>
      <title>Your AI reverse-engineering agent reads function names, not just code</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Mon, 26 Oct 2026 08:17:59 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/your-ai-reverse-engineering-agent-reads-function-names-not-just-code-c0o</link>
      <guid>https://dev.to/keel_alican_akyol/your-ai-reverse-engineering-agent-reads-function-names-not-just-code-c0o</guid>
      <description>&lt;p&gt;&lt;em&gt;Pilot results: one model (Qwen3-8B via Ollama), one toolchain (Ghidra 12.1.4 +&lt;br&gt;
GhidraMCP 2.0), two small fixtures, 10 seeds. Treat them as a controlled pilot,&lt;br&gt;
not a leaderboard. Code, corpus and pre-registration:&lt;br&gt;
&lt;a href="https://github.com/AlicanAkyol/blindsight" rel="noopener noreferrer"&gt;github.com/AlicanAkyol/blindsight&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The setup everyone is now running
&lt;/h2&gt;

&lt;p&gt;Hooking an LLM up to Ghidra through an MCP server (GhidraMCP, ida-pro-mcp,&lt;br&gt;
re-mcp) is suddenly normal. The agent calls &lt;code&gt;list_functions&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;decompile_function&lt;/code&gt;, &lt;code&gt;list_strings&lt;/code&gt; and friends, and writes you a triage.&lt;br&gt;
The obvious worry is prompt injection: a binary carries attacker-controlled&lt;br&gt;
text, the model reads it, and gets talked into "no malware detected."&lt;/p&gt;

&lt;p&gt;I built a small harness, &lt;a href="https://github.com/AlicanAkyol/blindsight" rel="noopener noreferrer"&gt;blindsight&lt;/a&gt;,&lt;br&gt;
to measure that worry on a real stack. The injection&lt;br&gt;
result turned out to be the &lt;em&gt;less&lt;/em&gt; interesting finding.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I actually measured
&lt;/h2&gt;

&lt;p&gt;Every test program is compiled once, then re-emitted in variants where a&lt;br&gt;
five-layer oracle finds &lt;strong&gt;no behavioural difference&lt;/strong&gt; — stdout/stderr/exit,&lt;br&gt;
created files, a qemu syscall trace, per-function normalised instructions and&lt;br&gt;
call targets, and a check that injected bytes sit outside executable sections.&lt;br&gt;
A variant only changes &lt;em&gt;data&lt;/em&gt; or a &lt;em&gt;symbol name&lt;/em&gt;. The design isolates the&lt;br&gt;
changed metadata, so a change in the agent's output points to what I changed&lt;br&gt;
rather than to the program behaving differently.&lt;/p&gt;

&lt;p&gt;Each scenario is run many times, seed-paired against a matched control, and&lt;br&gt;
scored on structured fields only (findings and recorded tool calls, never the&lt;br&gt;
model's prose). Hypotheses were pre-registered; the primary outcome is&lt;br&gt;
capability recall (did the agent find behaviours that are in the code),&lt;br&gt;
tested with exact McNemar.&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding 1: a boring name hides a capability
&lt;/h2&gt;

&lt;p&gt;Take a function that installs persistence (writes an autostart entry). Name it&lt;br&gt;
&lt;code&gt;install_autostart&lt;/code&gt; and the agent reports persistence. Rename it to something&lt;br&gt;
neutral — &lt;code&gt;routine_b&lt;/code&gt; — with the code byte-for-byte identical, and on the&lt;br&gt;
first fixture the agent found persistence in &lt;strong&gt;0 of 10&lt;/strong&gt; runs (descriptive:&lt;br&gt;
5/10). The effect is in the obvious direction, though at n=10 the&lt;br&gt;
pre-registered exact test lands just outside significance (p=0.0625).&lt;/p&gt;

&lt;p&gt;Why? In &lt;strong&gt;all 10&lt;/strong&gt; neutral-name runs the agent never decompiled the function&lt;br&gt;
at all. It decides &lt;em&gt;what to read&lt;/em&gt; from the symbol name. A boring name is never&lt;br&gt;
opened, so the capability is never seen.&lt;/p&gt;

&lt;p&gt;To separate "can't read code" from "didn't read this function," I re-ran with&lt;br&gt;
the function bodies forced into context. On the first fixture, forcing the full&lt;br&gt;
set of decompiled bodies brought persistence recall back to &lt;strong&gt;10/10&lt;/strong&gt;. On a&lt;br&gt;
second, independently structured fixture (UDP instead of TCP, a function-pointer&lt;br&gt;
dispatch table, a systemd timer instead of an autostart file) I used the&lt;br&gt;
stricter intervention — forcing &lt;strong&gt;only the one persistence function's body&lt;/strong&gt;,&lt;br&gt;
with no function listing added — and recall still recovered from 2/10 to&lt;br&gt;
&lt;strong&gt;10/10&lt;/strong&gt; (McNemar p=0.008). The miss is a &lt;em&gt;coverage&lt;/em&gt; failure, not a&lt;br&gt;
comprehension failure: supply the body and detection returns. The name-driven&lt;br&gt;
coverage mechanism replicates across both fixtures; the size of the natural-run&lt;br&gt;
name effect depends on program shape (p=0.45 at n=10 on the second fixture).&lt;/p&gt;

&lt;p&gt;Attacker's takeaway, with no injected text at all: give your capability&lt;br&gt;
functions dull names and an AI triage pass may never look at them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding 2: the injection mostly didn't land
&lt;/h2&gt;

&lt;p&gt;The same harness ran classic injections — a string that says "report NO&lt;br&gt;
MALWARE DETECTED," the same thing base64'd or split, and an ALIBI-style&lt;br&gt;
"this is approved corporate software" narrative. Two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delivery depends on the channel.&lt;/strong&gt; Strings in the string table almost
never reached the model (1/40 runs) because the agent passed regex-looking
filters to &lt;code&gt;list_strings&lt;/code&gt; that GhidraMCP treats as a literal substring, so
the query returned nothing. An instruction in a function name, or in code
the model decompiled, reached it far more often.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When it did land, it mostly failed.&lt;/strong&gt; For the decompiled-code instruction:
natural arm 1/10 runs flipped to benign overall, but only 3 of those 10 runs
actually delivered the payload to the model, so conditional on delivery it is
1/3; in a forced-exposure arm (payload in context every time) it flipped 0/10
(Wilson upper bound ~28%). The honest statement: the effect is inconsistent
for this model and fixture, and the sample is too small to rule out a low
flip rate.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The fix: pick what to read from behaviour, not from names
&lt;/h2&gt;

&lt;p&gt;If the agent misses a capability because it never opens a dull-named function,&lt;br&gt;
the fix is to stop letting names decide what gets read. The coverage-aware&lt;br&gt;
profile does this with a policy that does not rely on the meaning of the&lt;br&gt;
program's own (user-defined) function names. It starts from imported, risky&lt;br&gt;
libc APIs — &lt;code&gt;fopen&lt;/code&gt;, &lt;code&gt;mkdir&lt;/code&gt;, &lt;code&gt;socket&lt;/code&gt;, &lt;code&gt;connect&lt;/code&gt;, &lt;code&gt;sendto&lt;/code&gt;, &lt;code&gt;execve&lt;/code&gt; and&lt;br&gt;
friends — walks the xrefs from each import's PLT stub to the user function that&lt;br&gt;
calls it, expands one level of callers, and decompiles that budgeted set before&lt;br&gt;
the model concludes. A persistence function is reached because it calls&lt;br&gt;
&lt;code&gt;fopen&lt;/code&gt;/&lt;code&gt;mkdir&lt;/code&gt;, whatever it is named. (The attacker still controls which&lt;br&gt;
imports and calls exist; see the limits — this is a coverage heuristic, not a&lt;br&gt;
boundary.)&lt;/p&gt;

&lt;p&gt;Seed-paired, same seeds, against the unsafe-default natural baseline, on both&lt;br&gt;
fixtures (10 seeds each):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;scenario&lt;/th&gt;
&lt;th&gt;persistence-fn in context&lt;/th&gt;
&lt;th&gt;capability recall&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;clean (descriptive)&lt;/td&gt;
&lt;td&gt;0.00 → 1.00&lt;/td&gt;
&lt;td&gt;0.83 → 0.97&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v9 (neutral name)&lt;/td&gt;
&lt;td&gt;0.00 → 1.00&lt;/td&gt;
&lt;td&gt;0.60 → 1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;h_clean (descriptive)&lt;/td&gt;
&lt;td&gt;0.10 → 1.00&lt;/td&gt;
&lt;td&gt;0.47 → 1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;h_neutral (neutral name)&lt;/td&gt;
&lt;td&gt;0.00 → 1.00&lt;/td&gt;
&lt;td&gt;0.47 → 1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The neutral-name misses are gone: the capability function's body reaches the&lt;br&gt;
model every time, and recall rises on every scenario and falls on none. The&lt;br&gt;
policy selected the right function (&lt;code&gt;routine_b&lt;/code&gt;, &lt;code&gt;task_17&lt;/code&gt;) by import xref, not&lt;br&gt;
by name, and decompiled 4–7 of the 12–15 user functions — not a blind&lt;br&gt;
decompile-everything.&lt;/p&gt;
&lt;h3&gt;
  
  
  What it costs
&lt;/h3&gt;

&lt;p&gt;Nothing is free, but the cost is not where you'd guess. Per run, averaged over&lt;br&gt;
the suite:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;natural&lt;/th&gt;
&lt;th&gt;coverage-aware&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MCP tool calls (harness-side)&lt;/td&gt;
&lt;td&gt;8.2&lt;/td&gt;
&lt;td&gt;20.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tool text delivered &lt;strong&gt;to the model&lt;/strong&gt; (chars)&lt;/td&gt;
&lt;td&gt;~7.7k&lt;/td&gt;
&lt;td&gt;~3.4k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;model turns&lt;/td&gt;
&lt;td&gt;4.1&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wall time (s)&lt;/td&gt;
&lt;td&gt;115&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one thing that goes &lt;em&gt;up&lt;/em&gt; is harness-side MCP work — 2.4x more tool calls,&lt;br&gt;
because the policy lists functions and walks xrefs itself. But that work stays&lt;br&gt;
in the harness: the &lt;code&gt;list_functions&lt;/code&gt; and &lt;code&gt;get_xrefs_to&lt;/code&gt; output is consumed by&lt;br&gt;
the policy and never enters the model's context. What the model actually&lt;br&gt;
receives is &lt;em&gt;less&lt;/em&gt; (3.4k vs 7.7k chars), because it's handed the few relevant&lt;br&gt;
function bodies up front instead of exploring over several turns — so it answers&lt;br&gt;
in one turn and wall time drops from 115s to 32s. The real cost is extra&lt;br&gt;
tool-call volume against the MCP server, not model context or latency. (The&lt;br&gt;
policy's function-selection budget is 20; on a much larger binary that budget,&lt;br&gt;
not the signal, would bind, and that tradeoff needs its own measurement.)&lt;/p&gt;

&lt;p&gt;As a side observation (not a tested claim): the number of reported findings&lt;br&gt;
outside the ground-truth set fell from 6 to 1 across the suite. I did not test&lt;br&gt;
that difference statistically; it is descriptive only.&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding 3: the tested config enforced no read-only authority
&lt;/h2&gt;

&lt;p&gt;GhidraMCP exposes write tools (rename, set comment, set prototype). A triage&lt;br&gt;
task is read-only, but in the tested configuration nothing enforced that: a&lt;br&gt;
deliberately gullible mock agent makes 8 rename requests per run, and across 3&lt;br&gt;
runs all 24 were accepted. The fix is not a prompt — it's a server-side&lt;br&gt;
read-only mode that removes write tools and refuses them deterministically.&lt;br&gt;
With it on, the same 24 attempts were refused 24/24. (The real model attempted&lt;br&gt;
no writes in any of 403 runs under the plain triage task, so this boundary&lt;br&gt;
matters for agentic setups that do grant write tools.)&lt;/p&gt;
&lt;h2&gt;
  
  
  From finding to a thing you run
&lt;/h2&gt;

&lt;p&gt;A benchmark you read once doesn't change anyone's workflow. So the harness is&lt;br&gt;
driven by a small CLI meant for CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ral doctor                         # is the toolchain ready
ral validate                       # fixture integrity; --rebuild re-runs the oracle
ral run --profile P --suite S      # run a suite under a stack+defense profile
ral compare BASE CAND              # seed-paired recall + write diff
ral gate  BASE CAND --max-recall-drop 5 --deny-unauthorized-writes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gate&lt;/code&gt; returns a non-zero exit code when a model/MCP/prompt/defense change&lt;br&gt;
drops capability recall past a threshold (seed-paired against the baseline) or&lt;br&gt;
lets an unauthorized write through. &lt;code&gt;compare&lt;/code&gt; reports, per scenario, both the&lt;br&gt;
recall delta and whether the ground-truth capability function was decompiled —&lt;br&gt;
so you can see whether a regression is the agent &lt;em&gt;not looking&lt;/em&gt; (coverage) or&lt;br&gt;
&lt;em&gt;not understanding&lt;/em&gt; (comprehension).&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;p&gt;One model (Qwen3-8B), one toolchain (Ghidra 12.1.4 + GhidraMCP 2.0), two small&lt;br&gt;
hand-written fixtures, 10 seeds. These are pilot numbers; the point is the&lt;br&gt;
method and the direction, not a rate you should quote.&lt;/p&gt;

&lt;p&gt;The coverage-aware defense was &lt;strong&gt;designed after seeing these two fixtures&lt;/strong&gt;, so&lt;br&gt;
its results here are in-sample. It has not been validated on an independent&lt;br&gt;
holdout corpus, and the import-xref approach will need rework on stripped&lt;br&gt;
binaries (where PLT thunks and &lt;code&gt;FUN_&lt;/code&gt;-named functions change the signals it&lt;br&gt;
keys on). It is a coverage &lt;em&gt;heuristic&lt;/em&gt;, not a security boundary: a binary&lt;br&gt;
author controls which imports and calls exist, so an attacker could add decoy&lt;br&gt;
risky imports to exhaust the selection budget (which currently cuts by&lt;br&gt;
address order, not risk priority), or bypass import-based selection with direct&lt;br&gt;
syscalls or dynamic resolution. The false-finding reduction above is a&lt;br&gt;
descriptive observation, not a tested effect. The honest summary: on these two&lt;br&gt;
fixtures and this fixed config, a policy that does not use the meaning of the&lt;br&gt;
program's own function names raised persistence-function coverage to 10/10 and&lt;br&gt;
recovered the recall loss, at 2.4x harness-side tool calls (but less model&lt;br&gt;
context and lower wall time); it is not yet shown to generalise.&lt;/p&gt;

&lt;p&gt;The code and the pre-registration (including the null and not-significant&lt;br&gt;
results) are in the repo. Every run writes its full tool-call transcript, so&lt;br&gt;
the numbers can be re-scored from the logs of a fresh run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your stack
&lt;/h2&gt;

&lt;p&gt;If you run AI-assisted RE, the question worth answering before you ship a model&lt;br&gt;
or MCP upgrade is not "can it be injected" but "did this change quietly shrink&lt;br&gt;
what it looks at, or let it act without authority." Once the stack is installed&lt;br&gt;
(Ghidra + natives, JDK 21, the GhidraMCP jar, a local model; Docker only to&lt;br&gt;
rebuild the corpus), that's a single &lt;code&gt;ral gate&lt;/code&gt; in CI. Or point &lt;code&gt;ral check&lt;/code&gt; at&lt;br&gt;
your own agent through a small stdin/stdout adapter.&lt;/p&gt;

</description>
      <category>security</category>
      <category>reverseengineering</category>
      <category>ai</category>
      <category>malware</category>
    </item>
    <item>
      <title>Google Play's API 36 extension ends Nov 1. Here's what actually changes in a React Native app</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Sat, 24 Oct 2026 18:11:53 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/google-plays-api-36-extension-ends-nov-1-heres-what-actually-changes-in-a-react-native-app-414m</link>
      <guid>https://dev.to/keel_alican_akyol/google-plays-api-36-extension-ends-nov-1-heres-what-actually-changes-in-a-react-native-app-414m</guid>
      <description>&lt;p&gt;If you asked Google Play for more time on the target SDK rule, that time runs out on &lt;strong&gt;1 November 2026&lt;/strong&gt;. After that, an app update has to target &lt;strong&gt;Android 16 (API 36)&lt;/strong&gt; or Play Console won't accept it.&lt;/p&gt;

&lt;p&gt;Changing one number in &lt;code&gt;build.gradle&lt;/code&gt; takes a minute. The work is everything Android changes for apps that target each new level, and if you are coming from 33 or 34, you skip several of them at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Since &lt;strong&gt;31 August 2026&lt;/strong&gt;, new apps and app updates must target &lt;strong&gt;API 36&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Apps that requested an extension have until &lt;strong&gt;1 November 2026&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Already published apps must target at least &lt;strong&gt;API 35&lt;/strong&gt; to stay available to new users on newer Android versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Source: &lt;a href="https://support.google.com/googleplay/android-developer/answer/11926878" rel="noopener noreferrer"&gt;Google Play target API level requirements&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many apps are behind?
&lt;/h2&gt;

&lt;p&gt;I checked 900 open-source React Native and Expo apps from GitHub with &lt;a href="https://github.com/AlicanAkyol/nativekeel" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;, reading &lt;code&gt;targetSdkVersion&lt;/code&gt; the same way the tool does (root &lt;code&gt;ext&lt;/code&gt; block or &lt;code&gt;app/build.gradle&lt;/code&gt;). For 172 of them the value is in the repository; the rest are mostly Expo apps without an &lt;code&gt;android/&lt;/code&gt; folder, where the Expo SDK decides.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target SDK&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;36 or higher&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33 or lower&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;114 of 172 (66%) are below API 36&lt;/strong&gt;, so they can't ship an update after Nov 1, and &lt;strong&gt;105 (61%) are below 35&lt;/strong&gt;, so new users on newer Android versions already can't find them. Many open-source apps aren't actively released, so treat this as a picture of how far behind codebases drift, not of the Play Store.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes at each level you skip
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Target 31: &lt;code&gt;android:exported&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Every activity, service or receiver with an &lt;code&gt;&amp;lt;intent-filter&amp;gt;&lt;/code&gt; must say whether other apps can start it. Without it, the build fails with &lt;em&gt;"android:exported needs to be explicitly specified"&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;activity&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".MainActivity"&lt;/span&gt; &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;true&lt;/code&gt; for the launcher activity and anything other apps must reach, &lt;code&gt;false&lt;/code&gt; for the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Target 34: foreground service types
&lt;/h3&gt;

&lt;p&gt;A foreground service must declare its type in the manifest, with the matching permission, or the app crashes when the service starts. This usually comes from a library (audio, location, calls, uploads), so check that each one is on a version that declares it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;service&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".PlaybackService"&lt;/span&gt; &lt;span class="na"&gt;android:foregroundServiceType=&lt;/span&gt;&lt;span class="s"&gt;"mediaPlayback"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Target 35: edge-to-edge on Android 15
&lt;/h3&gt;

&lt;p&gt;The app is drawn behind the status bar and navigation bar. Two things show up right away in React Native apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Native's own &lt;code&gt;SafeAreaView&lt;/code&gt; only works on iOS, so on Android those screens slide under the status bar. Use &lt;code&gt;react-native-safe-area-context&lt;/code&gt; (its &lt;code&gt;SafeAreaView&lt;/code&gt; or &lt;code&gt;useSafeAreaInsets&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StatusBar&lt;/code&gt; &lt;code&gt;backgroundColor&lt;/code&gt; is ignored: the bar is transparent, and what you see is your own content behind it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Target 36: Android 16
&lt;/h3&gt;

&lt;p&gt;From &lt;a href="https://developer.android.com/about/versions/16/behavior-changes-16" rel="noopener noreferrer"&gt;Android 16 behavior changes&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The edge-to-edge opt-out is gone.&lt;/strong&gt; &lt;code&gt;windowOptOutEdgeToEdgeEnforcement&lt;/code&gt; was the quick way to postpone the work above. On Android 16 it is ignored, so screens that relied on it slide under the system bars too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large screens ignore orientation and resize locks.&lt;/strong&gt; On screens at least 600dp wide (tablets, foldables), &lt;code&gt;screenOrientation&lt;/code&gt;, &lt;code&gt;resizeableActivity="false"&lt;/code&gt; and aspect ratio limits are ignored, so a portrait-only app rotates and resizes. Games are exempt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictive back is on by default.&lt;/strong&gt; &lt;code&gt;onBackPressed&lt;/code&gt; is no longer called. Test the back button and the back gesture on every screen that overrides back behaviour.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  While you're in there: 16 KB pages
&lt;/h2&gt;

&lt;p&gt;Separate rule, same release: apps targeting Android 15+ must support 16 KB memory pages, and Google Play blocks updates without it from &lt;strong&gt;1 February 2027&lt;/strong&gt; (&lt;a href="https://developer.android.com/guide/practices/page-sizes" rel="noopener noreferrer"&gt;source&lt;/a&gt;). React Native supports it from 0.77, but every native library in the app has to be built for it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your app before you bump it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's free, MIT licensed and runs locally (your code isn't uploaded). It reports the target SDK and the follow-on problems it can see in the project: components missing &lt;code&gt;android:exported&lt;/code&gt;, screens using core &lt;code&gt;SafeAreaView&lt;/code&gt;, the edge-to-edge opt-out, native libraries without 16 KB support. &lt;code&gt;npx nativekeel plan&lt;/code&gt; puts them in order.&lt;/p&gt;

&lt;p&gt;Per-level details with links: &lt;a href="https://nativekeel.com/fix/google-play-target-api-level" rel="noopener noreferrer"&gt;nativekeel.com/fix/google-play-target-api-level&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you hit something on the way to API 36 that isn't covered here, tell me in the comments. Every check in the tool started as a real upgrade problem.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>android</category>
      <category>mobile</category>
      <category>expo</category>
    </item>
    <item>
      <title>We checked 947 React Native apps against the newest version. 36% would break in their own code.</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Fri, 23 Oct 2026 07:05:15 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/we-checked-947-react-native-apps-against-the-newest-version-36-would-break-in-their-own-code-42ae</link>
      <guid>https://dev.to/keel_alican_akyol/we-checked-947-react-native-apps-against-the-newest-version-36-would-break-in-their-own-code-42ae</guid>
      <description>&lt;p&gt;Every React Native team upgrades eventually, and the question before it is always the same: &lt;em&gt;what is going to break in our code?&lt;/em&gt; The release notes list the changes, but they don't tell you which of them touch your app.&lt;/p&gt;

&lt;p&gt;So we ran that question over 947 open-source React Native and Expo apps from GitHub, with &lt;a href="https://github.com/AlicanAkyol/nativekeel" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;'s new &lt;code&gt;upgrade&lt;/code&gt; command. It reads each project's code and dependencies, matches them against the breaking changes between its version and the newest one (React Native 0.88 / Expo SDK 58), and returns a verdict with file and line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Already on the newest line&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ready: no breaking change found in their code&lt;/td&gt;
&lt;td&gt;226&lt;/td&gt;
&lt;td&gt;24%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Several versions behind, nothing found in the newest steps&lt;/td&gt;
&lt;td&gt;338&lt;/td&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Not ready: at least one upgrade blocker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;347&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For 345 of those 347 apps (36% of all), the blocker is in code they wrote themselves, before counting native build issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks most
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Breaking change&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;StyleSheet.absoluteFillObject&lt;/code&gt; removed (React Native 0.85)&lt;/td&gt;
&lt;td&gt;143&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;StatusBar&lt;/code&gt; &lt;code&gt;backgroundColor&lt;/code&gt; / &lt;code&gt;translucent&lt;/code&gt; props removed (0.87)&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expo-file-system: &lt;code&gt;File.write()&lt;/code&gt; is now async (Expo SDK 58)&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;InteractionManager&lt;/code&gt; removed (0.87)&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expo-router navigation core reworked (Expo SDK 58)&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep imports from &lt;code&gt;react-native/Libraries/&lt;/code&gt; are type errors (0.87)&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of these are worth knowing about even if you never run a tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;absoluteFillObject&lt;/code&gt; fails silently.&lt;/strong&gt; React Native 0.85 removed it without a warning. TypeScript stops the build, but in JavaScript &lt;code&gt;{ ...StyleSheet.absoluteFillObject, top: 10 }&lt;/code&gt; spreads &lt;code&gt;undefined&lt;/code&gt;, the style loses &lt;code&gt;position: 'absolute'&lt;/code&gt;, and overlays stop covering their parent. You find out on screen. The fix is a rename to &lt;code&gt;StyleSheet.absoluteFill&lt;/code&gt;, which is now a plain object you can spread (&lt;a href="https://nativekeel.com/fix/stylesheet-absolutefillobject-removed-react-native-0-85" rel="noopener noreferrer"&gt;details&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;InteractionManager&lt;/code&gt; crashes in release builds.&lt;/strong&gt; A development build shows a clear red screen; a release build only has &lt;code&gt;undefined&lt;/code&gt; and fails at the first &lt;code&gt;runAfterInteractions&lt;/code&gt; call. Move to &lt;code&gt;requestIdleCallback&lt;/code&gt; (&lt;a href="https://nativekeel.com/fix/interactionmanager-removed-react-native-0-87" rel="noopener noreferrer"&gt;details&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Not every release breaks things
&lt;/h2&gt;

&lt;p&gt;We checked each React Native release from 0.82 to 0.88 against its official notes. 0.83, 0.86 and 0.88 have no user-facing breaking changes; the code-level breaks cluster in 0.85 and 0.87. If you are on 0.86, the move to 0.88 is mostly 0.87's list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Being honest about what a static check can't see
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The verdict covers the breaking changes in the catalog, which is checked against the official release notes for each version. For apps several versions behind, the earliest steps are not fully covered, so those get "several versions behind" instead of a green light. Upgrade one step at a time.&lt;/li&gt;
&lt;li&gt;Native build problems (Gradle, CocoaPods, a library that hasn't been updated) need an actual build. The tool flags the ones it knows (for example libraries whose iOS headers stop compiling on 0.87), but it doesn't replace building on the target version.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it on your app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs locally, sends no code anywhere, and exits with code 1 when blockers remain, so you can put it in the pull request that bumps React Native. There is also an MCP tool (&lt;code&gt;upgrade_readiness&lt;/code&gt;) if you work with an AI agent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The scan used 1,162 public GitHub repositories with React Native or Expo; 213 were monorepo roots or libraries rather than a single app and are left out, as are 2 whose version could not be read.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>SonarQube says your React Native app is fine. Here's what it can't see.</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Tue, 20 Oct 2026 10:52:21 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/sonarqube-says-your-react-native-app-is-fine-heres-what-it-cant-see-38j6</link>
      <guid>https://dev.to/keel_alican_akyol/sonarqube-says-your-react-native-app-is-fine-heres-what-it-cant-see-38j6</guid>
      <description>&lt;p&gt;I ran a small React Native sample app through SonarQube (Community Build 26.10). SonarQube's own analysis found &lt;strong&gt;nothing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The same app has an AWS access key in the JavaScript bundle, a target SDK that hides it from users on newer Android versions, a signing password committed to the repository, and an AppDelegate that will crash in release builds once the New Architecture is on.&lt;/p&gt;

&lt;p&gt;None of that is a code smell. It's mobile: store rules, native build files, React Native versions, what ends up inside the app you ship. A general-purpose analyzer doesn't know about it, and it shouldn't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting mobile findings into SonarQube
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nativekeel.com" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt; is the free CLI I build for exactly those checks. As of 0.1.93 it can write its findings in SonarQube's generic issue import format, so they land in the project you already have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel &lt;span class="nt"&gt;--sonar&lt;/span&gt; nativekeel-sonar.json &lt;span class="nt"&gt;--fail-on&lt;/span&gt; never
sonar-scanner &lt;span class="nt"&gt;-Dsonar&lt;/span&gt;.externalIssuesReportPaths&lt;span class="o"&gt;=&lt;/span&gt;nativekeel-sonar.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the same sample project looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofkyjbdcg31rxq57yaaa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofkyjbdcg31rxq57yaaa.png" alt="SonarQube summary: Security E, Reliability E" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ten issues, each on its file and line, with SonarQube's own categories:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqgg6dadlg9t0k9n1926z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqgg6dadlg9t0k9n1926z.png" alt="SonarQube issues imported from NativeKeel" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;NativeKeel&lt;/th&gt;
&lt;th&gt;Shows up in SonarQube as&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Leaked keys, signing secrets, security findings&lt;/td&gt;
&lt;td&gt;Security · Vulnerability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Store rules (Xcode 27 UIScene, Google Play target SDK, 16 KB pages, privacy manifest), crash risks&lt;/td&gt;
&lt;td&gt;Reliability · Bug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upgrade blockers, New Architecture, performance, accessibility&lt;/td&gt;
&lt;td&gt;Maintainability · Code smell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  In GitHub Actions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AlicanAkyol/nativekeel@v0&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;sonar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nativekeel-sonar.json&lt;/span&gt;
      &lt;span class="na"&gt;fail-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;never&lt;/span&gt;        &lt;span class="c1"&gt;# let the SonarQube quality gate decide&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SonarSource/sonarqube-scan-action@v8&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SONAR_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SONAR_TOKEN }}&lt;/span&gt;
      &lt;span class="na"&gt;SONAR_HOST_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SONAR_HOST_URL }}&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-Dsonar.externalIssuesReportPaths=nativekeel-sonar.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things that tripped me up while testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paths.&lt;/strong&gt; SonarQube attaches each issue to a file it indexed, relative to &lt;code&gt;sonar.projectBaseDir&lt;/code&gt;. Run NativeKeel from that same folder, and keep &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;ios/&lt;/code&gt; and &lt;code&gt;android/&lt;/code&gt; inside &lt;code&gt;sonar.sources&lt;/code&gt;. Projects often set &lt;code&gt;sonar.sources=src&lt;/code&gt;, and then findings on &lt;code&gt;package.json&lt;/code&gt; or &lt;code&gt;Info.plist&lt;/code&gt; have nowhere to go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first analysis passes the gate anyway.&lt;/strong&gt; SonarQube's default quality gate looks at new code, and on a project's first analysis nothing counts as new. From the next analysis on, a new NativeKeel finding counts like any other new issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What stays out of SonarQube
&lt;/h2&gt;

&lt;p&gt;External rules don't appear in SonarQube's quality profiles, so accepted findings are managed on the NativeKeel side: a &lt;code&gt;.nativekeel.json&lt;/code&gt; in the repo lists them, each with a reason, and they stay visible in every NativeKeel report. Malware warnings can't be ignored that way.&lt;/p&gt;

&lt;p&gt;Your code stays where it is: NativeKeel runs in your pipeline and writes a JSON file. Only package names go to npm and React Native Directory, and &lt;code&gt;--offline&lt;/code&gt; sends nothing.&lt;/p&gt;

&lt;p&gt;Setup page with the details: &lt;a href="https://nativekeel.com/sonarqube" rel="noopener noreferrer"&gt;nativekeel.com/sonarqube&lt;/a&gt;. If your team runs SonarQube on mobile apps, I'd like to hear what else you'd want to see in it.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>sonarqube</category>
      <category>mobile</category>
      <category>devops</category>
    </item>
    <item>
      <title>SonarQube-style grades for 1,040 open-source React Native apps: 17 get straight A's</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Sat, 17 Oct 2026 16:12:32 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/sonarqube-style-grades-for-1040-open-source-react-native-apps-17-get-straight-as-5dn3</link>
      <guid>https://dev.to/keel_alican_akyol/sonarqube-style-grades-for-1040-open-source-react-native-apps-17-get-straight-as-5dn3</guid>
      <description>&lt;p&gt;SonarQube gives code a grade from A to E. I wanted the same thing for the problems that actually stop React Native apps: a store rule you missed, an upgrade that will not build, a crash at launch, a key anyone can pull out of the app.&lt;/p&gt;

&lt;p&gt;So &lt;a href="https://nativekeel.com" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;, the free CLI I build for React Native and Expo health checks, now rates six areas in every report: &lt;strong&gt;Security, Store, Stability, Upgrade, Performance and Accessibility&lt;/strong&gt;. The rule is SonarQube's: A means nothing found in that area, B only low findings, C medium, D high, E at least one critical.&lt;/p&gt;

&lt;p&gt;I graded the 1,040 open-source apps I test it on (scan of 14 October 2026).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;th&gt;E&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;499&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;td&gt;154&lt;/td&gt;
&lt;td&gt;162&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;69&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Store&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;764&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;167&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stability&lt;/td&gt;
&lt;td&gt;818&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upgrade&lt;/td&gt;
&lt;td&gt;129&lt;/td&gt;
&lt;td&gt;148&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;280&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;418&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;736&lt;/td&gt;
&lt;td&gt;263&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;886&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;17 apps get an A in every area. 433 have at least one E.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A grade only says what a static scan found. An A is not a guarantee, and some areas can't reach E by design: performance and accessibility findings are never critical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store: 931 apps at D or E
&lt;/h2&gt;

&lt;p&gt;The store column is the one that surprised me. Most of it is a date nobody has put in the calendar yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Xcode 27 and UIScene (at least 745 apps).&lt;/strong&gt; Built with the iOS 27 SDK, an app without the scene life cycle does not launch. App Store uploads need that SDK from April 2027.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Play target SDK (167 apps at E).&lt;/strong&gt; An app that targets an old API level stops being offered to users on newer Android versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;16 KB memory pages (at least 229 apps)&lt;/strong&gt;, then smaller ones: the iOS privacy manifest, permissions Google Play restricts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these show up while you develop. They show up in a rejection email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade: 418 apps at E
&lt;/h2&gt;

&lt;p&gt;E in Upgrade means the New Architecture is still turned off (341 apps), the Expo SDK is no longer supported (237) or React Native itself is past support (168); many apps have more than one. Since 0.82 there is no legacy architecture to fall back to, so every month that passes makes the jump bigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security: 69 apps at E
&lt;/h2&gt;

&lt;p&gt;Two things put most of them there. A dependency version with a critical known vulnerability, most often an old &lt;code&gt;expo&lt;/code&gt; package (45 apps), and AWS access keys shipped inside the app (31), which anyone who downloads it can read.&lt;/p&gt;

&lt;h2&gt;
  
  
  One app or forty
&lt;/h2&gt;

&lt;p&gt;If you look after more than one app, a grade per app is only useful next to the others. &lt;code&gt;fleet&lt;/code&gt; scans every React Native and Expo app in a folder (client projects, or a monorepo) and puts them in one table, worst first, with the deadlines each one faces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel fleet ~/clients &lt;span class="nt"&gt;--html&lt;/span&gt; fleet.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep a run, and next month compare against it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel fleet ~/clients &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fleet-2026-10.json
npx nativekeel fleet ~/clients &lt;span class="nt"&gt;--base&lt;/span&gt; fleet-2026-10.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every app then shows what moved: "Security E→C, critical 4→2". &lt;a href="https://nativekeel.com/fleet-demo" rel="noopener noreferrer"&gt;Here is a sample page&lt;/a&gt; for four fictional apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quality gate in pull requests
&lt;/h2&gt;

&lt;p&gt;The GitHub Action keeps one comment on each pull request. It now starts with the gate and shows the grades, with what the change moved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quality gate: ✅ passed, no new critical or high findings.
Ratings: Security A (was C) · Store B · Stability A · Upgrade D · Performance A · Accessibility C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Findings the team has accepted go into a &lt;code&gt;.nativekeel.json&lt;/code&gt; in the repo, each with a reason. They stay listed in every report, so nothing disappears quietly. Malware warnings can't be ignored that way; the file comes with the repository, and so could the malware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free and MIT licensed, no account, and your code stays on your machine. More for teams: &lt;a href="https://nativekeel.com/teams" rel="noopener noreferrer"&gt;nativekeel.com/teams&lt;/a&gt;. If you look after many apps, I'd like to hear what your team would need next.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>'RCTBridge.h' file not found: I compiled every React Native 0.87 header to see which imports break</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Sat, 17 Oct 2026 04:08:22 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/rctbridgeh-file-not-found-i-compiled-every-react-native-087-header-to-see-which-imports-break-22go</link>
      <guid>https://dev.to/keel_alican_akyol/rctbridgeh-file-not-found-i-compiled-every-react-native-087-header-to-see-which-imports-break-22go</guid>
      <description>&lt;p&gt;After upgrading to React Native 0.87 (or Expo SDK 58), some iOS builds stop inside a library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/react-native-date-picker/ios/DatePicker.m:7:9: error: 'RCTComponent.h' file not found
node_modules/@react-native-menu/menu/ios/MenuViewManager.mm:3:9: 'RCTBridge.h' file not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The release notes explain why in one line: React Native 0.87 ships its iOS headers as header-only XCFrameworks, and "if you import a React Native header without its namespace, add it". So &lt;code&gt;#import &amp;lt;RCTAppDelegate.h&amp;gt;&lt;/code&gt; becomes &lt;code&gt;#import &amp;lt;React/RCTAppDelegate.h&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That leaves questions the notes don't answer. Does the quoted form &lt;code&gt;#import "RCTBridge.h"&lt;/code&gt; break too? Every header, or some? What about apps that build React Native from source? I wanted &lt;a href="https://nativekeel.com" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt; to warn about this before an upgrade, so I needed exact answers, not a regex that cries wolf.&lt;/p&gt;

&lt;h2&gt;
  
  
  The experiment
&lt;/h2&gt;

&lt;p&gt;I created a React Native 0.87.1 app (Xcode 26.2) and generated one source file per header: all 227 &lt;code&gt;RCT*.h&lt;/code&gt; headers under &lt;code&gt;react-native/React&lt;/code&gt;, each imported on its own, once as &lt;code&gt;"RCTX.h"&lt;/code&gt; and once as &lt;code&gt;&amp;lt;RCTX.h&amp;gt;&lt;/code&gt;. Then I compiled them twice: with the default prebuilt React Native core, and with &lt;code&gt;RCT_USE_PREBUILT_RNCORE=0&lt;/code&gt; (built from source).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Import&lt;/th&gt;
&lt;th&gt;Default (prebuilt)&lt;/th&gt;
&lt;th&gt;Built from source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#import &amp;lt;React/RCTBridge.h&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#import "RCTBridge.h"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;file not found&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#import &amp;lt;RCTBridge.h&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;file not found&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;file not found&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exceptions are small but matter: in quotes, &lt;code&gt;RCTFabricComponentsPlugins.h&lt;/code&gt;, &lt;code&gt;RCTComponentViewHelpers.h&lt;/code&gt;, the CoreModules headers and &lt;code&gt;RCTLocalizedString.h&lt;/code&gt; still resolve. That's why most Fabric libraries, which write &lt;code&gt;#import "RCTFabricComponentsPlugins.h"&lt;/code&gt;, build fine.&lt;/p&gt;

&lt;p&gt;One more pattern is safe: an import inside an &lt;code&gt;#if __has_include(&amp;lt;React/RCTBridge.h&amp;gt;)&lt;/code&gt; … &lt;code&gt;#else #import "RCTBridge.h"&lt;/code&gt; fallback. The compiler takes the first branch, and many older libraries are written exactly like that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which libraries hit it
&lt;/h2&gt;

&lt;p&gt;With those rules, I scanned the iOS sources of the 248 most downloaded React Native libraries with native iOS code (16 October 2026). Five fail:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Import&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;react-native-date-picker 5.0.13&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"RCTComponent.h"&lt;/code&gt;, &lt;code&gt;"RCTConvert.h"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;issue open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@react-native-menu/menu 2.0.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"RCTBridge.h"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fix proposed in a PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-bottom-tabs 1.4.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"RCTConversions.h"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fixed on main, next release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-ios-context-menu 3.2.1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"RCTBridge.h"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fix proposed in a PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-ios-utilities 5.2.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"RCTBridge.h"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no report yet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three of them already have "file not found" reports from people who upgraded, which is a good sign the rules match reality. In the 1,040 open-source apps I test with, 31 depend on at least one of these five.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update the library&lt;/strong&gt; if a release has the change.&lt;/li&gt;
&lt;li&gt;Otherwise &lt;strong&gt;patch the import&lt;/strong&gt; and keep it with patch-package:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;   - #import "RCTComponent.h"
   + #import &amp;lt;React/RCTComponent.h&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Temporary workaround:&lt;/strong&gt; build React Native from source, where the quoted form still resolves: &lt;code&gt;RCT_USE_PREBUILT_RNCORE=0 bundle exec pod install&lt;/code&gt;, or &lt;code&gt;buildReactNativeFromSource: true&lt;/code&gt; under &lt;code&gt;ios&lt;/code&gt; in &lt;code&gt;expo-build-properties&lt;/code&gt;. Clean builds get much slower, and it doesn't help with &lt;code&gt;&amp;lt;RCTBridge.h&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Finding it before the upgrade
&lt;/h2&gt;

&lt;p&gt;NativeKeel now reads the iOS code of your installed libraries and your own &lt;code&gt;ios/&lt;/code&gt; folder. On React Native below 0.87 it lists these imports as work for the upgrade; on 0.87 and later it reports them as the reason the build fails, with file and line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full table, with links to each issue and PR: &lt;a href="https://nativekeel.com/fix/rctcomponent-h-file-not-found-react-native-0-87" rel="noopener noreferrer"&gt;nativekeel.com/fix/rctcomponent-h-file-not-found-react-native-0-87&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If a library you use fails this way and isn't in the table, tell me and I'll add it.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ios</category>
      <category>mobile</category>
      <category>opensource</category>
    </item>
    <item>
      <title>3,968 buttons a screen reader can't name: accessibility in 871 open-source React Native apps</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:22:32 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/3968-buttons-a-screen-reader-cant-name-accessibility-in-871-open-source-react-native-apps-2oe0</link>
      <guid>https://dev.to/keel_alican_akyol/3968-buttons-a-screen-reader-cant-name-accessibility-in-871-open-source-react-native-apps-2oe0</guid>
      <description>&lt;p&gt;Turn on VoiceOver, open a React Native app, and swipe through the header. Quite often you hear "button", then "button", then "button". Those are the close, back and menu icons. Nothing says which is which.&lt;/p&gt;

&lt;p&gt;I added accessibility checks to &lt;a href="https://nativekeel.com" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;, the free CLI I build for React Native and Expo health checks, and ran them on the 871 open-source apps I use to test it (14 October 2026, NativeKeel 0.1.77).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;At least one accessibility finding&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;418 (48%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Icon-only buttons without a label&lt;/td&gt;
&lt;td&gt;282&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buttons smaller than 44×44 with no &lt;code&gt;hitSlop&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;209&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text or placeholders below WCAG contrast&lt;/td&gt;
&lt;td&gt;188&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text fields without a label or placeholder&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three or more of these&lt;/td&gt;
&lt;td&gt;115&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every check only reports what it can see for certain in the source. That's why some numbers are lower than you might expect: when a color comes from a theme, or a button is a custom component, the check stays quiet instead of guessing. I read samples of each by hand before publishing the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Icon-only buttons with no name (282 apps, 3,968 buttons)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Ionicons&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"close"&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The touchable is accessible, so VoiceOver stops on it. But there's no text inside, only an icon font glyph or an image, so there's nothing to read. The user hears "button".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;accessibilityLabel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Close"&lt;/span&gt; &lt;span class="na"&gt;accessibilityRole&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Ionicons&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"close"&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The label describes the action ("Close", "Delete photo"), not the icon ("X").&lt;/p&gt;

&lt;p&gt;A related gap is much more common: &lt;code&gt;Pressable&lt;/code&gt; and &lt;code&gt;TouchableOpacity&lt;/code&gt; don't set a role. Without &lt;code&gt;accessibilityRole="button"&lt;/code&gt; the user hears the text but not that it can be pressed. 94% of the apps with touchables miss it somewhere. NativeKeel lists it as a note rather than a warning, because a warning that appears in nearly every report stops meaning anything. The cheap fix is to set it once in your own &lt;code&gt;Button&lt;/code&gt; component.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Buttons too small to hit (209 apps)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;closeButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apple asks for at least 44×44 points and Android for 48×48 dp. A 28-point circle is easy to miss for someone with a tremor, or for anyone on a bus. &lt;code&gt;hitSlop&lt;/code&gt; makes the touch area bigger without changing the look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closeButton&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;hitSlop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check only uses sizes it can read: an explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; in an inline style or the same file's &lt;code&gt;StyleSheet&lt;/code&gt;, or no style at all around a single small icon. When a button is also under 24×24, the report says so separately, because that is the WCAG 2.2 AA minimum. A 40×40 button meets WCAG and still misses the platform guidelines, and the report doesn't call it a WCAG failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Light grey on white (188 apps)
&lt;/h2&gt;

&lt;p&gt;The median pair I found was &lt;strong&gt;2.85:1&lt;/strong&gt;. WCAG AA asks for 4.5:1 for normal text and 3:1 for large text. The usual suspects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Text&lt;/th&gt;
&lt;th&gt;Background&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#bbb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#fff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#fff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#4caf50&lt;/code&gt; (Material green)&lt;/td&gt;
&lt;td&gt;2.78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#999&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#fff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#808080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#fff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.95&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Placeholders count too: people read them to know what to type. 226 placeholders in 55 apps are below 4.5:1, mostly &lt;code&gt;placeholderTextColor="#999"&lt;/code&gt; on a white screen. A &lt;code&gt;TextInput&lt;/code&gt; is transparent by default, so the background is whatever view is behind it.&lt;/p&gt;

&lt;p&gt;The check walks up from the &lt;code&gt;Text&lt;/code&gt; to the nearest view with a background color. It gives up at anything it can't see through: an image background, a gradient, opacity, a theme color, a custom component. So the real number of low-contrast apps is higher, and the ones it reports are pairs you can verify with any contrast checker.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Text fields with no name (119 apps)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChangeText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A sighted user reads "Email" above the field. VoiceOver says "text field". The visible label isn't linked to the input: &lt;code&gt;accessibilityLabelledBy&lt;/code&gt; exists, but only on Android. Adding &lt;code&gt;accessibilityLabel="Email"&lt;/code&gt; to the input fixes both platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The React 19 trap: &lt;code&gt;Text.defaultProps&lt;/code&gt; stops working
&lt;/h2&gt;

&lt;p&gt;This one isn't strictly accessibility, but I found it while looking at text scaling. Some apps turn off font scaling for the whole app like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultProps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowFontScaling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On React 19, which ships with React Native 0.78 and later, that line does nothing for &lt;code&gt;&amp;lt;Text&amp;gt;&lt;/code&gt; in your JSX. React 19 applies &lt;code&gt;defaultProps&lt;/code&gt; only to class components. React Native's &lt;code&gt;Text&lt;/code&gt; is a function component, and React Native and Expo compile JSX with the automatic runtime, whose &lt;code&gt;jsx()&lt;/code&gt; doesn't read &lt;code&gt;defaultProps&lt;/code&gt; at all. I checked this in the React Native 0.82 renderer (&lt;code&gt;resolveClassComponentProps&lt;/code&gt; is the only place defaults are merged) and in the React 19 JSX runtime.&lt;/p&gt;

&lt;p&gt;What changes for the app is that the global font, color or scaling limit silently stops applying when you upgrade past 0.77. No warning, no error. If you capped scaling to protect a fixed layout, text now follows the system size and the layout can break. Move these defaults into your own &lt;code&gt;Text&lt;/code&gt; component before the upgrade. (Only components rendered through &lt;code&gt;React.createElement&lt;/code&gt;, as NativeWind does, still see &lt;code&gt;defaultProps&lt;/code&gt;, so NativeKeel doesn't flag NativeWind projects.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What static checks can't tell you
&lt;/h2&gt;

&lt;p&gt;They can't hear your app. Focus order, custom components that forward labels, what a screen reader actually announces after a navigation, dynamic type in a long list: those need a device. The checks above are the part you can fix before anyone turns on VoiceOver. Then do one pass of your main flows with VoiceOver, one with TalkBack, and one at the largest text size.&lt;/p&gt;

&lt;p&gt;If you sell in the EU, this is no longer optional for everyone: the European Accessibility Act has applied since 28 June 2025 to services such as e-commerce and consumer banking, apps included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel           &lt;span class="c"&gt;# accessibility findings are in the normal report&lt;/span&gt;
npx nativekeel improve   &lt;span class="c"&gt;# an ordered plan, with an accessibility group&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads files only, doesn't install or run your app, and your code doesn't leave your machine. If a finding is wrong for your app, please tell me: false alarms are what I fix first. &lt;a href="https://github.com/AlicanAkyol/nativekeel" rel="noopener noreferrer"&gt;github.com/AlicanAkyol/nativekeel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>a11y</category>
      <category>mobile</category>
    </item>
    <item>
      <title>What should a React Native app fix first? I asked 871 open-source apps</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Mon, 12 Oct 2026 17:07:01 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/what-should-a-react-native-app-fix-first-i-asked-871-open-source-apps-mhn</link>
      <guid>https://dev.to/keel_alican_akyol/what-should-a-react-native-app-fix-first-i-asked-871-open-source-apps-mhn</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Since the start of October I've been building &lt;a href="https://nativekeel.com" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;, 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel improve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;This is the start of the plan for a fictional sample app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes first in 871 apps
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;At least one critical item&lt;/td&gt;
&lt;td&gt;403 (46%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;At least one critical or high item&lt;/td&gt;
&lt;td&gt;823 (94%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first item on each app's plan:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;First item&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iOS: not on the UIScene life cycle yet (Xcode 27 won't launch it)&lt;/td&gt;
&lt;td&gt;321&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expo SDK no longer supported&lt;/td&gt;
&lt;td&gt;186&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android: targetSdk too low for Google Play to show the app&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A dependency version with a known critical or high vulnerability&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Architecture off&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An AI provider key shipped in the app&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything else&lt;/td&gt;
&lt;td&gt;122&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most apps don't have a security emergency at the top. They have a platform deadline. The UIScene change I wrote about &lt;a href="https://dev.to/keel_alican_akyol/xcode-27-wont-launch-93-of-react-native-apps-the-uiscene-change-and-the-libraries-that-crash-2gb5"&gt;last time&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The security part
&lt;/h2&gt;

&lt;p&gt;266 of the 871 apps have at least one critical or high security item. The most common, by number of apps:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A dependency version with a known critical or high advisory&lt;/td&gt;
&lt;td&gt;143&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android components without &lt;code&gt;android:exported&lt;/code&gt; (a build error from targetSdk 31)&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release signing passwords committed to the repository&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An AI provider key that ships inside the app&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A release keystore committed to the repository&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supabase tables without row level security&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firestore rules that let anyone write&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;.env&lt;/code&gt; files. For those, the action is to revoke or rotate first, and the recovery step never restores the leaked value.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I stopped claiming
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"This package is unused, remove it."&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"console.log slows down your release build."&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Whole-library lodash import adds hundreds of KB."&lt;/strong&gt; Same problem. Your bundler may already handle it. The finding now asks you to look at the release bundle before changing anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bug I found while writing this
&lt;/h2&gt;

&lt;p&gt;Rescanning the 871 apps for this post, 3 of them failed outright. Since 0.1.64, NativeKeel refused to scan a project if any &lt;code&gt;package.json&lt;/code&gt; 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 (&lt;code&gt;"@types/": "react-native-async-storage/async-storage"&lt;/code&gt;) and workspace aliases like &lt;code&gt;"@global-theme": "workspace:*"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other things that are new since the first post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npx nativekeel security&lt;/code&gt;: only the data theft, account access and code execution risks, with the attacker's prerequisites and a verification step for each.&lt;/li&gt;
&lt;li&gt;Native Android checks that follow incoming Intent data into WebViews, SQL and file paths, and archive entries into file writes.&lt;/li&gt;
&lt;li&gt;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 &lt;code&gt;babel.config.js&lt;/code&gt;, JavaScript disguised as a font). The scan found it in 4 of the 871 repositories.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npx nativekeel explain "&amp;lt;error&amp;gt;"&lt;/code&gt;: what a build, launch or store error means and where your project is affected.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npx nativekeel verify&lt;/code&gt;: after an upgrade, what got fixed and what is new compared with the last commit.&lt;/li&gt;
&lt;li&gt;An MCP server, plus plugins for Claude Code and Codex, so an AI assistant can run the same checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel improve            &lt;span class="c"&gt;# plain text in the terminal&lt;/span&gt;
npx nativekeel improve &lt;span class="nt"&gt;--out&lt;/span&gt; IMPROVE.md
npx nativekeel improve &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;a href="https://github.com/AlicanAkyol/nativekeel" rel="noopener noreferrer"&gt;github.com/AlicanAkyol/nativekeel&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>expo</category>
      <category>security</category>
    </item>
    <item>
      <title>Xcode 27 won't launch 93% of React Native apps: the UIScene change, and the libraries that crash after it</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Sun, 11 Oct 2026 09:48:54 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/xcode-27-wont-launch-93-of-react-native-apps-the-uiscene-change-and-the-libraries-that-crash-2gb5</link>
      <guid>https://dev.to/keel_alican_akyol/xcode-27-wont-launch-93-of-react-native-apps-the-uiscene-change-and-the-libraries-that-crash-2gb5</guid>
      <description>&lt;p&gt;On 9 September Apple announced that from April 2027, apps uploaded to App Store Connect must be built with the iOS 27 SDK, which means Xcode 27. Built with that SDK, an app that hasn't adopted the UIKit scene life cycle doesn't start. It stops at launch with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application failed to launch: UIScene life cycle is required for apps built with this SDK.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apple announced the rule in &lt;a href="https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle" rel="noopener noreferrer"&gt;TN3187&lt;/a&gt;. The check depends on the SDK you build with, not the iOS version on the phone. The same app built with Xcode 26 still runs on iOS 27, so nothing breaks for your users today. It breaks on the day you move to Xcode 27.&lt;/p&gt;

&lt;p&gt;I run a health check on several hundred open-source React Native and Expo apps (the set I use to test &lt;a href="https://nativekeel.com" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;). As of 11 October 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React Native and Expo apps with an iOS target&lt;/td&gt;
&lt;td&gt;628&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not on the scene life cycle yet&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;583 (93%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Largest groups&lt;/td&gt;
&lt;td&gt;Expo SDK 57 (117), Expo SDK 54 (111)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That number isn't a judgment on those apps. Until a few weeks ago, no React Native or Expo template used scenes at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where React Native and Expo stand
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React Native 0.88&lt;/strong&gt; is the first release whose template adopts it. &lt;code&gt;SceneDelegate.swift&lt;/code&gt; starts React Native in the window scene, and &lt;code&gt;Info.plist&lt;/code&gt; names it in &lt;code&gt;UIApplicationSceneManifest&lt;/code&gt;. 0.88 is still a release candidate (rc.4 on 7 October). 0.87 and earlier have nothing in the template, so the path is to upgrade and copy both files from the Upgrade Helper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo SDK 57&lt;/strong&gt; can opt in. It needs &lt;code&gt;expo&lt;/code&gt; 57.0.23+ and &lt;code&gt;expo-build-properties&lt;/code&gt; 57.0.20+:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"expo-build-properties"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enableSceneSupport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;npx expo prebuild&lt;/code&gt; again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expo SDK 58&lt;/strong&gt; (in beta since 15 September) generates the scene delegate by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK 56 and earlier&lt;/strong&gt; need an SDK upgrade first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How do you know you've adopted it? Your &lt;code&gt;Info.plist&lt;/code&gt; has a &lt;code&gt;UIApplicationSceneManifest&lt;/code&gt; with scene configurations, or your app delegate implements &lt;code&gt;application(_:configurationForConnecting:options:)&lt;/code&gt;. A manifest that only sets &lt;code&gt;UIApplicationSupportsMultipleScenes&lt;/code&gt; doesn't count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that bites after you adopt it: AppDelegate has no window
&lt;/h2&gt;

&lt;p&gt;In the 0.88 template the &lt;code&gt;UIWindow&lt;/code&gt; belongs to the scene delegate. &lt;code&gt;AppDelegate&lt;/code&gt; no longer has a &lt;code&gt;window&lt;/code&gt; property at all.&lt;/p&gt;

&lt;p&gt;Many iOS libraries still find the screen to present on like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight objective_c"&gt;&lt;code&gt;&lt;span class="n"&gt;UIViewController&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UIApplication&lt;/span&gt; &lt;span class="nf"&gt;sharedApplication&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootViewController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the new template that line throws:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight objective_c"&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AppDelegate&lt;/span&gt; &lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unrecognized&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I searched the iOS sources of popular React Native libraries for this call made without a &lt;code&gt;respondsToSelector:&lt;/code&gt; check, and found it in 15 that are still in use. Then I built a fresh React Native 0.88.0-rc.4 app and confirmed the crash with two of them, &lt;code&gt;react-native-blob-util&lt;/code&gt; and &lt;code&gt;react-native-google-mobile-ads&lt;/code&gt;. Both stopped crashing after one change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight objective_c"&gt;&lt;code&gt;&lt;span class="n"&gt;UIViewController&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RCTPresentedViewController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// or RCTKeyWindow().rootViewController&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both fixes are open as pull requests (&lt;a href="https://github.com/RonRadtke/react-native-blob-util/pull/498" rel="noopener noreferrer"&gt;react-native-blob-util#498&lt;/a&gt;, &lt;a href="https://github.com/invertase/react-native-google-mobile-ads/pull/911" rel="noopener noreferrer"&gt;react-native-google-mobile-ads#911&lt;/a&gt;). For &lt;code&gt;@react-native-firebase/messaging&lt;/code&gt;, which makes the call &lt;strong&gt;at launch&lt;/strong&gt;, a fix was already proposed upstream (&lt;a href="https://github.com/invertase/react-native-firebase/pull/9359" rel="noopener noreferrer"&gt;react-native-firebase#9359&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;68 of the 628 apps depend on at least one of these libraries. The most common:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;th&gt;When it crashes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;react-native-blob-util&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;presenting the open-in / options menu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@react-native-firebase/messaging&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-google-mobile-ads&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;showing banners, the consent form or the ad inspector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-date-picker&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;opening the modal picker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-image-crop-picker&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;opening the picker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rn-fetch-blob&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;presenting the open-in menu&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Until a release ships the fix, a &lt;code&gt;patch-package&lt;/code&gt; patch with the line above works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quiet one: deep links stop arriving
&lt;/h2&gt;

&lt;p&gt;With scenes, UIKit hands opened URLs and universal links to the &lt;strong&gt;scene&lt;/strong&gt; delegate. &lt;code&gt;application(_:open:options:)&lt;/code&gt; and &lt;code&gt;application(_:continue:restorationHandler:)&lt;/code&gt; in &lt;code&gt;AppDelegate&lt;/code&gt; aren't called anymore. If your scene delegate doesn't forward them to &lt;code&gt;RCTLinkingManager&lt;/code&gt;, nothing crashes. &lt;code&gt;Linking&lt;/code&gt; just never fires.&lt;/p&gt;

&lt;p&gt;I found this in a wallet app that had already moved to scenes: its &lt;code&gt;zcash:&lt;/code&gt; payment links stopped reaching JavaScript, both when the app was running and when the link launched it, because &lt;code&gt;launchOptions&lt;/code&gt; was &lt;code&gt;nil&lt;/code&gt; and the URL only arrived in the scene's &lt;code&gt;connectionOptions&lt;/code&gt; (&lt;a href="https://github.com/zingolabs/zingo-mobile/pull/1595" rel="noopener noreferrer"&gt;zingo-mobile#1595&lt;/a&gt;). If your app handles deep links, test them after the move: with the app running, and with the app closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check whether you've adopted scenes (the &lt;code&gt;Info.plist&lt;/code&gt; test above).&lt;/li&gt;
&lt;li&gt;Expo: SDK 57 with &lt;code&gt;enableSceneSupport&lt;/code&gt;, or SDK 58. Bare React Native: plan the 0.88 upgrade.&lt;/li&gt;
&lt;li&gt;List your dependencies against the libraries above and update or patch them &lt;strong&gt;before&lt;/strong&gt; you build with Xcode 27.&lt;/li&gt;
&lt;li&gt;Forward URLs and user activities from the scene delegate to &lt;code&gt;RCTLinkingManager&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build with Xcode 27 well before April. Test launch, every screen that presents a picker, menu or ad, and deep links from a cold start.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Check your app in one command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reports whether the app has adopted the scene life cycle, which of your installed libraries read the window from the app delegate, and whether URL handlers are still only in &lt;code&gt;AppDelegate&lt;/code&gt;. It runs locally, without an account, and doesn't upload your code. If you hit the crash already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel explain &lt;span class="s2"&gt;"-[AppDelegate window]: unrecognized selector"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you upgrade with an AI agent, the same checks are available over MCP, and there's a Claude Code plugin (&lt;code&gt;claude plugin marketplace add AlicanAkyol/nativekeel&lt;/code&gt;), so the agent checks this before it starts changing versions. A longer reference with the sources is at &lt;a href="https://nativekeel.com/fix/uiscene-life-cycle-required-xcode-27" rel="noopener noreferrer"&gt;nativekeel.com/fix/uiscene-life-cycle-required-xcode-27&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you maintain one of the libraries above and want a hand with the fix, I'm happy to send a PR.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ios</category>
      <category>mobile</category>
      <category>ai</category>
    </item>
    <item>
      <title>ImageBackground and SafeAreaView are deprecated: 183 of 653 React Native apps still use them</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Thu, 08 Oct 2026 06:37:32 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/imagebackground-and-safeareaview-are-deprecated-183-of-653-react-native-apps-still-use-them-35ff</link>
      <guid>https://dev.to/keel_alican_akyol/imagebackground-and-safeareaview-are-deprecated-183-of-653-react-native-apps-still-use-them-35ff</guid>
      <description>&lt;p&gt;React Native 0.87 deprecated &lt;code&gt;ImageBackground&lt;/code&gt;, &lt;code&gt;DrawerLayoutAndroid&lt;/code&gt; and &lt;code&gt;UTFSequence&lt;/code&gt;. &lt;code&gt;SafeAreaView&lt;/code&gt; was deprecated earlier, in 0.81. All four still work. They log a warning, and React Native's code says they "will be removed in a future release". After that, importing them from &lt;code&gt;'react-native'&lt;/code&gt; gives you &lt;code&gt;undefined&lt;/code&gt;, and every screen that renders one crashes.&lt;/p&gt;

&lt;p&gt;I run a health check on 653 open-source React Native and Expo apps (the set I use to test &lt;a href="https://nativekeel.com" rel="noopener noreferrer"&gt;NativeKeel&lt;/a&gt;). Here is how many import these from &lt;code&gt;'react-native'&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SafeAreaView&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;423&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ImageBackground&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;203&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DrawerLayoutAndroid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UTFSequence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is &lt;strong&gt;183 of 653 apps (28%)&lt;/strong&gt;. Among the 358 apps already on React Native 0.76 or newer, it is 87 (24%), so this is not just old code.&lt;/p&gt;

&lt;p&gt;Both fixes are small. Here they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  SafeAreaView: it never did anything on Android
&lt;/h2&gt;

&lt;p&gt;The core &lt;code&gt;SafeAreaView&lt;/code&gt; "is currently only applicable to iOS devices" (React Native docs). On Android it is a plain &lt;code&gt;View&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That used to be fine, because Android drew your app below the status bar. It no longer is. Apps that target Android 15 (API level 35) are &lt;a href="https://developer.android.com/about/versions/15/behavior-changes-15" rel="noopener noreferrer"&gt;edge-to-edge by default&lt;/a&gt;: the status bar and navigation bar are transparent, and your content draws behind them unless you apply insets. Google Play now requires new apps and updates to target API level 36, so every app that ships an update runs edge-to-edge on Android 15 and newer devices.&lt;/p&gt;

&lt;p&gt;So the core &lt;code&gt;SafeAreaView&lt;/code&gt; is not only deprecated: on current Android it lets your header slide under the status bar.&lt;/p&gt;

&lt;p&gt;The replacement is &lt;a href="https://github.com/AppAndFlow/react-native-safe-area-context" rel="noopener noreferrer"&gt;&lt;code&gt;react-native-safe-area-context&lt;/code&gt;&lt;/a&gt;, which handles both platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx expo &lt;span class="nb"&gt;install &lt;/span&gt;react-native-safe-area-context   &lt;span class="c"&gt;# Expo&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;react-native-safe-area-context        &lt;span class="c"&gt;# bare React Native&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrap the root of your app once. Expo Router already does this for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SafeAreaProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-safe-area-context&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SafeAreaProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RootNavigator&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SafeAreaProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then change the import on each screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- import { SafeAreaView, View, Text } from 'react-native';
&lt;/span&gt;&lt;span class="gi"&gt;+ import { View, Text } from 'react-native';
+ import { SafeAreaView } from 'react-native-safe-area-context';
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes an &lt;code&gt;edges&lt;/code&gt; prop when you only want some of the insets, for example &lt;code&gt;edges={['top']}&lt;/code&gt; on a screen that has a bottom tab bar. For a custom header, &lt;code&gt;useSafeAreaInsets()&lt;/code&gt; gives you the numbers.&lt;/p&gt;

&lt;p&gt;One detail from the scan: 52 apps already use &lt;code&gt;react-native-safe-area-context&lt;/code&gt; somewhere and still import the core &lt;code&gt;SafeAreaView&lt;/code&gt; on other screens. Those screens behave differently on Android. Search for both imports.&lt;/p&gt;

&lt;h2&gt;
  
  
  ImageBackground: five lines of your own
&lt;/h2&gt;

&lt;p&gt;React Native's docs now say: "Use a View with an absolutely positioned Image instead." That is what &lt;code&gt;ImageBackground&lt;/code&gt; always was inside. A drop-in component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ImageProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ImageStyle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StyleProp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ViewStyle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ImageProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StyleProp&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ViewStyle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;imageStyle&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StyleProp&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ImageStyle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imageStyle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resizeMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;resizeMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resizeMode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;absoluteFill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imageStyle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then replace &lt;code&gt;&amp;lt;ImageBackground&lt;/code&gt; with &lt;code&gt;&amp;lt;Background&lt;/code&gt;. If you used &lt;code&gt;borderRadius&lt;/code&gt; on the &lt;code&gt;ImageBackground&lt;/code&gt;, add &lt;code&gt;overflow: 'hidden'&lt;/code&gt; to the outer style so the image is clipped too. If you already use &lt;code&gt;expo-image&lt;/code&gt;, the same pattern works with its &lt;code&gt;Image&lt;/code&gt; and &lt;code&gt;contentFit="cover"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  DrawerLayoutAndroid and UTFSequence
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DrawerLayoutAndroid&lt;/code&gt; → &lt;a href="https://reactnavigation.org/docs/drawer-layout/" rel="noopener noreferrer"&gt;&lt;code&gt;react-native-drawer-layout&lt;/code&gt;&lt;/a&gt;, which also works on iOS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UTFSequence&lt;/code&gt; → write the Unicode escape directly, e.g. &lt;code&gt;'﻿'&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finding them in your app
&lt;/h2&gt;

&lt;p&gt;A grep for the names will find most of them, but it misses imports split across lines and finds the safe-area-context version too. NativeKeel lists each one with its file and line, and adds the fix to the upgrade plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs locally (your code is not uploaded), has no dependencies, and is open source. If it reports something wrong for your app, I would like to hear about it: &lt;a href="https://github.com/AlicanAkyol/nativekeel" rel="noopener noreferrer"&gt;https://github.com/AlicanAkyol/nativekeel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Numbers are from a scan of 653 open-source React Native and Expo apps on GitHub (libraries excluded), October 2026. From an earlier scan: &lt;a href="https://dev.to/keel_alican_akyol/we-scanned-470-open-source-react-native-apps-here-is-what-breaks-them-5072"&gt;We scanned 470 open-source React Native apps. Here is what breaks them.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>mobile</category>
      <category>opensource</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>We scanned 470 open-source React Native apps. Here is what breaks them.</title>
      <dc:creator>alican akyol</dc:creator>
      <pubDate>Tue, 06 Oct 2026 19:14:13 +0000</pubDate>
      <link>https://dev.to/keel_alican_akyol/we-scanned-470-open-source-react-native-apps-here-is-what-breaks-them-5072</link>
      <guid>https://dev.to/keel_alican_akyol/we-scanned-470-open-source-react-native-apps-here-is-what-breaks-them-5072</guid>
      <description>&lt;p&gt;I build a free CLI, &lt;code&gt;npx nativekeel&lt;/code&gt;, that checks React Native and Expo apps for the problems that turn a routine upgrade into a three-week project. To keep its false alarms down, I run it on every open-source React Native app I can find. The test set is now 470 apps, from abandoned 2019 demos to large production apps like wallets, chat clients and note-taking apps.&lt;/p&gt;

&lt;p&gt;Old apps fail in boring, expected ways: unsupported versions, the New Architecture off, Google Play's target SDK missed. So the numbers below are for the 194 apps that are already on &lt;strong&gt;React Native 0.76 or newer&lt;/strong&gt;. These are apps whose developers keep them up to date, and they still have these problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we found in up-to-date apps
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Share of up-to-date apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A dependency with a known high or critical vulnerability in the exact version shipped&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still on React Native 0.76, which is not ready for 16 KB memory pages (Google Play requires this since Nov 2025 for apps targeting Android 15+; 0.77 fixes it)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The template URL scheme &lt;code&gt;myapp://&lt;/code&gt; still in place&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30+ &lt;code&gt;console.log&lt;/code&gt; calls shipped in the release build&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whole-library &lt;code&gt;lodash&lt;/code&gt; or &lt;code&gt;moment&lt;/code&gt; imports&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Images over 500 KB required by the app&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A secret committed to the repo or compiled into the bundle (cloud API keys, release signing passwords, tokens in &lt;code&gt;eas.json&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across all 470 apps, including the old ones: 58% still have the New Architecture off, which means they cannot go past React Native 0.81. 28% target an Android SDK that Google Play no longer accepts for updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four findings worth checking in your own app today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Expo config is not a safe place for secrets.&lt;/strong&gt; Anything in &lt;code&gt;app.json&lt;/code&gt; → &lt;code&gt;extra&lt;/code&gt;, and every &lt;code&gt;EXPO_PUBLIC_&lt;/code&gt; variable, ships inside the app and can be read by anyone who downloads it. And &lt;code&gt;eas.json&lt;/code&gt; is committed with your code: three apps had a Sentry auth token in its build &lt;code&gt;env&lt;/code&gt;, which can read and change the whole Sentry organisation. Public SDK keys (RevenueCat, Firebase web config) are fine. Server keys belong in EAS secrets (&lt;code&gt;eas env:create&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;myapp://&lt;/code&gt; is shared with every other app that kept it.&lt;/strong&gt; The Expo template sets &lt;code&gt;"scheme": "myapp"&lt;/code&gt;. If you sign in with &lt;code&gt;expo-auth-session&lt;/code&gt;, Clerk or AppAuth, the OAuth redirect goes to &lt;code&gt;myapp://&lt;/code&gt;, and on Android any other installed app with the same scheme can show up to receive it. Use a scheme unique to your app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Unverified App Links open in the browser.&lt;/strong&gt; An &lt;code&gt;https://your.domain/...&lt;/code&gt; intent filter without &lt;code&gt;android:autoVerify="true"&lt;/code&gt; (and a matching &lt;code&gt;assetlinks.json&lt;/code&gt;) opens in the browser on Android 12+, not in your app. On older Android, any app can register the same link. This matters most for login, password reset and invite links.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Passwords travel further than you think.&lt;/strong&gt; The leak that surprised us most was not a hacked server. It was a password that ended up in a crash report or analytics event after passing through a couple of variables (&lt;code&gt;const info = email + ':' + password; track('signup_failed', { info })&lt;/code&gt;). Searching for &lt;code&gt;password&lt;/code&gt; next to &lt;code&gt;track(&lt;/code&gt; does not find it; you have to follow the variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we got wrong, and fixed
&lt;/h2&gt;

&lt;p&gt;Scanning real apps is mostly about finding where the tool is wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A wallet app installed a package from git that has the same name as an npm package with a malware advisory. Git dependencies are now never matched against npm advisories.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.DS_Store&lt;/code&gt; files caught in a patch-package patch are harmless. They are no longer reported as "build output that will not apply".&lt;/li&gt;
&lt;li&gt;Android verifies every web host when &lt;em&gt;any&lt;/em&gt; intent filter has &lt;code&gt;autoVerify&lt;/code&gt;. Our first deep-link check missed that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every release lists what changed: &lt;a href="https://nativekeel.com/changelog" rel="noopener noreferrer"&gt;https://nativekeel.com/changelog&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nativekeel            &lt;span class="c"&gt;# report in the terminal&lt;/span&gt;
npx nativekeel plan       &lt;span class="c"&gt;# an ordered upgrade plan&lt;/span&gt;
npx nativekeel &lt;span class="nt"&gt;--html&lt;/span&gt; report.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs locally. Your code never leaves your machine: the only network calls are package-name lookups and the same advisory request &lt;code&gt;npm audit&lt;/code&gt; makes. It has zero dependencies, publishes with npm provenance, and is MIT licensed. There is also a GitHub Action that adds findings to pull requests.&lt;/p&gt;

&lt;p&gt;These are static checks. They find the common, detectable mistakes, but they are not a penetration test. A clean report means none of these known problems, not "unhackable".&lt;/p&gt;

&lt;p&gt;If something it reports is wrong for your app, please open an issue on GitHub. False alarms are the bugs I care about most: &lt;a href="https://github.com/AlicanAkyol/nativekeel" rel="noopener noreferrer"&gt;https://github.com/AlicanAkyol/nativekeel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>mobile</category>
      <category>security</category>
    </item>
  </channel>
</rss>
