# Risk map — tailwindlabs/tailwindcss

> Canonical page: https://codevetter.com/unpack/tailwindcss/risk-map

# Risk map — tailwindlabs/tailwindcss

Scanned commit 41d9cae8e53378d16087fcf359eb785c2fd42ce4 (2026-09-08). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Security surface is narrow but real: the compiler loads and executes arbitrary user JS via `@plugin`/`@config` through jiti, the upgrade CLI shells out to git and the package manager and rewrites source files in place, and the scanner walks the filesystem outside the repo root whenever `@source` points there. The larger operational risks are correctness-of-caching risks — every integration hand-rolls its own mtime-based invalidation, and the core `build()` explicitly assumes candidates are only ever added, never removed. Cross-platform fragility (Windows paths, symlinks, Bun/USERPROFILE) shows up repeatedly as inline workarounds, which is a reliable signal of where regressions land.
- `@plugin` and `@config` execute arbitrary user code: `loadModule` resolves the id and calls `importModule`, which falls back to `createJiti(...).import(path)` when native `import()` fails — so a `.ts` config file is transpiled and run in-process with full Node privileges. — [packages/@tailwindcss-node/src/compile.ts#L114-L190](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L114-L190) [packages/tailwindcss/src/compat/apply-compat-hooks.ts#L54-L90](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/apply-compat-hooks.ts#L54-L90)
- `globalThis.__tw_resolve` and `globalThis.__tw_load` are ambient global escape hatches consulted before every CSS and JS resolution; the standalone binary installs them to serve embedded assets, but any code loaded into the process can override module resolution by setting them. — [packages/@tailwindcss-node/src/compile.ts#L192-L200](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L192-L200) [packages/@tailwindcss-node/src/compile.ts#L228-L236](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L228-L236) [packages/@tailwindcss-standalone/src/index.ts#L11-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-standalone/src/index.ts#L11-L60)
- `@tailwindcss/upgrade` shells out with `execSync` to `git rev-parse --show-toplevel`, `git status --porcelain`, and `npm ls tailwindcss --json`, then calls `pkgManager.add(...)` to install packages — it mutates the user's dependency tree as part of a 'migration'. — [packages/@tailwindcss-upgrade/src/utils/git.ts#L1-L20](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/utils/git.ts#L1-L20) [packages/@tailwindcss-upgrade/src/utils/version.ts#L1-L45](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/utils/version.ts#L1-L45) [packages/@tailwindcss-upgrade/src/index.ts#L263-L285](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/index.ts#L263-L285)
- The upgrade tool's only safety net for in-place source rewriting is the `isRepoDirty()` check, which `--force` disables outright; after that it rewrites every matched CSS file and every template file via `writeFileSafely` with no backup. — [packages/@tailwindcss-upgrade/src/index.ts#L58-L70](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/index.ts#L58-L70) [packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/utils/write-file-safely.ts)
- Template rewriting is heuristic and acknowledged as such: `isSafeMigration` maintains hand-written regex denylists for Vue `v-if`, Alpine `wire:`, shadcn `variant:` props, Next.js `placeholder=`, and `$emit(` — any unlisted pattern risks a class-name rewrite inside a string that is not a class attribute. — [packages/@tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts#L1-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts#L1-L60) [packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts#L32-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/template/migrate.ts#L32-L60)
- File IO reaches outside the repo root by design: `SourceEntry::External` exists specifically to let `@source "../node_modules/my-lib"` bypass the default ignore rules, and `resolveJsId` extends module search with `process.env.NODE_PATH`. — [crates/oxide/src/scanner/sources.rs#L50-L62](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/sources.rs#L50-L62) [packages/@tailwindcss-node/src/compile.ts#L184-L190](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L184-L190)
- `build()` carries a documented soundness assumption — 'this currently assumes that we only add new candidates and never remove any' — appearing twice as an early-return condition, so a candidate disappearing from source will not shrink the emitted CSS without a full rebuild. — [packages/tailwindcss/src/index.ts#L777-L800](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L777-L800)
- Cache invalidation is duplicated four times with slightly different shapes: `Root.requiresBuild()` in Vite, the `context.mtimes` loop in PostCSS, `getRebuildStrategy` in the CLI, and per-loader `CacheEntry.mtimes` in webpack/Turbopack — a bug fixed in one will not propagate to the others. — [packages/@tailwindcss-vite/src/index.ts#L528-L543](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L528-L543) [packages/@tailwindcss-postcss/src/index.ts#L160-L200](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L160-L200) [packages/@tailwindcss-cli/src/commands/build/index.ts#L800-L840](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L800-L840) [packages/@tailwindcss-webpack/src/index.ts#L34-L58](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-webpack/src/index.ts#L34-L58)
- `@tailwindcss/webpack` and `@tailwindcss/turbopack` are near-verbatim copies of each other down to identical `LoaderOptions`, `CacheEntry`, `getCacheKey`, and `getContextFromCache` definitions — divergence between them is a live duplication hazard. — [packages/@tailwindcss-webpack/src/index.ts#L1-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-webpack/src/index.ts#L1-L60) [packages/@tailwindcss-turbopack/src/index.ts#L1-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-turbopack/src/index.ts#L1-L60)
- The PostCSS plugin deliberately swallows errors — it `console.error`s and then throws `root.error(...)` after resetting `context.compiler = null`, with a comment that throwing directly 'will cause PostCSS to no longer watch for changes' — so genuine compile failures can surface as an empty stylesheet plus console noise. — [packages/@tailwindcss-postcss/src/index.ts#L314-L345](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L314-L345)
- The Vite plugin carries two hardcoded upstream-bug workarounds — skipping `addWatchFile` for the input file itself (Qwik) and ignoring `.svg` paths containing `#` or `?` (tailwindcss#16877) — meaning edits to those files silently do not trigger rebuilds. — [packages/@tailwindcss-vite/src/index.ts#L376-L392](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L376-L392)
- Windows and symlink handling is a recurring fragility: the scanner canonicalizes via `dunce::canonicalize` with a cached parent-directory map, inserts both symlinked and canonical paths into `self.files`, and `scan_content` has to re-walk the tree to validate previously-unknown changed files. — [crates/oxide/src/scanner/mod.rs#L155-L226](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L155-L226) [crates/oxide/src/scanner/mod.rs#L395-L440](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L395-L440)
- The standalone build re-spawns itself as a nested Bun process with `USERPROFILE` blanked out to work around Bun binary downloads failing on Windows CI when Turborepo passes the variable through — a build-time landmine with no test coverage. — [packages/@tailwindcss-standalone/scripts/build.ts#L9-L21](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-standalone/scripts/build.ts#L9-L21)
- `packages/tailwindcss/src/feature-flags.ts` is an empty file still present in the tree — dead scaffolding that suggests a removed flag system. — [packages/tailwindcss/src/feature-flags.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/feature-flags.ts)
- `crates/ignore` is a vendored fork of BurntSushi's crate carrying local `# CHANGED:` edits (e.g. pinning `edition = "2024"` instead of inheriting from the workspace), so upstream security or correctness fixes require a manual re-vendor. — [crates/ignore/Cargo.toml#L1-L20](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/ignore/Cargo.toml#L1-L20) [crates/ignore/src/incremental.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/ignore/src/incremental.rs)
- The CLI watch path is a ~600-line function with no unit tests of its own — `getRebuildStrategy`, `watchDirectories`, `createWatchers`, and the polling loop are only exercised indirectly through `integrations/cli/index.test.ts`. (inferred) — [packages/@tailwindcss-cli/src/commands/build/index.ts#L685-L870](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L685-L870) [integrations/cli/index.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/cli/index.test.ts)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
