react-brain
Browse decisions
React & language foundations 3App architecture 9UI 12Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness bencharchitecturechangelogmethodologyroadmap

entriestooling-ops · verified 2026-07-10 · react + react-native

App security — device trust, secrets, dependency risk

reviewedconfidence: highthis tier holds 75% on the public scorecard (6/11 graded · 0 overturned) →

related decisions: storage

cited by: react-core · dx

re-verified 4× — 2026-07-13 · 2026-07-10 · 2026-07-09 · 2026-07-06 · changelog

recommendation

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 → server-side Play Integrity / DeviceCheck (jail-monkey is client-side only, defeatable)
  • web XSS surface → Trusted Types + sanitized dangerouslySetInnerHTML
  • RSC/Server Functions on web → patch React to 19.2.5+ (DoS CVEs); does not affect RN
  • want a cheap CI gate for RN-specific security footguns → rnsec scan (young — treat findings as leads, route confirmations to the review skill)

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoffevidence
jail-monkey (v3)client-side jailbreak/root + mock-location + debug detection; v3 adds New-Arch support; NOT a substitute for server-side Play Integrity / DeviceCheck120k/wk
react-native-keychain / expo-secure-storestore tokens/secrets in Keychain/Keystore, not AsyncStorage (see RB-E-STORAGE)
dependency CVE scanningnpm/pnpm audit; route depth to the review skill's security phase
supply-chain hardeninginstall-script blocking (npm v12 default), install-age gating (min-release-age / npmMinimalAgeGate), provenance — provenance alone is not enough
Trusted Types (web)React integrates the browser Trusted Types API for XSS prevention; dangerouslySetInnerHTML needs an explicit policy
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 + single-author (~500★, v1.3) — a cheap CI layer, not a substitute for per-diff review6k/wk
DeepSec (Vercel Labs)open-source AI security-scan harness — agents trace data flows on YOUR infra with YOUR Claude/Codex subscription; produces severity-rated findings (NOT auto-fix PRs), ~10–20% false-positive rate per Vercel; framework-agnostic, new

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

npm weekly downloads (from the corpus's last signals run): rnsec 6k · jail-monkey 120k

Verified notes

DURABLE SCOPING FACT: 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; patched across React 19.0.5 / 19.1.6 / 19.2.5. The 2025–26 SUPPLY-CHAIN wave (Shai-Hulud worm, the @tanstack/* npm compromise, axios RAT) is the bigger practical risk — hence npm v12 blocking install scripts by default + install-age gating. Live CVEs belong in a security feed, not this encyclopedia; for per-diff secret/injection/dependency checks defer to the review skill. PROVENANCE (2026-07-09): rnsec was surfaced by Native Weekly — whose author ALSO authors rnsec — so it was independently vetted before inclusion (repo checked: ~500★, 63 rules, CI integrations, active v1.3 2026-04). Kept with the young/single-author caveat; watch whether it earns adoption. INCIDENT LEDGER UPDATE (2026-07-10): the Vercel April-2026 incident (verified vs Vercel's 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 unaffected. Lessons: mark platform env vars SENSITIVE, and treat AI dev-tools as supply-chain surface. The TanStack npm compromise now has its first-party postmortem (reading). HARDENING TOOLKIT ADDITION (2026-07-10, verified vs the nuqs post): npm STAGED PUBLISHING (draft → 2FA-gated validate → finalize) + local tarball-hash reproduction — the maintainer-side countermeasure to the TanStack-style CI-compromise chain ("assume GitHub itself is compromised"); joins install-script blocking and install-age gating as the third supply-chain layer.

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

The React2Shell StoryLachlan Davidson

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.

The Flight Protocol Made Your DoS My ProblemSascha Becker

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).

Shai-Hulud Postmortem (npm supply-chain worm)Eric Allam (Trigger.dev)

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.

Postmortem: TanStack npm supply-chain compromiseTanner Linsley (TanStack)

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.

RSC Server Functions Are Not An API BoundaryLong Ho

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.)

Blocking Install Scripts Is Not a Silver BulletNodeSource

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.

Sources

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 are patched across React 19.0.5 / 19.1.6 / 19.2.5 and do not affect 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).

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) — React integrates the browser Trusted Types API for XSS prevention; dangerouslySetInnerHTML needs an explicit policy. The web injection-surface control.

Dependency CVE scanningnpm/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. 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.


See also: RB-E-REACT-CORE / RB-E-META-FRAMEWORKS (RSC CVEs are server-side), RB-E-STORAGE (secrets belong in Keychain), RB-E-DX (supply-chain hardening in CI). Per-diff enforcement: the review skill.

Related in tooling-ops: build · testing · dx · observability · ota