lodash/lodash
The security-sensitive surface is narrow but sharp: `_.template` compiles to a real `Function`, and the path-write family (`_.set`, `_.unset`, `_.merge`, `_.defaultsDeep`) has layered prototype-pollution guards that are the subject of prior CVEs. The repo's own `threat-model.md` scopes these precisely. Structurally, the largest risk is coupling: `lodash.js` is 546 KB in one file that every contributor edits, `test/test.js` is 814 KB, and `fp/_mapping.js` is a hand-maintained table that must stay in lockstep with the method list or the FP build silently drifts. `dist/lodash.core.js` is not produced by `npm run build`, so the dist-sync workflow cannot detect a stale core build. CI also pulls two unpinned packages from the network at test time.
- `_.template` builds a source string and evaluates it with `Function(importsKeys, sourceURL + 'return ' + source)`; the project's own threat model calls it insecure, cites CVE-2021-23337, and states it will be removed in v5.
- `_.template`'s hardening is two regex checks: `sourceURL` has all whitespace collapsed to spaces so a newline can't escape the `//#` comment, and `variable` is rejected if it matches `/[()=,{}\[\]\/\s]/`.
- `baseSet` returns the object untouched if any path segment is `__proto__`, `constructor` or `prototype` — a blanket block that also silently no-ops legitimate writes to a literal `constructor` key.
- `baseUnset` uses a different, narrower rule than `baseSet`: it blocks `__proto__` only when the object lacks an own `__proto__`, and blocks `constructor`/`prototype` only in non-terminal positions — the asymmetry is deliberate but easy to break when editing one and not the other.
- Prototype-pollution defense is spread across four unrelated call sites — `baseAssignValue`, `baseSet`, `baseUnset`, `safeGet` — plus `isKey`'s `value !== '__proto__'` check, so a new path-writing method added without touching all of them would be unprotected. (inferred)
- `dist/lodash.core.js` carries the header `Build: lodash --source ./lodash.js core -o ./dist/lodash.core.js` and is produced by the external `lodash-cli`; `npm run build` only regenerates `lodash.js`, `lodash.min.js`, `lodash.fp.js` and `mapping.fp.js`.
- Because `npm run build` never writes `dist/lodash.core*.js`, `ci-dist-sync.yml`'s `git diff --exit-code dist/` will pass even when the core build is stale relative to an edited `lodash.js` — a silent staleness hole in the sync gate. (inferred)
- `browser-testing.yml` installs two packages that are not in `package.json` and are unpinned: `npm install -D @playwright/test@latest` and `npx http-server`, both fetched from the registry on every CI run despite every GitHub Action being SHA-pinned.
- `npm run doc:fp` invokes `node lib/fp/build-doc` with no target argument, so `_.last(process.argv)` resolves to the script's own path and `fs.writeFile(target, ...)` writes the wiki output next to the build script rather than to an intended doc location.
- The build toolchain is pinned to end-of-life tooling: `uglify-js` exactly `2.7.5`, `webpack ^1.14.0` (using the webpack-1-only `OccurenceOrderPlugin` and `DedupePlugin`), `jscs ^3.0.7` and `istanbul 0.4.5`, while Renovate is configured to never propose major npm updates.
- The test suite deletes and replaces host globals (`Object.create`, `Symbol`, `Map`, `Promise`, `Set`, `WeakMap`, `Function.prototype.toString`) and restores them by hand in a separate function — an asymmetry that leaks global state into all subsequent tests if `removeBizarroMethods` drifts from `addBizarroMethods`.
- `test/test.js` intentionally defines a non-configurable throwing getter on `global.root` and calls `require('vm').runInNewContext` to build a cross-realm fixture — both are process-level side effects that make the suite hostile to running inside other harnesses.
- The browser harness will load an arbitrary remote origin: `ui.isForeign` is set when the `build` query param matches `^(\w+:)?//`, and `index.html` then `document.write`s that URL as a script src — fine for a localhost runner, dangerous if the page is ever hosted publicly.
- `test/remove.js` builds a `RegExp` from `process.argv` and does a blind `fs.writeFileSync` over a resolved path with no existence or backup check — a mistyped argument overwrites the target file.
- PhantomJS is dead code: `test/test.js` and `perf/perf.js` still branch on `root.phantom`, create a `require('webpage')` page and call `phantom.exit`, but PhantomJS appears in no devDependency or workflow.
- `lib/common/util.js:pitch` is the only error handling in the entire build toolchain — it rethrows synchronously from an `async.series` callback, so a failed copy or minify surfaces as an unhandled throw with no cleanup of partially written `dist/` files. (inferred)