CVE-2019-10773 (Other, published 2019-12-16T20:15Z)
Description: value: In Yarn before 1.21.1, the package install functionality can be abused to generate arbitrary symlinks on the host filesystem by using specially crafted "bin" keys. Existing files could be overwritten depending on the current user permission set.
Severity: MEDIUM (CVSS v3 base score: 7.8)
Weakness: CWE-59 \u2014 Improper Link Resolution Before File Access ('Link Following')
The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
Repository: https://github.com/yarnpkg/yarn
Files changed:
__tests__/__snapshots__/normalize-manifest.js.snap
__tests__/fixtures/normalize-manifest/dangerous bin name/actual.json	
__tests__/fixtures/normalize-manifest/dangerous bin name/expected.json	
__tests__/fixtures/normalize-manifest/dangerous bin values/actual.json	
__tests__/fixtures/normalize-manifest/dangerous bin values/expected.json	
__tests__/fixtures/normalize-manifest/empty bin string/expected.json	
src/reporters/lang/en.js
src/util/normalize-manifest/fix.js
src/util/normalize-manifest/util.js
Commit message: Fixes bin overwrites (#7755)

* Fixes potential file overwrite at install time

* Fixes the regexp

* Adds warnings

* Fixes overzealous removals

* Fixes test

* Adds tests


Vulnerable code:
  "version": "",
  "bin": ""

import {isValidLicense} from './util.js';

Fixed code:
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",
]
`;

  "foo: Invalid bin field for \\"foo\\".",
{
    "name": "foo",
    "version": "",
    "bin": {
        "/tmp/foo": "main.js",
        "../tmp/foo": "main.js",
        "tmp/../../foo": "main.js"
    }
}
{
    "name": "foo",
    "version": "",
    "bin": {}
}
{
    "name": "foo",
    "version": "",
    "bin": {
        "foo": "../../../../../foo",
        "bar": "/hello/world",
        "baz": "./foo/bar/../../../../../../foo"
    }
}
{
    "name": "foo",
    "version": "",
    "bin": {}
}
  "version": ""
  invalidBinField: 'Invalid bin field for $0.',
  invalidBinEntry: 'Invalid bin entry for $1 (in $0).',
import {isValidBin, isValidLicense} from './util.js';
const VALID_BIN_KEYS = /^[a-z0-9_-]+$/i;

  // 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));
  }

const path = require('path');
const PARENT_PATH = /^\.\.([\\\/]|$)/;

export function isValidBin(bin: string): boolean {
  return !path.isAbsolute(bin) && !PARENT_PATH.test(path.normalize(bin));
}