CVE-2019-10773
Description
Yarn before 1.21.1 allows arbitrary symlink creation via crafted bin entries in package.json, enabling file overwrite attacks.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Yarn before 1.21.1 allows arbitrary symlink creation via crafted bin entries in package.json, enabling file overwrite attacks.
Vulnerability
Overview
CVE-2019-10773 is a design flaw in Yarn, the JavaScript package manager, affecting versions prior to 1.21.1. The root cause lies in the package installation process, which does not properly validate the paths specified in the bin field of package.json. An attacker can craft a malicious package with bin entries containing relative paths (e.g., "../some/path") or absolute paths, causing Yarn to create arbitrary symlinks on the host filesystem during installation [2]. This allows the attacker to control where symlinks point, potentially overwriting existing files or creating new ones at attacker-chosen locations.
Exploitation
Exploitation requires no special privileges beyond the ability to install a package. An attacker publishes a malicious package to a registry (e.g., npm) or tricks a user into installing it via other means. When the victim runs yarn install, Yarn processes the bin entries and creates symlinks accordingly. The attack does not require authentication; it relies on the user's current permissions. For example, a bin entry like "keyfile": "/Users/danielruf/.ssh/id_rsa" would create a symlink pointing to the user's SSH private key, making it accessible to the malicious package [2].
Impact
Successful exploitation can lead to arbitrary file overwrite or symlink creation, depending on the user's file system permissions. An attacker could overwrite critical system files, inject malicious code into startup scripts, or exfiltrate sensitive data by creating symlinks to files like SSH keys or configuration files. The impact is limited only by the user's write permissions; in many cases, this can result in privilege escalation or complete compromise of the user's environment [2].
Mitigation
The vulnerability was addressed in Yarn version 1.21.1, released on December 10, 2019. The fix introduces validation of bin entries, rejecting paths that escape the package's expected directory (e.g., paths containing .. or absolute paths) [3][4]. Users are strongly advised to upgrade to Yarn 1.21.1 or later. Red Hat also released an advisory (RHSA-2020:0475) covering this issue [1]. No workarounds are available; upgrading is the only reliable mitigation.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
yarnnpm | < 1.22.0 | 1.22.0 |
Affected products
2- Yarn/Yarndescription
Patches
1039bafd74b7bFixes bin overwrites (#7755)
9 files changed · +78 −4
src/reporters/lang/en.js+2 −1 modified@@ -26,7 +26,8 @@ const messages = { couldntClearPackageFromCache: "Couldn't clear package $0 from cache", clearedPackageFromCache: 'Cleared package $0 from cache', packWroteTarball: 'Wrote tarball to $0.', - + invalidBinField: 'Invalid bin field for $0.', + invalidBinEntry: 'Invalid bin entry for $1 (in $0).', helpExamples: ' Examples:\n$0\n', helpCommands: ' Commands:\n$0\n', helpCommandsMore: ' Run `$0` for more information on specific commands.',
src/util/normalize-manifest/fix.js+21 −1 modified@@ -2,7 +2,7 @@ import {MANIFEST_FIELDS} from '../../constants'; import type {Reporter} from '../../reporters/index.js'; -import {isValidLicense} from './util.js'; +import {isValidBin, isValidLicense} from './util.js'; import {normalizePerson, extractDescription} from './util.js'; import {hostedGitFragmentToGitUrl} from '../../resolvers/index.js'; import inferLicense from './infer-license.js'; @@ -12,6 +12,8 @@ const semver = require('semver'); const path = require('path'); const url = require('url'); +const VALID_BIN_KEYS = /^[a-z0-9_-]+$/i; + const LICENSE_RENAMES: {[key: string]: ?string} = { 'MIT/X11': 'MIT', X11: 'MIT', @@ -159,6 +161,24 @@ export default (async function( info.bin = {[name]: info.bin}; } + // Validate that the bin entries reference only files within their package, and that + // their name is a valid file name + if (typeof info.bin === 'object' && info.bin !== null) { + const bin: Object = info.bin; + for (const key of Object.keys(bin)) { + const target = bin[key]; + if (!VALID_BIN_KEYS.test(key) || !isValidBin(target)) { + delete bin[key]; + warn(reporter.lang('invalidBinEntry', info.name, key)); + } else { + bin[key] = path.normalize(target); + } + } + } else if (typeof info.bin !== 'undefined') { + delete info.bin; + warn(reporter.lang('invalidBinField', info.name)); + } + // bundleDependencies is an alias for bundledDependencies if (info.bundledDependencies) { info.bundleDependencies = info.bundledDependencies;
src/util/normalize-manifest/util.js+7 −0 modified@@ -2,12 +2,19 @@ import type {PersonObject} from '../../types.js'; +const path = require('path'); const validateLicense = require('validate-npm-package-license'); +const PARENT_PATH = /^\.\.([\\\/]|$)/; + export function isValidLicense(license: string): boolean { return !!license && validateLicense(license).validForNewPackages; } +export function isValidBin(bin: string): boolean { + return !path.isAbsolute(bin) && !PARENT_PATH.test(path.normalize(bin)); +} + export function stringifyPerson(person: mixed): any { if (!person || typeof person !== 'object') { return person;
__tests__/fixtures/normalize-manifest/dangerous bin name/actual.json+9 −0 added@@ -0,0 +1,9 @@ +{ + "name": "foo", + "version": "", + "bin": { + "/tmp/foo": "main.js", + "../tmp/foo": "main.js", + "tmp/../../foo": "main.js" + } +}
__tests__/fixtures/normalize-manifest/dangerous bin name/expected.json+5 −0 added@@ -0,0 +1,5 @@ +{ + "name": "foo", + "version": "", + "bin": {} +}
__tests__/fixtures/normalize-manifest/dangerous bin values/actual.json+9 −0 added@@ -0,0 +1,9 @@ +{ + "name": "foo", + "version": "", + "bin": { + "foo": "../../../../../foo", + "bar": "/hello/world", + "baz": "./foo/bar/../../../../../../foo" + } +}
__tests__/fixtures/normalize-manifest/dangerous bin values/expected.json+5 −0 added@@ -0,0 +1,5 @@ +{ + "name": "foo", + "version": "", + "bin": {} +}
__tests__/fixtures/normalize-manifest/empty bin string/expected.json+1 −2 modified@@ -1,5 +1,4 @@ { "name": "foo", - "version": "", - "bin": "" + "version": "" }
__tests__/__snapshots__/normalize-manifest.js.snap+19 −0 modified@@ -60,6 +60,24 @@ Array [ ] `; +exports[`dangerous bin name: dangerous bin name 1`] = ` +Array [ + "foo: Invalid bin entry for \\"/tmp/foo\\" (in \\"foo\\").", + "foo: Invalid bin entry for \\"../tmp/foo\\" (in \\"foo\\").", + "foo: Invalid bin entry for \\"tmp/../../foo\\" (in \\"foo\\").", + "foo: No license field", +] +`; + +exports[`dangerous bin values: dangerous bin values 1`] = ` +Array [ + "foo: Invalid bin entry for \\"foo\\" (in \\"foo\\").", + "foo: Invalid bin entry for \\"bar\\" (in \\"foo\\").", + "foo: Invalid bin entry for \\"baz\\" (in \\"foo\\").", + "foo: No license field", +] +`; + exports[`dedupe all trivial dependencies: dedupe all trivial dependencies 1`] = ` Array [ "No license field", @@ -92,6 +110,7 @@ Array [ exports[`empty bin string: empty bin string 1`] = ` Array [ + "foo: Invalid bin field for \\"foo\\".", "foo: No license field", ] `;
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
14- access.redhat.com/errata/RHSA-2020:0475ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-5xf4-f2fq-f69jghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/3HIZW4NZVV5QY5WWGW2JRP3FHYKZ6ZJ5/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/ITY5BC63CCC647DFNUQRQ5AJDKUKUNBI/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2019-10773ghsaADVISORY
- blog.daniel-ruf.de/critical-design-flaw-npm-pnpm-yarnghsaWEB
- blog.daniel-ruf.de/critical-design-flaw-npm-pnpm-yarn/mitrex_refsource_MISC
- github.com/yarnpkg/yarn/commit/039bafd74b7b1a88a53a54f8fa6fa872615e90e7ghsax_refsource_MISCWEB
- github.com/yarnpkg/yarn/issues/7761ghsax_refsource_CONFIRMWEB
- github.com/yarnpkg/yarn/pull/7755ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3HIZW4NZVV5QY5WWGW2JRP3FHYKZ6ZJ5ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ITY5BC63CCC647DFNUQRQ5AJDKUKUNBIghsaWEB
- snyk.io/vuln/SNYK-JS-YARN-537806%2Cmitrex_refsource_MISC
- snyk.io/vuln/SNYK-JS-YARN-537806,ghsaWEB
News mentions
0No linked articles in our index yet.