CVE-2021-32804 (JavaScript, published 2021-08-03T19:15Z)
Description: value: The npm package "tar" (aka node-tar) before versions 6.1.1, 5.0.6, 4.4.14, and 3.3.2 has a arbitrary File Creation/Overwrite vulnerability due to insufficient absolute path sanitization. node-tar aims to prevent extraction of absolute file paths by turning absolute paths into relative paths when the `preservePaths` flag is not set to `true`. This is achieved by stripping the absolute path root from any absolute file paths contained in a tar file. For example `/home/user/.bashrc` would turn into `home/user/.bashrc`. This logic was insufficient when file paths contained repeated path roots such as `////home/user/.bashrc`. `node-tar` would only strip a single path root from such paths. When given an absolute file path with repeating path roots, the resulting path (e.g. `///home/user/.bashrc`) would still resolve to an absolute path, thus allowing arbitrary file creation and overwrite. This issue was addressed in releases 3.2.2, 4.4.14, 5.0.6 and 6.1.1. Users may work around this vulnerability without upgrading by creating a custom `onentry` method which sanitizes the `entry.path` or a `filter` method which removes entries with absolute paths. See referenced GitHub Advisory for details. Be aware of CVE-2021-32803 which fixes a similar bug in later versions of tar.
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/npm/node-tar
Files changed:
lib/strip-absolute-path.js
lib/unpack.js
lib/write-entry.js
test/strip-absolute-path.js
test/unpack.js
test/write-entry.js
Commit message: fix: strip absolute paths more comprehensively


Vulnerable code:
      if (path.win32.isAbsolute(p)) {
        const parsed = path.win32.parse(p)
        entry.path = p.substr(parsed.root.length)
        const r = parsed.root
        this.warn('TAR_ENTRY_INFO', `stripping ${r} from absolute path`, {
    if (!this.preservePaths && path.win32.isAbsolute(p)) {
      // absolutes on posix are also absolutes on win32
      // so we only need to test this one to get both
      const parsed = path.win32.parse(p)
      this.path = p.substr(parsed.root.length)
      pathWarn = parsed.root
    if (path.isAbsolute(this.path) && !this.preservePaths) {
      const parsed = path.parse(this.path)
      pathWarn = parsed.root
      this.path = this.path.substr(parsed.root.length)
      path: absolute,
        'stripping / from absolute path',
  const f = path.resolve(files, '512-bytes.txt')
        'TAR_ENTRY_INFO', /stripping .* from absolute path/, { path: f },
        path: f.replace(/^(\/|[a-z]:\\\\)/, ''),

Fixed code:
// unix absolute paths are also absolute on win32, so we use this for both
const { isAbsolute, parse } = require('path').win32

// returns [root, stripped]
module.exports = path => {
  let r = ''
  while (isAbsolute(path)) {
    // windows will think that //x/y/z has a "root" of //x/y/
    const root = path.charAt(0) === '/' ? '/' : parse(path).root
    path = path.substr(root.length)
    r += root
  }
  return [r, path]
}
const stripAbsolutePath = require('./strip-absolute-path.js')
      const [root, stripped] = stripAbsolutePath(p)
      if (root) {
        entry.path = stripped
        this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute path`, {
const stripAbsolutePath = require('./strip-absolute-path.js')
    if (!this.preservePaths) {
      const [root, stripped] = stripAbsolutePath(this.path)
      if (root) {
        this.path = stripped
        pathWarn = root
      }
    if (!this.preservePaths) {
      const [root, stripped] = stripAbsolutePath(this.path)
      if (root) {
        this.path = stripped
        pathWarn = root
      }
const t = require('tap')
const stripAbsolutePath = require('../lib/strip-absolute-path.js')

const cases = {
  '/': ['/', ''],
  '////': ['////', ''],
  'c:///a/b/c': ['c:///', 'a/b/c'],
  '\\\\foo\\bar\\baz': ['\\\\foo\\bar\\', 'baz'],
  '//foo//bar//baz': ['//', 'foo//bar//baz'],
  'c:\\c:\\c:\\c:\\\\d:\\e/f/g': ['c:\\c:\\c:\\c:\\\\d:\\', 'e/f/g'],
}

for (const [input, [root, stripped]] of Object.entries(cases))
  t.strictSame(stripAbsolutePath(input), [root, stripped], input)
  const root = path.parse(absolute).root
  const extraAbsolute = root + root + root + absolute
  t.ok(path.isAbsolute(extraAbsolute))
      path: extraAbsolute,
        `stripping ${root}${root}${root}${root} from absolute path`,
  const absolute = path.resolve(files, '512-bytes.txt')
  const { root } = path.parse(absolute)
  const f = root + root + root + absolute
  const warn = root + root + root + root
        'TAR_ENTRY_INFO', `stripping ${warn} from absolute path`, { path: f },
        path: f.replace(/^(\/|[a-z]:\\\\){4}/, ''),