webpack/webpack
webpack's real security surface is that it executes arbitrary third-party code by design — loaders, plugins and config files all run with full Node privileges in the developer's process — so the interesting risks are the paths that widen that, not the bundler core. Concretely: `HttpUriPlugin` fetches remote code over the network, `lazyCompilationBackend` opens a listening HTTP server, `CleanPlugin` deletes files, `DotenvPlugin` reads secrets into the bundle, and the cache deserializer reconstructs arbitrary registered classes from disk. Operationally, the largest fragility is size and coupling: `Compilation.js` is 6,724 lines with a seal pipeline whose phase ordering is load-bearing, the three `syntax-parser.js` files total ~46k lines with V8-inlining-budget constraints baked into specific methods, and several correctness properties (cache round-trip, generated-file freshness, deep-import compatibility) are enforced only by conventions that a careless edit silently violates.
- Arbitrary code execution is the design, not a bug: `loadLoader` requires loader modules from the resolved graph and `runLoaders` invokes them with a full loader context, so any dependency reachable as a loader runs with the developer's privileges — there is no sandbox in this path.
- `HttpUriPlugin` is the highest-value security path: it fetches module source over `http:`/`https:`, routes through `options.proxy` or the `http_proxy`/`HTTP_PROXY` environment variables, and its `allowedUris` policy must be re-checked after every redirect — the redirect check at L773-790 is the control that stops an allowed host from redirecting to an arbitrary one.
- Fetched remote bodies are written to disk under `cacheLocation` (defaulting to `<lockfileLocation>.data`) keyed by a computed `key`, so a poisoned or unvalidated response becomes a persisted build input.
- `lazyCompilationBackend` binds a real HTTP/HTTPS listener in the dev process and keeps a `Set` of live sockets; its request handler mutates an `activeModules` refcount from client-supplied keys, so exposure of that port exposes compile triggers.
- `CleanPlugin` performs `unlink` and `rmdir` under `output.path` based on a filesystem diff, with `keep` (a `SyncBailHook` plus user matcher) and `dry` as the only brakes — a misconfigured `output.path` makes this a destructive operation on a directory the user did not intend.
- `DotenvPlugin` reads `.env`, `.env.local`, `.env.[mode]` and `.env.[mode].local` and feeds the parsed, `dotenv-expand`-expanded values into `DefinePlugin` — i.e. straight into the emitted bundle — so a missing `prefix` filter ships secrets to the browser.
- The cache deserializer instantiates classes named by strings read off disk: `registerLoader(/^webpack\/lib\//, …)` maps a request to a loader in the generated `internalSerializables`, and an unknown request only produces a `console.warn` — a cache file is therefore a trusted input.
- Deserialization re-enters constructors with no arguments, so a constructor that dereferences a parameter throws during restore and manifests only as a `Restoring failed for <identifier> from pack` debug line — a silent-by-default failure mode documented as a recurring trap.
- Blast-radius hotspot #1 is `lib/Compilation.js` (6,724 lines): a single class owning the module graph, chunk graph, five async queues, the whole seal pipeline, asset emission and the hook surface — any reordering inside `seal` changes what plugins observe.
- Blast-radius hotspot #2 is the parser trio: `javascript/syntax-parser.js` (17,844 lines), `css/syntax-parser.js` (14,973) and `html/syntax-parser.js` (13,197), whose hot methods (`readWord`, `readString`, `finishToken`) are sized to fit V8's ~460-byte inlining budget — adding one argument to `readWord` once cost 1.4%.
- Fragile coupling: five `lib/*/data.js` modules and every `types.d.ts`/`declarations/*.d.ts`/`schemas/**/*.check.js` file are generated, and the `lint` job fails with only `… need to be updated` — a message that reads like a code problem even when the cause is a reworded JSDoc paragraph.
- Moving any file in `lib/` risks breaking the published ecosystem's deep imports; the only record of which paths are load-bearing is `tooling/deep-webpack-imports.json` (with `perKeyword`, `requests` and a `removed` map), checked by a CI job and mirrored by `test/unitCases/deepPathShims.unittest.js`.
- Legacy surface is explicit but live: `lib/wasm-sync/` is kept only until the next major, and `lib/index.js` re-exports `SingleEntryPlugin`, `LibraryTemplatePlugin`, `WebpackOptionsDefaulter` and `JavascriptModulesPlugin` behind `util.deprecate` with `DEP_WEBPACK_*` codes; root-level shims like `lib/NormalModule.js` (10 lines) exist purely as re-exports.
- The Node 10.13 baseline is an under-enforced trap: `lib/` and `hot/` ship as raw source with no build step, and the baseline binds test fixtures too — a `configCases/` fixture using newer syntax passes locally and fails the Node 10 CI job with a bare `SyntaxError` inside the emitted bundle.
- Instance-shape discipline is a correctness-adjacent perf invariant enforced only by review: every field must be initialized in the constructor and never `delete`d, because a late first assignment forces a V8 hidden-class transition and deopts the inline caches on hot property reads.