Secrets in Keychain/secure-store (never AsyncStorage); harden the supply chain (block install scripts, gate install-age); device trust via server-side attestation; route per-diff secret/injection checks to the review skill.
device-trust requirement, or jail-monkey installed → server-side Play Integrity / DeviceCheck (jail-monkey is client-side only and defeatable; keep it as a signal, not the gate)
web XSS surface → Trusted Types (needs React ≥19.3 under an enforcing CSP; plus sanitized dangerouslySetInnerHTML)
RSC/Server Functions on web → patched React (19.2.8 / 19.1.9 / 19.0.8 floors for the recurring server-function DoS; does not affect React Native)
dev server reachable beyond localhost (--host, server.host, a Docker port mapping) → patched Vite (≥8.0.5 / ≥7.3.2 for CVE-2026-39364; RB-E-BUILD owns the floor) and no secrets beside the dev server
want a cheap CI gate for RN-specific security footguns → rnsec (young — treat findings as leads, route confirmations to the review skill)
want an AI-driven data-flow scan of your own codebase → DeepSec (severity-rated findings, ~10–20% false positives; not auto-fix)
known-CVE check on dependencies → dependency CVE scanning (npm/pnpm audit in CI; depth via the review skill)
Options & tradeoffs
the field considered — and why each one isn’t the default here
option
tradeoff
evidence
jail-monkey (v3)
Client-side jailbreak/root + mock-location + debug detection; v3 adds New-Arch support. Defeatable on the device, so NOT a substitute for server-side Play Integrity / DeviceCheck. 3.0.0 (2026-04-28, verified vs npm).
171k/wk
react-native-keychain / expo-secure-store
Store tokens/secrets in Keychain/Keystore, never AsyncStorage (plaintext); RB-E-STORAGE owns the storage thread. react-native-keychain 10.0.0 (2025-03-23), expo-secure-store 57.0.4 (2026-09-11; both verified vs npm).
dependency CVE scanning
npm/pnpm audit for known CVEs; route per-diff depth to the review skill's security phase. Catches published CVEs only — it does not see a freshly poisoned version.
supply-chain hardening
Install-script blocking (npm v12 default), install-age gating (min-release-age / npmMinimalAgeGate), staged publishing, provenance, and npm's publish-time malware scan (since 2026-07-28) — provenance alone is not enough. npm 12.1.0 (2026-09-22, verified vs npm). See note: SUPPLY CHAIN.
Trusted Types (web)
Browser API that makes injection sinks (innerHTML) demand TrustedHTML/TrustedScript from your policy under CSP `require-trusted-types-for 'script'`. React is compatible from 19.3 (2026-09-09) — a CSP-enforced app needs React ≥19.3; dangerouslySetInnerHTML still needs an explicit policy. See note: TRUSTED TYPES.
rnsec (RN/Expo security scanner)
Zero-config static scanner — 63 rules across storage/network/auth/secrets/crypto, CI-ready (exit codes, PR comments, JSON/HTML reports). Young and single-author: a cheap CI layer, not a substitute for per-diff review. 1.3.0 (2026-04-19, verified vs npm); ~500★ (2026-07-09).
4k/wk
DeepSec (Vercel Labs)
Open-source AI security-scan harness: agents trace data flows on YOUR infra with YOUR Claude/Codex subscription; severity-rated findings (NOT auto-fix PRs), ~10–20% false-positive rate per Vercel; framework-agnostic. deepsec 2.3.10 (2026-09-23, verified vs npm).
npm weekly downloads (from the corpus's last signals run): rnsec 4k · jail-monkey 171k
Verified notes
Security for a React or React Native app is mostly about where secrets live, who is allowed to trust the device, and what your dependencies run — not about the framework. Secrets go in the platform keystore because AsyncStorage is plaintext; device trust is decided on the server because anything on the device can be tampered with; and the supply chain is the largest practical risk: the 2025–26 incidents (Shai-Hulud, the @tanstack/* compromise, axios RAT, keyv) were dependency and CI compromises, not React bugs. Live CVEs belong in a security feed; this entry records the durable rules and the floors that recur.
SUPPLY CHAIN (2026-07-10 to 2026-08-07, verified vs the GitHub changelogs, the GitHub umbrella post, the nuqs post and the Aikido keyv report): defenses now stack in four places. INSTALL: npm v12 blocks install scripts by default, and install-age gating is a platform default (3-day Dependabot cooldown). PUBLISH: npm staged publishing (draft → 2FA-gated validate → finalize) plus local tarball-hash reproduction is the maintainer-side answer to a TanStack-style CI compromise ("assume GitHub itself is compromised"). REGISTRY (2026-07-28): npm scans every newly published package for malware before it becomes installable — default and non-negotiable, outcomes publish / hold for manual review / block (with appeal), typically a 5–15 minute publish delay; packages with legitimate security-relevant capability must declare DUAL-USE via a `contentPolicy` field in package.json plus a text-only DISCLOSURE file, persisted across versions, with 2FA required. CI: actions/checkout v7 ships safer pull_request_target defaults, closing the 'Pwn Request' door the TanStack postmortem documents → pin checkout ≥7. GitHub's umbrella post (2026-07-28) bundles the program: staged publishing (May), npm v12 install-script defaults (June), a 72-hour read-only lock on high-impact accounts after email changes or 2FA-recovery use (June), Actions hardening, and the Dependabot cooldown (July). The keyv compromise (2026-08-04) is the live counterexample: a maintainer GitHub account takeover published poisoned versions WITH VALID GitHub-Actions provenance, a preinstall-hook dropper (downloads Bun, harvests npm/GitHub/AWS/Vault/Stripe/Slack credentials) and a self-replicating worm — 444 packages / 1,381 versions / ~2B combined monthly installs within a day, a week after registry scanning went default. Consequence: provenance alone is not enough, registry scanning catches only what it can detect, and install-script blocking + install-age gating remain the layers that bite — with egress control and Node's permission model for the execution-time door (NodeSource reading). Maintainers: budget the publish delay into release pipelines (this repo publishes via OIDC trusted publishing). The keyv incident was ongoing at last check; promote it to a reading when a definitive postmortem lands.
AI DEV-TOOLS ARE SUPPLY CHAIN (2026-07-10, verified vs Vercel's April-2026 bulletin): a compromised third-party AI tool (Context.ai) led through an employee's Google Workspace to decryption of customers' plaintext-decryptable "non-sensitive" env vars; npm packages were unaffected → mark platform env vars SENSITIVE, and treat AI dev-tools as supply-chain surface.
DEV SERVERS ARE ATTACK SURFACE (2026-09-21, verified vs GHSA-v2wj-q39q-566r and F5 Labs' August 2026 telemetry; React Status #491): CVE-2026-39364 is a HIGH-severity `server.fs.deny` bypass in the Vite dev server — query parameters such as ?raw or ?import&raw make its @fs route skip deny-list filtering and serve .env files, certificates or private source with a 200. Fixed in 8.0.5 and 7.3.2 (affected: >=8.0.0 <=8.0.4 and >=7.1.0 <=7.3.1); RB-E-BUILD owns the floor and the mechanism. It is only reachable when the dev server is exposed beyond localhost, and F5 measured a sustained August 2026 campaign hunting exactly that: 807 session-grouped attacks, ~32,000 events, cycling wordlists of env files, AWS keys, Azure tokens and IaC state files, from a cluster that also probed the Next.js middleware bypass CVE-2025-29927 → your threat model includes the tools you run while developing: dev servers and the credentials beside them are actively scanned whenever `--host`, `server.host` or a Docker port mapping makes them reachable.
RSC SCOPING (verified vs the React 2025-12-11 post and GHSA-wx67-qw84-cm4g): the React Server Components DoS family (CVE-2026-23864, CVSS 7.5; React2DoS CVE-2026-23869) and the Dec-2025 RSC source-exposure/RCE issues are SERVER-SIDE (react-server-dom-*; Next.js App Router) and do NOT affect React Native; first patched across React 19.0.5 / 19.1.6 / 19.2.5. The family recurs, so the floor moves: GHSA-wx67-qw84-cm4g (High, CVSS 7.5, 2026-07) is another server-function DoS (crafted HTTP requests → OOM / excessive CPU) affecting react-server-dom-webpack/parcel/turbopack 19.0.0–19.0.7, 19.1.0–19.1.8 and 19.2.0–19.2.7, patched in 19.0.8 / 19.1.9 / 19.2.8 — the current floors. No server and no RSC-capable bundler means no exposure.
TANSTACK START XSS (2026-09-30, verified vs tanstack.com/blog and npm): CVE-2026-102989, a CRITICAL reflected XSS in TanStack Start's server-function response handling — a crafted server-function URL can return attacker-controlled HTML from the app's origin, so a user who opens the link runs the attacker's JS with that user's session. Affected: @tanstack/react-start 1.143.12–1.168.59, @tanstack/solid-start 1.143.12–1.168.56, @tanstack/vue-start 1.143.12–1.168.55, @tanstack/start-server-core 1.143.12–1.169.38. Patched: react-start ≥1.168.60, solid-start ≥1.168.57, vue-start ≥1.168.56, start-server-core ≥1.169.39. Edge mitigations help temporarily; full protection needs a rebuild+redeploy on the patched version, not just a dependency bump.
TRUSTED TYPES (verified vs the React 19.3 post, 2026-09-09): React before 19.3 coerced every value with '' + value, turning Trusted Types objects back into strings the browser rejects under an enforcing CSP → a CSP-enforced app needs React ≥19.3.
RNSEC PROVENANCE (2026-07-09): rnsec was surfaced by Native Weekly, whose author also authors rnsec, so it was vetted independently before inclusion (repo checked: ~500★, 63 rules, CI integrations, v1.3 2026-04). Kept with the young/single-author caveat; adoption is the open question.
CROSS-REFERENCES: RB-E-META-FRAMEWORKS owns Next.js security patches, including the 2026-09-22 out-of-band critical RCE release; RB-E-BUILD owns the Vite floor; RB-E-STORAGE owns secure storage; the review skill owns per-diff secret/injection/dependency checks.
HISTORY: 2026-10-01 REOPENED: TanStack Start critical reflected XSS, CVE-2026-102989 (server-function response handling; floors react-start ≥1.168.60 / solid-start ≥1.168.57 / vue-start ≥1.168.56 / start-server-core ≥1.169.39); added the GitHub Security Lab AI-agent reading (24 Android CVEs, human-review-required caveat). 2026-09-21 REOPENED: dev-server attack surface — Vite CVE-2026-39364 with measured mass-scanning. 2026-08-07 GAP CLOSED: GitHub's supply-chain umbrella post; keyv compromise as counterexample. 2026-07-28 REOPENED: npm publish-time malware scanning + dual-use metadata. 2026-07-16 GAP CLOSED: actions/checkout v7 pull_request_target defaults (back-audit). 2026-07-10 GAP CLOSED: Vercel April-2026 incident; npm staged publishing. 2026-07-09 GAP CLOSED: rnsec added.
Canonical reading
Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.
Real-world evidence for the DeepSec-style pattern of this entry: GitHub's open-source Taskflow Agent combines AI prompts with structured, PLATFORM-SPECIFIC workflows (not generic prompting) to find real bugs — 24 Android vulnerabilities across shipped apps, including a location-tracking injection in OsmAnd and a deeplink-based account takeover in Wikipedia's Android app. The caveat that matters: the authors state LLMs struggle to estimate severity and produce false positives, so every finding still needs review by a researcher who knows the mobile platform — automation finds candidates, it doesn't replace the review.
An RCE postmortem on CVE-2025-55182 in React's Flight protocol (thenable abuse, prototype-chain exploitation), with durable lessons on runtime-vs-build-time type safety and lenient-parsing attack surface. Canonical RSC security case study.
A DoS deep-dive (CVE-2026-23870) in the RSC Flight deserializer, with the generalizable lesson that dissolving a network boundary into developer ergonomics still owes a threat model (applies to tRPC/GraphQL/WebSockets too).
A first-party postmortem of the Shai-Hulud worm (preinstall-script credential exfiltration) with concrete mitigations — disable npm install scripts, pnpm 10, OIDC publishing. The supply-chain reading this entry's own note calls for; applies to any React/RN delivery pipeline.
First-party postmortem of the incident this entry's note cites: 84 versions across 42 @tanstack/* packages, via a pull_request_target 'Pwn Request' → pnpm store-cache poisoning → OIDC token extraction from the runner. The CI/CD-side attack chain — a different door than the install-script family covered by the Shai-Hulud and NodeSource readings.
The architectural half of this entry's RSC stance: compiler-generated server references are reachable via direct POST, so auth/authz must live INSIDE every server function — plus the rolling-deploy skew failure mode (generated function IDs drift across deploys and break old clients). Treat stable server functions as formal API boundaries. (Site is a JS shell; content verified via its RSS feed.)
The DEFENSE half of this entry's Flight-protocol readings, which are otherwise all attack postmortems. Walks the protocol as an attack surface — a line-delimited format with its own type system, its own reference resolution, and its own rules for reconstructing executable behavior on the client — then works through the CVSS 10.0 React2Shell mechanics (CVE-2025-55182) to a RANKED set of defenses, from strict schema validation of everything crossing the boundary to CSRF hardening. Read when you ship RSC and need practice, not just incident history.
The first-party umbrella over this entry's whole supply-chain thread: GitHub's 2026 program in one post — npm staged publishing, install-script restrictions (npm v12), 72-hour read-only account lock after email/2FA-recovery changes, Actions hardening (checkout defaults, workflow-execution policies, read-only cache), an outbound-network-firewall technical preview, and a 3-day default Dependabot cooldown. The platform-side complement to the Shai-Hulud and TanStack postmortems' local mitigations.
The nuance behind this entry's supply-chain note: npm v12's default block on preinstall/install/postinstall closes the INSTALL-TIME door but leaves the EXECUTION-TIME door open. Attackers already moved the payload into the module body as a top-level side effect (runs on first require), or ship a binding.gyp so npm's implicit node-gyp rebuild fires anyway (the June-2026 Miasma campaign). Removing a trigger isn't removing the capability — so layer defenses: Node's --permission model (--allow-net / --allow-child-process / --allow-fs-write to cap what running code may DO), CI egress allow-listing (Harden-Runner), and container/seccomp isolation. Install-script blocking raises the floor; it is not the ceiling.
Depth (in-domain rules) is owned by the review skill — this entry is selection breadth.
The full explanation
The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.
About app security — boundaries, secrets, and supply chain (React & React Native)
Diataxis: Explanation. This page builds understanding of where React/RN security risk
actually lives — durable posture, not a live CVE feed. Per-diff secret/injection/dependency
checking is owned by the review skill; live advisories belong in a security feed. Read this
for the why.
The one question that organises everything: which side of which boundary?
Most security confusion in the React world dissolves once you ask where the boundary is —
because risk, and responsibility, sit on a specific side of it:
Client vs server. The scariest React-specific CVEs of 2025–26 — the RSC / Server-Function
DoS family (e.g. CVE-2026-23864, CVSS 7.5; React2DoS) and the Dec-2025 RSC source-exposure /
RCE issues — are server-side (react-server-dom-*, Next.js App Router). They were first
patched across React 19.0.5 / 19.1.6 / 19.2.5, but the family recurs: after GHSA-wx67-qw84-cm4g
(2026-07) the floors are 19.0.8 / 19.1.9 / 19.2.8. None of it affects React Native. Conflating "React had
a critical CVE" with "my RN app is exposed" is the most common scoping error.
Client vs server, again (device trust). Anything the client asserts about itself — "not
jailbroken," "real device" — is defeatable, because the attacker owns the client. Client-side
checks (jail-monkey) are signal, not enforcement; trust must be established server-side
(Play Integrity / DeviceCheck attestation).
Your code vs the supply chain. The bigger practical risk in 2025–26 wasn't your code at
all — it was the supply chain: the Shai-Hulud worm, the @tanstack/* npm compromise, the
axios RAT. That's why npm v12 now blocks install scripts by default.
So the durable posture isn't "scan for CVEs" — it's know which boundary a risk lives on and put
the control on the right side.
The default, and why
Secrets in Keychain / secure-store (never AsyncStorage); harden the supply chain (block
install scripts, gate install-age); device trust via server-side attestation; route per-diff
secret/injection/dependency checks to the review skill.
Each clause follows from a boundary. Secrets on a device belong in the OS secure enclave, not in
plaintext key-value (RB-E-STORAGE). The supply chain is now the highest-probability attack path,
so install-script blocking + install-age gating + provenance are baseline (provenance alone is not
enough). Device trust is meaningless client-side, so it must be server-verified. And live
per-change checks (a leaked key, an injection sink, a vulnerable dep in this PR) are a per-diff
concern — owned by the review skill, not by a static encyclopedia.
The landscape, and when each control matters
Keychain / secure-store (react-native-keychain / expo-secure-store) — the only correct home
for tokens/secrets on device. AsyncStorage is plaintext; using it for secrets is a recurring
finding (RB-E-STORAGE).
Supply-chain hardening — install-script blocking (npm v12 default), install-age gating
(minimumReleaseAge / min-release-age), and provenance. The response to the worm/compromise wave;
the highest-leverage dependency controls — but install-script blocking is a floor, not a ceiling
(malware shifts to import-time execution; see failure modes). Two layers now sit outside your
install: npm scans every newly published package for malware before it becomes installable
(2026-07-28), and maintainers can use staged publishing (draft → 2FA-gated validate → finalize).
Neither replaces the local layers — the keyv worm (2026-08-04) shipped with valid provenance a
week after registry scanning went default.
Dev servers — part of the attack surface, not just production. Vite's CVE-2026-39364 let an
exposed dev server serve .env files and keys (fixed in 8.0.5 / 7.3.2; RB-E-BUILD owns the
floor), and exposed dev servers were mass-scanned in August 2026. Anything reachable through
--host, server.host or a Docker port mapping should be patched and kept away from secrets.
Device trust (jail-monkey v3 + server attestation) — jail-monkey does client-side
jailbreak/root/mock-location/debug detection (v3 adds New-Arch support), but it is not a
substitute for server-side Play Integrity / DeviceCheck. Use it as a hint; enforce on the server.
Trusted Types (web) — the browser API that makes injection sinks demand policy-created
objects instead of strings. React is only compatible from 19.3: earlier versions coerced every
value to a string, which an enforcing CSP rejects, so a CSP-enforced app needs React ≥19.3.
dangerouslySetInnerHTML still needs an explicit policy. The web injection-surface control.
Dependency CVE scanning — npm/pnpm audit and friends; route the depth (triage, per-diff)
to the review skill's security phase rather than treating the encyclopedia as the scanner.
Tradeoffs and failure modes to name out loud
Mis-scoping server CVEs to RN. The RSC DoS/RCE family is server-side; patch your React on
the web, but don't panic your React Native app over it (RB-E-REACT-CORE, RB-E-META-FRAMEWORKS).
Trusting the client. Shipping device-trust or entitlement decisions that the client can flip.
If the attacker owns the runtime, the check must live on the server.
Secrets in AsyncStorage. Plaintext tokens one adb backup away. Keychain/secure-store, always.
Provenance as a complete answer. Provenance proves origin, not safety; pair it with install-
script blocking and age-gating.
Install-script blocking as a complete answer. npm v12's default block closes the install-time
door, but malware has already moved to the execution-time door — the payload as a top-level
module side effect that runs on first require, or a binding.gyp that fires npm's implicit
node-gyp rebuild (the June-2026 Miasma campaign). Removing a trigger isn't removing the
capability; layer runtime constraints on top — Node's --permission model (cap net / child-process
/ fs-write), CI egress allow-listing, container/seccomp isolation.
Treating this page as a CVE feed. Specific live advisories go stale; they belong in a feed +
the review skill, while this entry holds the durable posture.
How it interacts with the rest of the stack
React core / meta-frameworks (RB-E-REACT-CORE, RB-E-META-FRAMEWORKS). The RSC/Server-
Function server surface is where the React-specific CVEs live; patch cadence is part of choosing
a web framework, and RB-E-META-FRAMEWORKS owns the Next.js patch thread (including its
out-of-band critical RCE releases). None of it reaches React Native.
Storage (RB-E-STORAGE). Secrets → Keychain/secure-store, not AsyncStorage; the two entries
share that rule.
DX (RB-E-DX). Supply-chain hardening (install-script blocking, dependency automation) lives
in the same CI/dependency-hygiene loop.
Per-diff depth (review). Secret/injection/XSS/auth/dependency checks on an actual change
set are owned by the review skill — this page is posture, that skill is enforcement.
In one paragraph
Security clarity comes from asking which side of which boundary a risk lives on: the scary
React CVEs (RSC DoS/RCE) are server-side and don't touch React Native; device-trust checks
are meaningless on the client and must be server-verified; and the biggest practical danger is
the supply chain, not your code (hence install-script blocking + age-gating). Keep secrets in
Keychain/secure-store, use Trusted Types for web XSS, and route live per-diff
secret/injection/dependency checks to the review skill — this entry is durable posture, not a
CVE feed.