VYPR
High severityNVD Advisory· Published Aug 3, 2021· Updated Aug 3, 2024

Arbitrary File Creation/Overwrite due to insufficient absolute path sanitization

CVE-2021-32804

Description

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.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

The npm package "tar" (node-tar) before versions 6.1.1, 5.0.6, 4.4.14, and 3.3.2 allows arbitrary file creation/overwrite due to insufficient absolute path sanitization when handling tar entries with repeated path roots, potentially leading to code execution.

Vulnerability

This vulnerability exists in the npm package tar (aka node-tar) before versions 6.1.1, 5.0.6, 4.4.14, and 3.3.2. The tar module attempts to prevent extraction of absolute file paths by converting them 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. However, this logic was insufficient when file paths contained repeated path roots, such as ////home/user/.bashrc. The implementation would only strip a single path root, leaving a path like ///home/user/.bashrc, which still resolves to an absolute path, thereby allowing arbitrary file creation and overwrite [1][2][4].

Exploitation

An attacker needs to provide a crafted tar archive containing entries with absolute file paths that include repeated path roots (e.g., ////home/user/.bashrc). The attacker does not need any special network position beyond being able to supply the tar file to a system using a vulnerable version of node-tar. No authentication is required if the vulnerable code is processing untrusted archives. The extraction occurs without the preservePaths flag set (the default), and the flawed sanitization fails to fully strip the excess path roots, resulting in an absolute path that can overwrite or create files outside the intended extraction target [2][4].

Impact

Successful exploitation allows an attacker to arbitrarily create or overwrite files on the file system. This can lead to privilege escalation, denial of service, or arbitrary code execution if the overwritten file is used in an elevated context or by other processes (e.g., overwriting a script that is later executed). The impact is limited by the permissions of the process running the vulnerable code, but in many cases, full system compromise may be achievable [2][4].

Mitigation

The issue is addressed in tar versions 3.2.2, 4.4.14, 5.0.6, and 6.1.1 [2][3][4]. Users should upgrade to one of these fixed versions or later. If upgrading is not immediately possible, users can implement a workaround by creating a custom onentry method to sanitize entry.path or a filter method to remove entries with absolute paths. The project advisory provides example code for both approaches [4]. Note that an adjacent issue, CVE-2021-32803, also affects this release level and may require further patching [2][4].

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.

PackageAffected versionsPatched versions
tarnpm
< 3.2.23.2.2
tarnpm
>= 4.0.0, < 4.4.144.4.14
tarnpm
>= 5.0.0, < 5.0.65.0.6
tarnpm
>= 6.0.0, < 6.1.16.1.1

Affected products

91

Patches

1
1f036ca23f64

fix: strip absolute paths more comprehensively

https://github.com/npm/node-tarisaacsJul 23, 2021via ghsa
6 files changed · +57 20
  • lib/strip-absolute-path.js+14 0 added
    @@ -0,0 +1,14 @@
    +// 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]
    +}
    
  • lib/unpack.js+5 5 modified
    @@ -14,6 +14,7 @@ const path = require('path')
     const mkdir = require('./mkdir.js')
     const wc = require('./winchars.js')
     const pathReservations = require('./path-reservations.js')
    +const stripAbsolutePath = require('./strip-absolute-path.js')
     
     const ONENTRY = Symbol('onEntry')
     const CHECKFS = Symbol('checkFs')
    @@ -224,11 +225,10 @@ class Unpack extends Parser {
     
           // absolutes on posix are also absolutes on win32
           // so we only need to test this one to get both
    -      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`, {
    +      const [root, stripped] = stripAbsolutePath(p)
    +      if (root) {
    +        entry.path = stripped
    +        this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute path`, {
               entry,
               path: p,
             })
    
  • lib/write-entry.js+13 10 modified
    @@ -23,6 +23,7 @@ const CLOSE = Symbol('close')
     const MODE = Symbol('mode')
     const warner = require('./warn-mixin.js')
     const winchars = require('./winchars.js')
    +const stripAbsolutePath = require('./strip-absolute-path.js')
     
     const modeFix = require('./mode-fix.js')
     
    @@ -52,12 +53,12 @@ const WriteEntry = warner(class WriteEntry extends MiniPass {
           this.on('warn', opt.onwarn)
     
         let pathWarn = false
    -    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 (!this.preservePaths) {
    +      const [root, stripped] = stripAbsolutePath(this.path)
    +      if (root) {
    +        this.path = stripped
    +        pathWarn = root
    +      }
         }
     
         this.win32 = !!opt.win32 || process.platform === 'win32'
    @@ -351,10 +352,12 @@ const WriteEntryTar = warner(class WriteEntryTar extends MiniPass {
           this.on('warn', opt.onwarn)
     
         let pathWarn = false
    -    if (path.isAbsolute(this.path) && !this.preservePaths) {
    -      const parsed = path.parse(this.path)
    -      pathWarn = parsed.root
    -      this.path = this.path.substr(parsed.root.length)
    +    if (!this.preservePaths) {
    +      const [root, stripped] = stripAbsolutePath(this.path)
    +      if (root) {
    +        this.path = stripped
    +        pathWarn = root
    +      }
         }
     
         this.remain = readEntry.size
    
  • test/strip-absolute-path.js+14 0 added
    @@ -0,0 +1,14 @@
    +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)
    
  • test/unpack.js+5 2 modified
    @@ -776,14 +776,17 @@ t.test('absolute paths', t => {
       })
     
       const absolute = path.resolve(dir, 'd/i/r/absolute')
    +  const root = path.parse(absolute).root
    +  const extraAbsolute = root + root + root + absolute
    +  t.ok(path.isAbsolute(extraAbsolute))
       t.ok(path.isAbsolute(absolute))
       const parsed = path.parse(absolute)
       const relative = absolute.substr(parsed.root.length)
       t.notOk(path.isAbsolute(relative))
     
       const data = makeTar([
         {
    -      path: absolute,
    +      path: extraAbsolute,
           type: 'File',
           size: 1,
           atime: new Date('1979-07-01T19:10:00.000Z'),
    @@ -798,7 +801,7 @@ t.test('absolute paths', t => {
       t.test('warn and correct', t => {
         const check = t => {
           t.match(warnings, [[
    -        'stripping / from absolute path',
    +        `stripping ${root}${root}${root}${root} from absolute path`,
             { path: absolute, code: 'TAR_ENTRY_INFO' },
           ]])
           t.ok(fs.lstatSync(path.resolve(dir, relative)).isFile(), 'is file')
    
  • test/write-entry.js+6 3 modified
    @@ -385,7 +385,10 @@ t.test('nonexistent file', t => {
     })
     
     t.test('absolute path', t => {
    -  const f = path.resolve(files, '512-bytes.txt')
    +  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
       t.test('preservePaths=false strict=false', t => {
         const warnings = []
         const ws = new WriteEntry(f, {
    @@ -398,13 +401,13 @@ t.test('absolute path', t => {
           out = Buffer.concat(out)
           t.equal(out.length, 1024)
           t.match(warnings, [[
    -        'TAR_ENTRY_INFO', /stripping .* from absolute path/, { path: f },
    +        'TAR_ENTRY_INFO', `stripping ${warn} from absolute path`, { path: f },
           ]])
     
           t.match(ws.header, {
             cksumValid: true,
             needPax: false,
    -        path: f.replace(/^(\/|[a-z]:\\\\)/, ''),
    +        path: f.replace(/^(\/|[a-z]:\\\\){4}/, ''),
             mode: 0o644,
             size: 512,
             linkpath: null,
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.