The missing yarn audit fix
Important
v11 is not released yet. The current stable line is v10.x (npm i -D yarn-audit-fix).
v11 is published under the npm snapshot dist-tag, where experimental builds land for early testing:
yarn add -D yarn-audit-fix@snapshot # or: npm i -D yarn-audit-fix@snapshotWhat v11 brings — see Migration notes for the full breaking-change list:
- New lockfile engine on
lockgraph: patches the lockfile graph directly, auto-detecting every yarn schema — Classic + Berry v4–v10 (#248). - Faithful lockfile handling — preserves checksums, integrity,
conditions/dependenciesMeta/peerDependenciesMeta, andpatch:/resolutions/ git / npm-alias entries, with no spurious churn (real-world locks round-trip unchanged). - Single direct-patch flow — the legacy
convertflow and the--flowswitch (andsynpconversion) are removed. - Registry-direct audit — advisories come from the registry's bulk endpoint instead of spawning
yarn/npm audit: works with custom/in-house registries and inherits auth from.npmrc/.yarnrc. The run is now async (runSyncremoved). - Slimmer footprint —
jest→vitest; droppedlodash-es/fs-extra/chalk/js-yaml;commander→minimist. Runs on Node ≥ 14.18, noenginespin.
yarn auditdetects vulnerabilities but cannot fix them. The authors suggest Dependabot or Snyk, which is inconvenient in many setups. Discussion: yarn/issues/7075.yarn auditdoes not support custom (in-house) registries — see this issue & PR, still unmerged.
yarn-audit-fix fetches advisories straight from the registry (the npm bulk
advisory endpoint) and patches the lockfile graph directly via lockgraph
(kudos to G. Kosev, code reference).
Full description: dev.to/yarn-audit-fix-for-yarn-2-berry
- Supports every yarn lockfile schema in the wild: Yarn 1 Classic, Yarn 2/3 (berry v4–v6) and Yarn 4+ (berry v8/v9/v10), auto-detected via
lockgraph. - Fixes vulnerabilities by patching the lockfile graph directly
- Opt-in engine & license constraints — only apply a fix whose dependency closure runs on your target Node and passes your license policy
- macOS / Linux / Windows
- CLI and JS API
- TypeScript typings
| Yarn | Lockfile schema | Supported |
|---|---|---|
| 1.x | yarn-classic |
✅ |
| 2.x | yarn-berry-v4 |
✅ |
| 3.0 | yarn-berry-v5 |
✅ |
| 3.1+ | yarn-berry-v6 |
✅ |
| 4.0–4.13 | yarn-berry-v8 |
✅ |
| 4.14+ | yarn-berry-v9 |
✅ |
| 5.x dev | yarn-berry-v10 |
✅ |
Node.js: >=14.18 — inherited from lockgraph
yarn add yarn-audit-fix -Dor run it directly:
npm_config_yes=true npx yarn-audit-fix$ yarn-audit-fix [--opts] Verifying package structure... Patching yarn.lock with audit data... Upgraded deps (1): minimist@1.2.5 → 1.2.6 [critical, CVSS 9.8] GHSA-xvch-5gv4-984h Done
The lockfile is patched in place — the fix versions, their new transitive
dependencies, and (for yarn berry) the package checksums are all resolved
straight from the registry, so there's no reconcile yarn install step.
| Option | Description | Default |
|---|---|---|
--audit-level |
Include a vulnerability with a level as defined or higher. Supported values: low, moderate, high, critical | all |
--cwd |
Current working dir | process.cwd() |
--dry-run |
Get an idea of what audit fix will do | |
--json |
Print the outcome as JSON — { dryRun, upgraded, skipped, excluded, noFix } — instead of the human summary; pairs with --dry-run for a machine-readable preview. |
false |
--force |
Apply cross-major fixes: bump past a consumer's declared range, and rewrite a direct-dep range in package.json (root or a workspace) that the fix falls outside. See Direct-dependency pins |
false |
--help/-h |
Print help message | |
--npm-path |
Switch to project's local npm version instead of system default. Or provide a custom path. system / local / <custom path> |
system |
--registry |
Custom registry url | |
--silent |
Disable log output | false |
--verbose |
Switch log level to verbose/debug | false |
--exclude |
Packages to skip updating — comma-separated glob[@range] rules (e.g. lodash,@scope/*@>=2 <3); the range is matched against the installed version. Repeatable. |
|
--ignore |
Advisory ids to ignore — comma-separated globs matched against the GHSA id (from the advisory url) or the npm advisory id (e.g. GHSA-*,1106913). Repeatable. |
|
--engines.<engine> |
Only apply a fix whose completed dependency closure runs on the given engine — --engines.node='>=18', =floor (infer from the installed tree), or bare --engines.node (the Node running the CLI). Repeatable per engine. See Constraints. |
|
--license.allow / --license.deny |
Only apply a fix whose closure's SPDX licenses pass the policy — --license.allow=MIT,ISC (or bare --license=MIT,ISC) / --license.deny=GPL-3.0. See Constraints. |
|
--package-type |
Only apply a fix whose closure stays require-able — cjs rejects an ESM-only dependency a CommonJS tree can't require(). See Constraints. |
|
--on-conflict |
What to do when a fix can't satisfy the engine / license / package-type constraints: skip (leave it, report) or stop (error). |
skip |
--safe |
Fix only what's safe on every automatable axis (the opposite of --force): bundles --engines.node=floor + --package-type=cjs (the latter only in a CommonJS project). See Constraints. |
|
--production / --prod |
Fix only advisories reachable from production dependencies (dependencies / optionalDependencies / peerDependencies); dev-only ones are left and reported. See Scope. |
false |
--workspace |
Monorepo: fix only the selected workspaces' closures — comma-separated globs on a workspace's name / path / path-basename (e.g. @scope/core, packages/*, core,cli). See Scope. |
By default a fix is the lowest published version that clears the advisory. You can
add opt-in acceptance gates so a fix is only applied when its completed
dependency closure also satisfies a policy — otherwise the vulnerability is left
in place and reported (or, with --on-conflict=stop, the run errors).
- Engines —
--engines.node='>=18'accepts a fix only if every new package it pulls in runs on Node ≥ 18. This catches the case where a security bump quietly drags in a transitive that needs a newer runtime than your project targets — the install passes, but CI/production on the old Node breaks. A bare--engines.nodeuses the Node running the CLI (printed in the report, since that may differ from your project's target); pass an explicit range to be sure, or--engines.node=floorto infer it from what your tree already requires (the highestenginesfloor across the rootpackage.json+ installednode_modules) — a fix is then rejected only if it would raise that floor. Any engine works (--engines.npm='>=9'), lenient by npm parity — a package that declares noenginesis accepted. - Licenses —
--license.allow=MIT,ISC,Apache-2.0(or--license.deny=GPL-3.0) accepts a fix only if the SPDX license of every new package it pulls in passes the allow/deny policy. A bare--license=MIT,ISCis an allow list. An unresolvable SPDX expression ((MIT OR X)) is flagged, not silently accepted. - Package format —
--package-type=cjsaccepts a fix only if every new package it pulls in stays require-able: a dependency that has gone ESM-only ("type": "module"with no CJSmain/exports) would break everyrequire()of it in a CommonJS tree, so the fix is left for you to take deliberately. It's a consistency check on entry points, not a runtime one — yaf can't prove your code runs, but it can catch a fix that flips a dependency's module format.
Every skip is attributed in the report — the axis, the blocking package, and the
versions tried — so you can widen the target, --exclude the package, or accept
the newer dependency:
Constraints: node >=18
Skipped (constraints — no fix keeps the closure within the policy [node >=18]; …):
lodash@4.17.11 → 4.18.0 — needs some-dep@^2.0.0
Constraints gate a fix's dependency closure (the transitives it pulls in), not the fixed package's own
engines/license.
--safe rolls the automatable gates into one flag — the opposite of --force.
It's shorthand for --engines.node=floor (don't raise the engine floor your tree
already stands on) plus --package-type=cjs in a CommonJS project (don't introduce
an ESM-only dependency). So yarn-audit-fix --safe means fix everything you can
without changing what my project runs on or how it loads — and flags the rest for
a deliberate follow-up. (License stays an explicit policy; --safe doesn't guess it.)
If a fix falls outside a direct dependency's declared range in package.json (an
exact pin like "lodash": "4.17.11", or a range that can't reach the fix), the
package is flagged and skipped by default — the fix is never silently applied
behind a manifest that forbids it. Re-run with --force to rewrite that range in
place (preserving the ^ / ~ / exact operator) and apply the bump, matching
npm audit fix --force. In a monorepo the pin is found and rewritten in the
workspace package.json that declares it — the report names the file:
Skipped (package.json pins these to a range the fix can't satisfy; re-run with --force …):
lodash (pinned → "4.17.11" in packages/foo/package.json)
By default every advisory in the lockfile is fixed. Two opt-in flags narrow the
fix to a subgraph — they gate which advisories are fixed, not how (the patch
and its --immutable reproducibility are unchanged):
--production(--prod) fixes only advisories reachable from your production dependencies.yarn.lockcarries no dev/prod marker — every edge is a plain dependency — so the split is read from eachpackage.json: a package reachable only throughdevDependenciesis left in place (and reported), whiledependencies/optionalDependencies/peerDependenciesand their closures are fixed. This mirrors what a--omit=devproduction install actually ships.--workspace=<globs>(monorepo) fixes only the selected workspaces' closures. Comma-separated globs match a workspace's name, its path, or the path basename (@scope/core,packages/*,core,cli). A workspace reached as a dependency of a selected one contributes only its production deps, so one package's dev tree never leaks into another's closure. A pattern that matches no workspace is an error, not a silent empty run.
Combine them — --production --workspace=packages/api is the production closure
of packages/api. Out-of-scope advisories are reported (a count by default, the
full list under --verbose), so a reduced fix set is never a silent surprise:
Scope: production
Skipped 12 package(s) outside production scope (--verbose to list)
--json prints the outcome as a single JSON object instead of the human summary —
pair it with --dry-run for a preview a CI step can gate on (no lockfile is
written). Human logs and the spinner are suppressed, so stdout is pure JSON:
yarn-audit-fix --dry-run --json{
"dryRun": true,
"upgraded": [{ "name": "lodash", "from": "4.17.20", "to": "4.17.21", "severity": "high" }],
"skipped": [{ "package": "some-tool@1.4.0 → 2.0.0", "reason": "constraint" }],
"excluded": [],
"noFix": []
}reason is one of consumer-range, override-pin, manifest-pin, constraint,
or out-of-scope.
Any CLI option can be set via a YAF-prefixed env var (the dot-nested
--engines.* / --license.* are flag-only). For example:
YAF_FORCE—--forceYAF_AUDIT_LEVEL=high—--audit-level=highYAF_ON_CONFLICT=stop—--on-conflict=stopYAF_PRODUCTION=true—--productionYAF_WORKSPACE='packages/*'—--workspace=packages/*YAF_JSON=true—--json
yarn-audit-fix exposes its internals, so you can tweak the steps or build your own flow. Typedoc: antongolub.github.io/yarn-audit-fix/modules
import { run } from 'yarn-audit-fix'
// async since v11 — advisories are fetched from the registry over HTTP
await run({
verbose: true
})Individual stages (resolveBins, patchLockfile, yarnInstall, …) are exported too, so you can compose your own pipeline if needed.
BREAKING: the legacy convert flow is removed, and so is the --flow switch (plus its synp-based two-way lockfile conversion and the now-dead --package-lock-only / --legacy-peer-deps / --loglevel / --only flags). Direct graph patching is the only flow now — it is more controllable and supports every yarn schema.
With a single flow, the flow abstraction itself is gone: getFlow, the TFlow / TStage types, and the optional custom-flow argument to run are removed. Call run(flags) — the patch pipeline is inlined. The individual stages are still exported if you want to assemble your own.
Adds first-class Yarn 4+ support (#248). The bespoke v1/v2 lockfile adapters are replaced with lockgraph, which auto-detects every yarn schema (classic + berry v4–v10). The audit parser handles both the yarn 2/3 {advisories: …} shape and yarn 4's NDJSON, deriving patched_versions from Vulnerable Versions when the field is absent. Each vulnerable package is upgraded graph-natively to the lowest published version that clears its advisory, and the fix version's new transitive dependencies are pulled into the lockfile (resolved from the registry) — so an upgrade that changes a package's dependency set no longer leaves the lockfile incomplete.
BREAKING: yarn-audit-fix no longer runs a reconcile yarn install. The lockfile is patched and completed entirely in place — fix versions, their new transitive closure, and (for yarn berry) the recomputed package checksums all come straight from the registry. yaf no longer shells out to yarn, and a node_modules directory is no longer required to run it.
BREAKING: advisories are now fetched straight from the registry (the npm bulk advisory endpoint) instead of spawning yarn audit / npm audit. This fixes audit against custom/in-house registries (yarn#7012) and is faster (the lockfile graph is already parsed). The registry, per-scope registries and auth are inherited from .npmrc / .yarnrc.yml / .yarnrc (project then global) + env, or overridden with --registry. Auth tokens are bound to the host that declared them and only sent over HTTPS.
BREAKING: because the fetch is over HTTP, the run is now async — runSync is removed. Use await run(flags) (the CLI is unchanged). The exported stages are still available if you assemble your own pipeline.
Node floor / engines: v11 no longer declares engines.node, so installing yarn-audit-fix never warns EBADENGINE on its own behalf. The effective runtime floor is Node ≥ 14.18, inherited from lockgraph. The CLI argument parser also moved off commander (which had been ratcheting its own Node floor up) to a tiny minimist-based parser — flags, env vars and --help are unchanged.
CLI flags: both --exclude and --ignore are applied client-side now (they used to be forwarded to yarn npm audit). --exclude takes comma-separated package rules — glob[@range], e.g. --exclude="lodash,@scope/*@>=2 <3" — and skips updating any package whose name matches the glob (and, if a range is given, whose installed version satisfies it); handy for a manually pinned transitive you don't want bumped. --ignore keeps its advisory scope but now matches advisory ids — comma-separated globs against the GHSA id or the npm advisory id (e.g. --ignore="GHSA-*"), dropping matching advisories before the fix. (The npm bulk-advisory endpoint doesn't expose CVE numbers, so — like zx's audit script — matching is by GHSA / npm id.) The standalone --ignore-engines flag is removed — there is no longer a reconcile yarn install for it to apply to.
v10 bumps the pkg deps and requires NodeJS v14.
v9 adds experimental Yarn 2+ lockfile support and changes how lockfiles are detected (no longer via parse failure).
From v8 the library no longer bundles npm, so the system default is used instead. If needed, you can:
- Install the required npm version and provide a custom path via CLI / ENV / JS API
- Use a pinch of npx magic:
npm_config_yes=true YAF_NPM_PATH=local npx -p yarn-audit-fix -p npm@8 -c yarn-audit-fix
Converted to ESM along with its deps, so the legacy require API was dropped in v7. Use import instead, or try esm-hook. The CLI works as before.
// const {run} = require('yarn-audit-fix') turns into
import {run} from 'yarn-audit-fix'Default fix strategy has been changed to direct lockfile patching with yarn audit --json data. The previous legacy convert flow was opt-in via --flow=convert until v11, where it was removed entirely.
The --npm-v7 flag is redundant. From v4.0.0 the package's own npm is used by default. You can still pick the system default with --npm-path=system, or a custom one with --npm-path=/another/npm/bin.
npm_config_yes=true npx yarn-audit-fix --audit-level=moderate
Runtime digest
yarn-audit-fix version 4.3.6 is out of date. Install the latest 6.0.0 for better results
npx caches previously loaded packages, so you need one of:
- Pin the version:
npx yarn-audit-fix@6.0.0 - Reset the npx cache. On macOS/Linux:
rm -rf ~/.npm/_npx
After installation, the binary may not be found — usually a $PATH issue locating node_modules/.bin (npm/issues/957). Two easy ways around it:
- Run it through yarn:
yarn yarn-audit-fix - Invoke the script directly:
node_modules/.bin/yarn-audit-fix
Some advisories can't be auto-fixed — there's no published version that satisfies the consumer's declared range, so the bump is skipped (re-run with --force to apply cross-major updates anyway).
npm_config_yes=true npx yarn-audit-fix --audit-level=moderatePatching yarn.lock with audit data...
Upgraded deps: <none>
No fix available: postcss@7.0.27, ws@7.2.3
DoneNot everything can be repaired.
yarn-audit-fix is compatible with any NodeJS version which supports ESM, but nested packages can declare their own engine requirements.
node-releases@2.0.48: The engine "node" is incompatible with this module. Expected version ">=18". Got "16.20.1"yarn-audit-fix no longer runs a yarn install, so a vulnerable project's own transitive engine constraints can't abort the fix. If you hit this while installing yarn-audit-fix itself (or on a later yarn install of your own), update the runtime — or pass the flag:
yarn add yarn-audit-fix -D --ignore-enginesEarlier versions spawned yarn npm audit / npm audit, which could choke on an unusual yarn.lock entry (e.g. a transitive in npm: alias form) and return 400 Bad Request (berry#4117).
v11 no longer spawns an audit subprocess — advisories are fetched straight from the registry's bulk endpoint for the parsed lockfile graph — so this failure mode is gone. If you still want to leave a specific package alone, exclude it client-side (no yarn version requirement):
npx yarn-audit-fix --exclude example-dependencyFeel free to open any issues: bugs, feature requests or other questions. You're always welcome to suggest a PR. Just fork this repo, write some code, add some tests and push your changes. Any feedback is appreciated.