CodeVetter · Repo Unpack · Risk map
react/create-react-app
The highest-risk surfaces are process spawning during install (npm/yarn), the dev server's host check and proxy, env inlining, and eject rewriting package.json. Secrets are not stored in-repo; the dangerous pattern is REACT_APP_* being baked into the client bundle. Several code paths shell out to git. The project is in maintenance mode (v5.1.0) so dependency CVEs in webpack 5 / Jest 27 will linger unless backporting continues. Dead weight includes IE polyfill entrypoints and Flow leftovers.
- createReactApp.js spawns the package manager (npm, yarn, or pnpm when use-pnp) with user-influenced package specifiers (--scripts-version and --template). Specifiers are passed as arguments (not a shell string) in the spawn path, but a malicious --scripts-version file: URL or git URL can still execute that package's install scripts. This is inherent to any scaffolder.
- executeNodeScript writes a temporary JS file and runs it with the Node binary to invoke init.js, passing serialized arguments. The temp file is removed afterwards. If cwd or the template path were attacker-controlled beyond the CLI user, this is an arbitrary Node execution primitive — acceptable for a local CLI, unsafe if wrapped as a service.
- Dev server host check: allowedHosts is set to all when DANGEROUSLY_DISABLE_HOST_CHECK=true, which re-opens DNS-rebinding exposure against the webpack-dev-server. Proxy middleware (http-proxy-middleware) forwards to package.json proxy and can be abused if a malicious site is allowed to hit the dev server. sockHost/sockPath/sockPort (WDS_SOCKET_*) are also env-controlled.
- env.js documents and implements that any REACT_APP_* variable is embedded into the JS bundle via DefinePlugin. Putting secrets in REACT_APP_ keys ships them to every browser. .env.local is loaded for development and production but intentionally skipped for test.
- init.js and eject.js invoke git (init, add, commit, status) through cross-spawn or execSync. A hostile git hook in an existing repo (eject) or a strange GIT_* environment can run code. eject refuses a dirty tree unless forced, which is a safety check not a security boundary.
- HTTPS dev server: when HTTPS=true, start.js / webpackDevServer.config.js enables https. Certificate handling uses webpack-dev-server defaults (self-signed) unless SSL_CRT_FILE and SSL_KEY_FILE are set. Those paths are read from disk; a wrong path fails the start rather than falling back silently in the config that reads them.
- ModuleScopePlugin and the image inline size limit are integrity features, not auth. There is no authentication layer anywhere: CRA generates static SPAs. Anyone who treats the dev server as an authenticated backend proxy inherits the proxy's lack of CSRF protection.
- Dependency blast radius is react-scripts/package.json: webpack, webpack-dev-server, Jest, babel, eslint, postcss, and workbox are pinned to the v5 line (webpack 5, Jest 27, ESLint 8). A vulnerability in webpack-dev-server affects every app that has not ejected. Ejected apps freeze a copy and stop receiving fixes.
- IE compatibility code (react-app-polyfill/js/ie11.js and ie9) and confusing-browser-globals are legacy surfaces. They still ship because older generated apps import them. Changing the globals list is a breaking ESLint change for every consumer of eslint-config-react-app.
- eslint-config-react-app uses try/catch require.resolve to optionally enable TypeScript, Jest, and Testing Library rules. If those plugins are installed but at an incompatible major, ESLint crashes the build/start because CRACO-less CRA fails compilation on ESLint errors (ESLINT_NO_DEV_ERRORS can soften dev). That coupling is a frequent break when users upgrade plugins independently.
- The docs site and the published README can drift from the code. available-scripts.md and adding-custom-environment-variables.md are the contract users trust. netlify.toml only publishes the website; a docs-only claim about runtime behavior must be checked against packages/react-scripts before editing code.
- CI docker e2e is the only place the full behavior matrix runs. A change to webpack.config.js or env.js can pass unit tests and e2e-simple yet break typescript fixtures or PUBLIC_URL handling. Those fixtures are the regression net. (inferred)
- No in-repo secret material was observed in the manifests or workflows read: no private keys, no production service credentials. Workflows use GitHub-hosted runners and standard checkout/setup-node. The risk is downstream (user apps embedding REACT_APP secrets), not this repo's own deploy tokens being hard-coded.