# Risk map — lodash/lodash

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

# Risk map — lodash/lodash

Scanned commit 2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8 (2026-09-11). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

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. — [lodash.js#L14988-14996](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14988-L14996) [threat-model.md#L47-51](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/threat-model.md#L47-L51)
- `_.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]/`. — [lodash.js#L14919-14927](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14919-L14927) [lodash.js#L14960-14966](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14960-L14966) [lodash.js#L181](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L181)
- `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. — [lodash.js#L4036-4043](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4036-L4043)
- `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. — [lodash.js#L4382-4404](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4382-L4404)
- 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) — [lodash.js#L2597-2610](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L2597-L2610) [lodash.js#L4036-4043](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4036-L4043) [lodash.js#L4382-4404](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4382-L4404) [lodash.js#L6704-6715](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L6704-L6715) [lodash.js#L6418-6422](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L6418-L6422)
- `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`. — [dist/lodash.core.js#L1-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/dist/lodash.core.js#L1-L9) [lib/main/build-dist.js#L11-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L11-L29) [lib/fp/build-dist.js#L16-54](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-dist.js#L16-L54)
- 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) — [.github/workflows/ci-dist-sync.yml#L30-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-dist-sync.yml#L30-L37) [lib/main/build-dist.js#L24-31](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L24-L31)
- `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. — [.github/workflows/browser-testing.yml#L22-34](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/browser-testing.yml#L22-L34) [package.json#L30-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L30-L59) [playwright.config.js#L1](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/playwright.config.js#L1)
- `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. — [package.json#L17](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L17) [lib/fp/build-doc.js#L73-77](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-doc.js#L73-L77) [.github/workflows/ci-docs.yml#L27-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-docs.yml#L27-L29)
- 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. — [package.json#L52-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L52-L59) [lib/fp/build-dist.js#L24-28](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-dist.js#L24-L28) [renovate.json#L7-13](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/renovate.json#L7-L13)
- 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/index.html#L49-131](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L49-L131) [test/index.html#L133-188](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L133-L188)
- `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. — [test/test.js#L313-320](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L313-L320) [test/test.js#L632-635](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L632-L635)
- 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/asset/test-ui.js#L109-142](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L109-L142) [test/index.html#L204-207](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L204-L207)
- `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. — [test/remove.js#L11-27](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/remove.js#L11-L27)
- 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. — [test/test.js#L269-306](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L269-L306) [perf/perf.js#L14-36](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/perf/perf.js#L14-L36) [package.json#L30-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L30-L59)
- `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) — [lib/common/util.js#L31-40](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/util.js#L31-L40) [lib/main/build-dist.js#L25-28](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L25-L28) [lib/fp/build-dist.js#L49-53](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-dist.js#L49-L53)

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)
