[Pkg-javascript-devel] Bug#1140683: bookworm-pu: package node-tar/6.1.13+~cs7.0.5-1+deb12u1

Daniel Leidert dleidert at debian.org
Wed Jun 24 15:08:59 BST 2026


Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: node-tar at packages.debian.org
Control: affects -1 + src:node-tar
User: release.debian.org at packages.debian.org
Usertags: pu

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

[ Reason ]

node-tar in Bookworm is vulnerable to multiple CVEs.

  - CVE-2024-28863: excessive memory consumption
  - CVE-2026-23745: sanitize absolute linkpaths properly
    (the fix opens CVE-2026-24842 and CVE-2026-31802)
  - CVE-2026-26960: do not write linkpaths through symlinks
  - CVE-2026-29786: parse root off paths before sanitizing parts

By fixing CVE-2026-23745, it becomes necessary to fix CVE-2026-24842 and
CVE-2026-31802 as well, because the fix introduces the vulnerable code for
these issues:

  - CVE-2026-24842: properly sanitize hard links containing '..'
  - CVE-2026-31802: prevent escaping symlinks with drive-relative paths

[ Impact ]

The issues are currently fixed in Bullseye and in Trixie. Thus, users of
Bookworm are, and users upgrading to Bookworm become vulnerable to these
issues.

[ Tests ]

All tests are passing.

[ Risks ]

There is the risk of regression. However, tests are passing and the fixes have
been successfully applied and tested in Bullseye and Trixie.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEvu1N7VVEpMA+KD3HS80FZ8KW0F0FAmo75PsACgkQS80FZ8KW
0F3iEhAA2OWEX4qZ9BGLa2gyoMr6XAf+cA4DWG97PL1jmVlabf6/ZkOhVnF6oWbA
Z4+pfKiYsI4uur23lwn4jgU0kEyB0OA6fYOGQf5madlb9O4AyX4D09js2PKPhllv
+PqSx9JYJEHVSWqGaaDSkGjUjYNd0xp3GkZNlPZAc3tj9fQ+JiGitBr5gtwv1bz8
bitozUS2k5aAnzTWZYfnObG6MvoleQaDxzW2y5dNs5xve0A72Ahc5K0UCuwbl/l9
rCiLMvDym0jjLhI0DNDtd1lBETQrArXZ/qwE/onnMKz+T4eY5XBtKXYfleQ3bSwz
qfoC/VPYbVkx7GzemZ0XaYCd8Vdv4hcasboF18qokGFVdNCLXXe+qbrDctwtaC8D
iBFFhJ2d0JP4Qyk2N1yDudzn3BXZYuXH8fyocc8b0IF8/R1249CEajas4bHYHM12
QOg7QE/vrlqMOPHeugkrYKbrpSHnwYbl+yvNyjgPcX2uKRd/3G1F0vvvRnr9Cdc8
4GacH84J5bJPExjtwEaAn7UxtovFIAER8LbXs90deB7eNjDWiBNk66v3PQRgDq+X
6CCzymP0w/tZ/yAwgN6aI3XSx1x/jZNHX+u+m9AoX2hynRV+KLa1XIwzsOoNo7LJ
VGWinuff6RL+2iHFa5L+GBjAtu1D42h4tvgfXmGM7nR6Wythj+w=
=yC0W
-----END PGP SIGNATURE-----
-------------- next part --------------
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/changelog node-tar-6.1.13+~cs7.0.5/debian/changelog
--- node-tar-6.1.13+~cs7.0.5/debian/changelog	2022-12-17 14:53:17.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/changelog	2026-04-30 04:21:06.000000000 +0200
@@ -1,3 +1,39 @@
+node-tar (6.1.13+~cs7.0.5-1+deb12u1) bookworm; urgency=medium
+
+  * Non-maintainer upload by the Debian LTS team.
+  * d/patches/CVE-2024-28863.patch: Add patch to fix CVE-2024-28863.
+    - Generating a large number of sub-folders can consume memory on the system
+      and even crash the Node.js client within a few seconds using a path with
+      too many sub-folders inside.
+  * d/patches/CVE-2026-23745.patch: Add patch to fix CVE-2026-23745.
+    - When preservePaths is false, the linkpath of Link (hardlink) and
+      SymbolicLink entries fail to be sanitized, allowing malicious archives to
+      bypass the extraction root restriction, leading to arbitrary file
+      overwrites via hardlinks and symlink poisoning via absolute symlink
+      targets.
+  * d/patches/CVE-2026-23745-regression-fix.patch: Add patch to fix a
+    regression introduced by the fix for CVE-2026-23745.
+    - The fix for CVE-2026-23745 introduces a regression that prevents
+      unpacking archives with valid linkpaths within the archive.
+  * d/patches/CVE-2026-24842.patch: Add patch to fix CVE-2026-24842.
+    - The security check for hardlink entries allows an attacker to craft a
+      malicious TAR archive that bypasses path traversal protections and
+      creates hardlinks to arbitrary files outside the extraction directory.
+  * d/patches/CVE-2026-26960-1.patch,
+    d/patches/CVE-2026-26960-2.patch: Add patch to fix CVE-2026-26960.
+    - An attacker-controlled archive can create a hardlink inside the
+      extraction directory that points to a file outside the extraction root,
+      enabling arbitrary file read and write as the extracting user.
+  * d/patches/CVE-2026-29786.patch: Add patch to fix CVE-2026-29786.
+    - An attacker-controlled archive can create a hardlink that points outside
+      the extraction directory by using a drive-relative link target.
+  * d/patches/CVE-2026-31802.patch: Add patch to fix CVE-2026-31802.
+    - An attacker-controlled archive can create a hardlink that points outside
+      the extraction directory by using a drive-relative link target.
+  * d/patches/fix-tests.patch: Fix autopkgtest.
+
+ -- Daniel Leidert <dleidert at debian.org>  Thu, 30 Apr 2026 04:21:06 +0200
+
 node-tar (6.1.13+~cs7.0.5-1) unstable; urgency=medium
 
   * Team upload
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/gbp.conf node-tar-6.1.13+~cs7.0.5/debian/gbp.conf
--- node-tar-6.1.13+~cs7.0.5/debian/gbp.conf	2022-11-10 14:49:38.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/gbp.conf	2026-04-30 04:21:06.000000000 +0200
@@ -4,7 +4,7 @@
 upstream-branch=upstream
 # The default name for the Debian branch is "master".
 # Change it if the name is different (for instance, "debian/unstable").
-debian-branch=master
+debian-branch=debian/bookworm
 # git-import-orig uses the following names for the upstream tags.
 # Change the value if you are not using git-import-orig
 upstream-tag=upstream/%(version)s
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/api-backward-compatibility.patch node-tar-6.1.13+~cs7.0.5/debian/patches/api-backward-compatibility.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/api-backward-compatibility.patch	2021-11-11 18:42:10.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/api-backward-compatibility.patch	2026-04-30 04:21:06.000000000 +0200
@@ -1,7 +1,15 @@
-Description: expose old method names for backward compatibility
-Author: J?r?my Lal <kapouer at melix.org>
+From: =?utf-8?b?SsOpcsOpbXkgTGFs?= <kapouer at melix.org>
+Date: Sun, 1 Mar 2026 01:31:59 +0100
+Subject: expose old method names for backward compatibility
+
 Last-Update: 2018-06-08
 Forwarded: not-needed
+---
+ index.js | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/index.js b/index.js
+index c9ae06e..1c0b826 100644
 --- a/index.js
 +++ b/index.js
 @@ -1,11 +1,11 @@
Bin?rdateien /tmp/_PugFZ0wrL/node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2024-28863.patch und /tmp/2mvRegGifr/node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2024-28863.patch sind verschieden.
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745.patch node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,163 @@
+From: isaacs <i at izs.me>
+Date: Fri, 16 Jan 2026 12:33:17 -0800
+Subject: [PATCH] fix: sanitize absolute linkpaths properly
+
+Fix: https://github.com/isaacs/node-tar/security/advisories/GHSA-8qq5-rm4j-mr97
+
+Reviewed-By: Daniel Leidert <dleidert at debian.org>
+Origin: https://github.com/isaacs/node-tar/commit/340eb285b6d986e91969a1170d7fe9b0face405e
+Bug: https://github.com/isaacs/node-tar/security/advisories/GHSA-8qq5-rm4j-mr97
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-23745
+Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-23745
+---
+ lib/unpack.js               | 61 ++++++++++++++++++++++++++++++++-------------
+ test/ghsa-8qq5-rm4j-mr97.js | 54 +++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 97 insertions(+), 18 deletions(-)
+ create mode 100644 test/ghsa-8qq5-rm4j-mr97.js
+
+diff --git a/lib/unpack.js b/lib/unpack.js
+index 6b4ba4e..0c0d095 100644
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -32,6 +32,7 @@ const SYMLINK = Symbol('symlink')
+ const HARDLINK = Symbol('hardlink')
+ const UNSUPPORTED = Symbol('unsupported')
+ const CHECKPATH = Symbol('checkPath')
++const STRIPABSOLUTEPATH = Symbol('stripAbsolutePath')
+ const MKDIR = Symbol('mkdir')
+ const ONERROR = Symbol('onError')
+ const PENDING = Symbol('pending')
+@@ -244,6 +245,43 @@ class Unpack extends Parser {
+     }
+   }
+ 
++  // return false if we need to skip this file
++  // return true if the field was successfully sanitized
++  [STRIPABSOLUTEPATH](entry, field) {
++    const path = entry[field]
++    if (!path || this.preservePaths) return true
++
++    const parts = path.split('/')
++    if (
++      parts.includes('..') ||
++      /* c8 ignore next */
++      (isWindows && /^[a-z]:\.\.$/i.test(parts[0]))
++    ) {
++      this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, {
++        entry,
++        [field]: path,
++      })
++      // not ok!
++      return false
++    }
++
++    // strip off the root
++    const [root, stripped] = stripAbsolutePath(path)
++    if (root) {
++      // ok, but triggers warning about stripping root
++      entry[field] = String(stripped)
++      this.warn(
++        'TAR_ENTRY_INFO',
++        `stripping ${root} from absolute ${field}`,
++        {
++          entry,
++          [field]: path,
++        },
++      )
++    }
++    return true
++  }
++
+   [CHECKPATH] (entry) {
+     const p = normPath(entry.path)
+     const parts = p.split('/')
+@@ -274,24 +312,11 @@ class Unpack extends Parser {
+       return false
+     }
+ 
+-    if (!this.preservePaths) {
+-      if (parts.includes('..') || isWindows && /^[a-z]:\.\.$/i.test(parts[0])) {
+-        this.warn('TAR_ENTRY_ERROR', `path contains '..'`, {
+-          entry,
+-          path: p,
+-        })
+-        return false
+-      }
+-
+-      // strip off the root
+-      const [root, stripped] = stripAbsolutePath(p)
+-      if (root) {
+-        entry.path = stripped
+-        this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute path`, {
+-          entry,
+-          path: p,
+-        })
+-      }
++    if (
++      !this[STRIPABSOLUTEPATH](entry, 'path') ||
++      !this[STRIPABSOLUTEPATH](entry, 'linkpath')
++    ) {
++      return false
+     }
+ 
+     if (path.isAbsolute(entry.path)) {
+diff --git a/test/ghsa-8qq5-rm4j-mr97.js b/test/ghsa-8qq5-rm4j-mr97.js
+new file mode 100644
+index 0000000..851aef5
+--- /dev/null
++++ b/test/ghsa-8qq5-rm4j-mr97.js
+@@ -0,0 +1,54 @@
++'use strict'
++
++const { readFileSync, readlinkSync, writeFileSync } = require('fs')
++const { resolve } = require('path')
++const t = require('tap')
++const mkdirp = require('mkdirp')
++const rimraf = require('rimraf')
++const { Header, x } = require('tar')
++
++const targetSym = '/some/absolute/path'
++const fixtures = resolve(__dirname, 'fixtures')
++const sanitizerdir = resolve(fixtures, 'ghsa-8qq5-rm4j-mr97')
++const dir = resolve(sanitizerdir, 'linkpath-sanitization')
++const secretFile = resolve(dir, 'secret.txt')
++const tarFile = resolve(dir, 'exploit.tar')
++const out = resolve(dir, 'out_repro')
++
++const getExploitTar = () => {
++  const exploitTar = Buffer.alloc(512 + 512 + 1024)
++
++  new Header({
++    path: 'exploit_hard',
++    type: 'Link',
++    size: 0,
++    linkpath: secretFile,
++  }).encode(exploitTar, 0)
++
++  new Header({
++    path: 'exploit_sym',
++    type: 'SymbolicLink',
++    size: 0,
++    linkpath: targetSym,
++  }).encode(exploitTar, 512)
++
++  return exploitTar
++}
++
++t.test('verify that linkpaths get sanitized properly', t => {
++  mkdirp.sync(out)
++  writeFileSync(secretFile, 'ORIGINAL DATA')
++  writeFileSync(tarFile, getExploitTar())
++  t.teardown(_ => rimraf.sync(dir))
++
++  x({
++    cwd: out,
++    file: tarFile,
++    preservePaths: false,
++  }).then(() => {
++    writeFileSync(resolve(out, 'exploit_hard'), 'OVERWRITTEN')
++    t.equal(readFileSync(secretFile, 'utf8'), 'ORIGINAL DATA')
++    t.not(readlinkSync(resolve(out, 'exploit_sym')), targetSym)
++    t.end()
++  })
++})
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745-regression-fix.patch node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745-regression-fix.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745-regression-fix.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-23745-regression-fix.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,309 @@
+From: Nathan Sarang-Walters <nwalters512 at gmail.com>
+Date: Tue, 20 Jan 2026 14:35:00 -0800
+Subject: [PATCH] fix: do not prevent valid linkpaths within archive
+
+PR-URL: https://github.com/isaacs/node-tar/pull/450
+Credit: @nwalters512
+Close: #450
+Reviewed-by: @isaacs
+
+EDIT(@isaacs): fixed for test coverage and to disallow absolute
+linkpaths that contain `..`
+
+Reviewed-By: Daniel Leidert <dleidert at debian.org>
+Origin: https://github.com/isaacs/node-tar/commit/e9a1ddb821b29ddee75b9470dd511066148c8070
+Bug: https://github.com/isaacs/node-tar/pull/450
+---
+ lib/unpack.js               | 49 +++++++++++++++++++++-------
+ test/ghsa-8qq5-rm4j-mr97.js | 79 ++++++++++++++++++++++++++++++++++++++-------
+ test/unpack.js              | 78 ++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 183 insertions(+), 23 deletions(-)
+
+diff --git a/lib/unpack.js b/lib/unpack.js
+index 0c0d095..baf8f1b 100644
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -248,25 +248,52 @@ class Unpack extends Parser {
+   // return false if we need to skip this file
+   // return true if the field was successfully sanitized
+   [STRIPABSOLUTEPATH](entry, field) {
+-    const path = entry[field]
+-    if (!path || this.preservePaths) return true
++    const p = entry[field]
++    if (!p || this.preservePaths) return true
++
++    const parts = p.split('/')
+ 
+-    const parts = path.split('/')
+     if (
+       parts.includes('..') ||
+       /* c8 ignore next */
+       (isWindows && /^[a-z]:\.\.$/i.test(parts[0]))
+     ) {
+-      this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, {
+-        entry,
+-        [field]: path,
+-      })
+-      // not ok!
+-      return false
++      // For linkpath, check if the resolved path escapes cwd rather than
++      // just rejecting any path with '..' - relative symlinks like
++      // '../sibling/file' are valid if they resolve within the cwd.
++      // For paths, they just simply may not ever use .. at all.
++      if (field === 'path') {
++        this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, {
++          entry,
++          [field]: p,
++        })
++        // not ok!
++        return false
++      } else {
++        // Resolve linkpath relative to the entry's directory.
++        // `path.posix` is safe to use because we're operating on
++        // tar paths, not a filesystem.
++        const entryDir = path.posix.dirname(entry.path)
++        const resolved = path.posix.normalize(
++          path.posix.join(entryDir, p),
++        )
++        // If the resolved path escapes (starts with ..), reject it
++        if (resolved.startsWith('../') || resolved === '..') {
++          this.warn(
++            'TAR_ENTRY_ERROR',
++            `${field} escapes extraction directory`,
++            {
++              entry,
++              [field]: p,
++            },
++          )
++          return false
++        }
++      }
+     }
+ 
+     // strip off the root
+-    const [root, stripped] = stripAbsolutePath(path)
++    const [root, stripped] = stripAbsolutePath(p)
+     if (root) {
+       // ok, but triggers warning about stripping root
+       entry[field] = String(stripped)
+@@ -275,7 +302,7 @@ class Unpack extends Parser {
+         `stripping ${root} from absolute ${field}`,
+         {
+           entry,
+-          [field]: path,
++          [field]: p,
+         },
+       )
+     }
+diff --git a/test/ghsa-8qq5-rm4j-mr97.js b/test/ghsa-8qq5-rm4j-mr97.js
+index 851aef5..95b0e03 100644
+--- a/test/ghsa-8qq5-rm4j-mr97.js
++++ b/test/ghsa-8qq5-rm4j-mr97.js
+@@ -1,6 +1,6 @@
+ 'use strict'
+ 
+-const { readFileSync, readlinkSync, writeFileSync } = require('fs')
++const { lstatSync, readFileSync, readlinkSync, writeFileSync } = require('fs')
+ const { resolve } = require('path')
+ const t = require('tap')
+ const mkdirp = require('mkdirp')
+@@ -8,47 +8,102 @@ const rimraf = require('rimraf')
+ const { Header, x } = require('tar')
+ 
+ const targetSym = '/some/absolute/path'
++const absoluteWithDotDot = '/../a/target'
+ const fixtures = resolve(__dirname, 'fixtures')
+ const sanitizerdir = resolve(fixtures, 'ghsa-8qq5-rm4j-mr97')
+ const dir = resolve(sanitizerdir, 'linkpath-sanitization')
+ const secretFile = resolve(dir, 'secret.txt')
+ const tarFile = resolve(dir, 'exploit.tar')
+-const out = resolve(dir, 'out_repro')
++const out = resolve(dir, 'out')
+ 
+ const getExploitTar = () => {
+-  const exploitTar = Buffer.alloc(512 + 512 + 1024)
++  const chunks = []
+ 
++  const hardHeader = Buffer.alloc(512)
+   new Header({
+     path: 'exploit_hard',
+     type: 'Link',
+     size: 0,
+     linkpath: secretFile,
+-  }).encode(exploitTar, 0)
++  }).encode(hardHeader, 0)
++  chunks.push(hardHeader)
+ 
++  const symHeader = Buffer.alloc(512)
+   new Header({
+     path: 'exploit_sym',
+     type: 'SymbolicLink',
+     size: 0,
+     linkpath: targetSym,
+-  }).encode(exploitTar, 512)
++  }).encode(symHeader, 0)
++  chunks.push(symHeader)
+ 
+-  return exploitTar
++  const escapeHeader = Buffer.alloc(512)
++  new Header({
++    path: 'secret.txt',
++    type: 'SymbolicLink',
++    linkpath: '../secret.txt',
++  }).encode(escapeHeader, 0)
++  chunks.push(escapeHeader)
++
++  const aDirHeader = Buffer.alloc(512)
++  new Header({
++    path: 'a/',
++    type: 'Directory',
++    mode: 0o755,
++  }).encode(aDirHeader, 0)
++  chunks.push(aDirHeader)
++
++  const absWithDotDotHeader = Buffer.alloc(512)
++  new Header({
++    path: 'a/link',
++    type: 'SymbolicLink',
++    linkpath: absoluteWithDotDot,
++  }).encode(absWithDotDotHeader, 0)
++  chunks.push(absWithDotDotHeader)
++
++  chunks.push(Buffer.alloc(1024))
++  return Buffer.concat(chunks)
+ }
+ 
+-t.test('verify that linkpaths get sanitized properly', t => {
++t.test('hardlink escape does not clobber target', t => {
+   mkdirp.sync(out)
+   writeFileSync(secretFile, 'ORIGINAL DATA')
+   writeFileSync(tarFile, getExploitTar())
+   t.teardown(_ => rimraf.sync(dir))
+ 
+-  x({
+-    cwd: out,
+-    file: tarFile,
+-    preservePaths: false,
+-  }).then(() => {
++  x({ cwd: out, file: tarFile }).then(() => {
+     writeFileSync(resolve(out, 'exploit_hard'), 'OVERWRITTEN')
+     t.equal(readFileSync(secretFile, 'utf8'), 'ORIGINAL DATA')
++    t.end()
++  })
++})
++
++t.test('symlink escapes are sanitized', t => {
++  mkdirp.sync(out)
++  writeFileSync(secretFile, 'ORIGINAL DATA')
++  writeFileSync(tarFile, getExploitTar())
++  t.teardown(_ => rimraf.sync(dir))
++
++  x({ cwd: out, file: tarFile }).then(() => {
+     t.not(readlinkSync(resolve(out, 'exploit_sym')), targetSym)
++    t.throws(() => lstatSync(resolve(out, 'secret.txt')), {
++      code: 'ENOENT',
++    })
++    t.end()
++  })
++})
++
++t.test('absolute symlink with .. has prefix stripped', t => {
++  mkdirp.sync(out)
++  writeFileSync(tarFile, getExploitTar())
++  t.teardown(_ => rimraf.sync(dir))
++
++  x({ cwd: out, file: tarFile }).then(() => {
++    t.equal(
++      readlinkSync(resolve(out, 'a/link')),
++      '../a/target',
++      'symlink target should be normalized',
++    )
+     t.end()
+   })
+ })
+diff --git a/test/unpack.js b/test/unpack.js
+index 198f034..06e9140 100644
+--- a/test/unpack.js
++++ b/test/unpack.js
+@@ -3294,3 +3294,81 @@ t.test('excessively deep subfolder nesting', async t => {
+     check(t, 64)
+   })
+ })
++
++t.test('valid relative symlink with .. should be extracted', t => {
++  const dir = path.resolve(unpackdir, 'relative-symlink')
++  mkdirp.sync(dir)
++
++  const makeRelSymTar = () => {
++    const chunks = []
++
++    const fooDirHeader = Buffer.alloc(512)
++    new Header({
++      path: 'foo/',
++      type: 'Directory',
++      mode: 0o755,
++    }).encode(fooDirHeader, 0)
++    chunks.push(fooDirHeader)
++
++    const fileContent = 'hello world'
++    const fileHeader = Buffer.alloc(512)
++    new Header({
++      path: 'foo/file.txt',
++      type: 'File',
++      mode: 0o644,
++      size: fileContent.length,
++    }).encode(fileHeader, 0)
++    chunks.push(fileHeader)
++
++    const contentBuf = Buffer.alloc(512)
++    contentBuf.write(fileContent)
++    chunks.push(contentBuf)
++
++    const barDirHeader = Buffer.alloc(512)
++    new Header({
++      path: 'bar/',
++      type: 'Directory',
++      mode: 0o755,
++    }).encode(barDirHeader, 0)
++    chunks.push(barDirHeader)
++
++    const symlinkHeader = Buffer.alloc(512)
++    new Header({
++      path: 'bar/link.txt',
++      type: 'SymbolicLink',
++      linkpath: '../foo/file.txt',
++    }).encode(symlinkHeader, 0)
++    chunks.push(symlinkHeader)
++
++    const escapedSymlinkHeader = Buffer.alloc(512)
++    new Header({
++      path: 'bar/badlink.txt',
++      type: 'SymbolicLink',
++      linkpath: '../../oh/no',
++    }).encode(escapedSymlinkHeader, 0)
++    chunks.push(escapedSymlinkHeader)
++
++    chunks.push(Buffer.alloc(1024))
++    return Buffer.concat(chunks)
++  }
++
++  const tarFile = path.resolve(dir, 'test.tar')
++  fs.writeFileSync(tarFile, makeRelSymTar())
++
++  const warnings = []
++
++  const u = new Unpack({
++    cwd: dir,
++    onwarn: (c, m) => warnings.push([c, m]),
++  })
++  u.on('end', () => {
++    t.ok(fs.lstatSync(dir + '/foo/file.txt').isFile())
++    t.ok(fs.lstatSync(dir + '/bar/link.txt').isSymbolicLink())
++    t.equal(fs.readlinkSync(dir + '/bar/link.txt'), '../foo/file.txt')
++    t.equal(fs.readFileSync(dir + '/bar/link.txt', 'utf8'), 'hello world')
++    t.throws(() => fs.lstatSync(dir + '/bar/badlink.txt'))
++    t.match(warnings, [['TAR_ENTRY_ERROR', 'linkpath escapes extraction directory']])
++    t.end()
++  })
++  u.end(fs.readFileSync(tarFile))
++})
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-24842.patch node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-24842.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-24842.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-24842.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,78 @@
+From: isaacs <i at izs.me>
+Date: Tue, 27 Jan 2026 12:47:48 -0800
+Subject: [PATCH] fix: properly sanitize hard links containing ..
+
+Fix: https://github.com/isaacs/node-tar/security/advisories/GHSA-34x7-hfp2-rc4v
+
+The issue here is that *hard* links are resolved relative to the unpack
+cwd, so if they have `..`, they cannot possibly be valid, same as files
+and for the same reason. The loosening of this restriction for symbolic
+links should have been limited by type, allowing this error.
+
+Reviewed-By: Daniel Leidert <dleidert at debian.org>
+Origin: https://github.com/isaacs/node-tar/commit/f4a7aa9bc3d717c987fdf1480ff7a64e87ffdb46
+Bug: https://github.com/isaacs/node-tar/security/advisories/GHSA-34x7-hfp2-rc4v
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-24842
+Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-24842
+---
+ lib/unpack.js               |  3 ++-
+ test/ghsa-8qq5-rm4j-mr97.js | 18 ++++++++++++++++++
+ 2 files changed, 20 insertions(+), 1 deletion(-)
+
+diff --git a/lib/unpack.js b/lib/unpack.js
+index baf8f1b..d3f8f0e 100644
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -249,6 +249,7 @@ class Unpack extends Parser {
+   // return true if the field was successfully sanitized
+   [STRIPABSOLUTEPATH](entry, field) {
+     const p = entry[field]
++    const { type } = entry
+     if (!p || this.preservePaths) return true
+ 
+     const parts = p.split('/')
+@@ -262,7 +263,7 @@ class Unpack extends Parser {
+       // just rejecting any path with '..' - relative symlinks like
+       // '../sibling/file' are valid if they resolve within the cwd.
+       // For paths, they just simply may not ever use .. at all.
+-      if (field === 'path') {
++      if (field === 'path' || type === 'Link') {
+         this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, {
+           entry,
+           [field]: p,
+diff --git a/test/ghsa-8qq5-rm4j-mr97.js b/test/ghsa-8qq5-rm4j-mr97.js
+index 95b0e03..9b96a40 100644
+--- a/test/ghsa-8qq5-rm4j-mr97.js
++++ b/test/ghsa-8qq5-rm4j-mr97.js
+@@ -28,6 +28,20 @@ const getExploitTar = () => {
+   }).encode(hardHeader, 0)
+   chunks.push(hardHeader)
+ 
++  const hardSubHeader = Buffer.alloc(1024)
++  new Header({
++    path: 'sub/',
++    type: 'Directory',
++    size: 0,
++  }).encode(hardSubHeader, 0)
++  new Header({
++    path: 'sub/exploit_sub',
++    type: 'Link',
++    size: 0,
++    linkpath: '../secret.txt',
++  }).encode(hardSubHeader, 512)
++  chunks.push(hardSubHeader)
++
+   const symHeader = Buffer.alloc(512)
+   new Header({
+     path: 'exploit_sym',
+@@ -74,6 +88,10 @@ t.test('hardlink escape does not clobber target', t => {
+   x({ cwd: out, file: tarFile }).then(() => {
+     writeFileSync(resolve(out, 'exploit_hard'), 'OVERWRITTEN')
+     t.equal(readFileSync(secretFile, 'utf8'), 'ORIGINAL DATA')
++
++    writeFileSync(resolve(out, 'sub/exploit_sub'), 'OVERWRITTEN SUB')
++    t.equal(readFileSync(secretFile, 'utf8'), 'ORIGINAL DATA')
++
+     t.end()
+   })
+ })
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-1.patch node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-1.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-1.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-1.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,269 @@
+From: isaacs <i at izs.me>
+Date: Thu, 12 Feb 2026 20:50:19 -0800
+Subject: [PATCH] fix: do not write linkpaths through symlinks
+
+Prevent any `Link` or `SymbolicLink` entry from being created if its
+`linkpath` would target a location that is through a symbolic link from
+the current working directory.
+
+This matches the behavior of `bsdtar` for hard links, and is somewhat
+more restrictive in applying the same logic to symbolic links as well.
+
+Unpacking links with targets that extend through symlink folders is
+allowed if `preservePaths` option is enabled, as this disables all
+protective link checking by design, and is only designed for use with
+trusted input.
+
+Fix: https://github.com/isaacs/node-tar/security/advisories/GHSA-83g3-92jg-28cx
+
+Reviewed-By: Daniel Leidert <dleidert at debian.org>
+Origin: https://github.com/isaacs/node-tar/commit/d18e4e1f846f4ddddc153b0f536a19c050e7499f
+Bug: https://github.com/isaacs/node-tar/security/advisories/GHSA-83g3-92jg-28cx
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-26960
+Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-26960
+---
+ lib/mkdir.js          |  2 ++
+ lib/process-umask.js  |  1 +
+ lib/unpack.js         | 77 ++++++++++++++++++++++++++++++++++++++++++++++++---
+ test/process-umask.js |  6 ++++
+ test/unpack.js        | 71 +++++++++++++++++++++++++++++++++++++++++++++++
+ 5 files changed, 153 insertions(+), 4 deletions(-)
+ create mode 100644 lib/process-umask.js
+ create mode 100644 test/process-umask.js
+
+diff --git a/lib/mkdir.js b/lib/mkdir.js
+index 8ee8de7..6e51f31 100644
+--- a/lib/mkdir.js
++++ b/lib/mkdir.js
+@@ -227,3 +227,5 @@ module.exports.sync = (dir, opt) => {
+ 
+   return done(created)
+ }
++
++module.exports.SymlinkError = SymlinkError
+diff --git a/lib/process-umask.js b/lib/process-umask.js
+new file mode 100644
+index 0000000..0cddb41
+--- /dev/null
++++ b/lib/process-umask.js
+@@ -0,0 +1 @@
++module.exports = { umask: () => process.umask() }
+diff --git a/lib/unpack.js b/lib/unpack.js
+index d3f8f0e..aa36bfa 100644
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -18,6 +18,8 @@ const stripAbsolutePath = require('./strip-absolute-path.js')
+ const normPath = require('./normalize-windows-path.js')
+ const stripSlash = require('./strip-trailing-slashes.js')
+ const normalize = require('./normalize-unicode.js')
++const { SymlinkError } = require('./mkdir.js')
++const { umask } = require('./process-umask.js');
+ 
+ const ONENTRY = Symbol('onEntry')
+ const CHECKFS = Symbol('checkFs')
+@@ -30,6 +32,7 @@ const DIRECTORY = Symbol('directory')
+ const LINK = Symbol('link')
+ const SYMLINK = Symbol('symlink')
+ const HARDLINK = Symbol('hardlink')
++const ENSURE_NO_SYMLINK = Symbol('ensureNoSymlink')
+ const UNSUPPORTED = Symbol('unsupported')
+ const CHECKPATH = Symbol('checkPath')
+ const STRIPABSOLUTEPATH = Symbol('stripAbsolutePath')
+@@ -217,7 +220,7 @@ class Unpack extends Parser {
+     this.cwd = normPath(path.resolve(opt.cwd || process.cwd()))
+     this.strip = +opt.strip || 0
+     // if we're not chmodding, then we don't need the process umask
+-    this.processUmask = opt.noChmod ? 0 : process.umask()
++    this.processUmask = opt.noChmod ? 0 : umask()
+     this.umask = typeof opt.umask === 'number' ? opt.umask : this.processUmask
+ 
+     // default mode for dirs created as parents
+@@ -310,6 +313,7 @@ class Unpack extends Parser {
+     return true
+   }
+ 
++  // no IO, just string checking for absolute indicators
+   [CHECKPATH] (entry) {
+     const p = normPath(entry.path)
+     const parts = p.split('/')
+@@ -591,12 +595,58 @@ class Unpack extends Parser {
+   }
+ 
+   [SYMLINK] (entry, done) {
+-    this[LINK](entry, entry.linkpath, 'symlink', done)
++    const parts = normPath(
++      path.relative(
++        this.cwd,
++        path.resolve(
++          path.dirname(String(entry.absolute)),
++          String(entry.linkpath),
++        ),
++      ),
++    ).split('/')
++    this[ENSURE_NO_SYMLINK](
++      entry,
++      this.cwd,
++      parts,
++      () =>
++        this[LINK](entry, String(entry.linkpath), 'symlink', done),
++      er => {
++        this[ONERROR](er, entry)
++        done()
++      },
++    )
+   }
+ 
+   [HARDLINK] (entry, done) {
+     const linkpath = normPath(path.resolve(this.cwd, entry.linkpath))
+-    this[LINK](entry, linkpath, 'link', done)
++    const parts = normPath(String(entry.linkpath)).split(
++      '/',
++    )
++    this[ENSURE_NO_SYMLINK](
++      entry,
++      this.cwd,
++      parts,
++      () => this[LINK](entry, linkpath, 'link', done),
++      er => {
++        this[ONERROR](er, entry)
++        done()
++      },
++    )
++  }
++
++  [ENSURE_NO_SYMLINK] (entry, cwd, parts, done, onError) {
++    const p = parts.shift()
++    if (this.preservePaths || p === undefined) return done()
++    const t = path.resolve(cwd, p)
++    fs.lstat(t, (er, st) => {
++      if (er) return done()
++      if (st.isSymbolicLink()) {
++        return onError(
++          new SymlinkError(t, path.resolve(t, parts.join('/'))),
++        )
++      }
++      this[ENSURE_NO_SYMLINK](entry, t, parts, done, onError)
++    })
+   }
+ 
+   [PEND] () {
+@@ -767,8 +817,27 @@ class Unpack extends Parser {
+     }
+   }
+ 
++  [ENSURE_NO_SYMLINK] (_entry, cwd, parts, done, onError) {
++    if (this.preservePaths || !parts.length) return done()
++    let t = cwd
++    for (const p of parts) {
++      t = path.resolve(t, p)
++      let st
++      try {
++        st = fs.lstatSync(t)
++      } catch (er) {
++        return done()
++      }
++      if (st.isSymbolicLink()) {
++        return onError(
++          new SymlinkError(t, path.resolve(t, parts.join('/'))),
++        )
++      }
++    }
++    done()
++  }
++
+   [LINK] (entry, linkpath, link, done) {
+-    // XXX: get the type ('symlink' or 'junction') for windows
+     fs[link](linkpath, entry.absolute, er => {
+       if (er) {
+         this[ONERROR](er, entry)
+diff --git a/test/process-umask.js b/test/process-umask.js
+new file mode 100644
+index 0000000..b7d0745
+--- /dev/null
++++ b/test/process-umask.js
+@@ -0,0 +1,6 @@
++'use strict';
++
++const t = require('tap')
++const { umask } = require('../lib/process-umask.js');
++
++t.equal(umask(), process.umask())
+diff --git a/test/unpack.js b/test/unpack.js
+index 06e9140..2337377 100644
+--- a/test/unpack.js
++++ b/test/unpack.js
+@@ -3372,3 +3372,74 @@ t.test('valid relative symlink with .. should be extracted', t => {
+   })
+   u.end(fs.readFileSync(tarFile))
+ })
++
++t.test('no linking through a symlink', t => {
++  const types = ['Link', 'SymbolicLink']
++  for (const type of types) {
++    t.test(type, t => {
++      const exploit = makeTar([
++        {
++          type: 'SymbolicLink',
++          path: 'a/b/up',
++          linkpath: '../..',
++          mode: 0o755,
++        },
++        {
++          type: 'SymbolicLink',
++          path: 'a/b/escape',
++          linkpath: 'up/..',
++          mode: 0o755,
++        },
++        {
++          type,
++          path: 'exploit',
++          linkpath: 'a/b/escape/exploited-file',
++          mode: 0o755,
++        },
++        '',
++        '',
++      ])
++
++      const dir = path.resolve(unpackdir, 'symlink-linking', type)
++      const exploitedFile = path.resolve(dir, 'exploited-file')
++      const cwd = path.resolve(dir, 'x')
++
++      const setup = () =>  {
++        mkdirp.sync(cwd)
++        fs.writeFileSync(exploitedFile, 'original content')
++      }
++
++      const cleanup = () => rimraf.sync(dir)
++
++      const check = t => {
++        fs.writeFileSync(path.resolve(cwd, 'exploit'), 'pwned')
++        t.equal(fs.readFileSync(exploitedFile, 'utf8'), 'original content')
++      }
++
++      t.test('sync', t => {
++        setup()
++        t.teardown(cleanup)
++        t.throws(() => {
++          new UnpackSync({ cwd, strict: true }).end(exploit)
++        })
++        check(t)
++        t.end()
++      })
++
++      t.test('async', async t => {
++        setup()
++        t.teardown(cleanup)
++        await t.rejects(new Promise((res, rej) => {
++          new Unpack({ cwd, strict: true })
++            .on('finish', res)
++            .on('error', rej)
++            .end(exploit)
++        }))
++        check(t)
++      })
++
++      t.end()
++    })
++  }
++  t.end()
++})
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-2.patch node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-2.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-2.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-26960-2.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,65 @@
+From: Guillermo de Angel <scumfrog at users.noreply.github.com>
+Date: Fri, 13 Feb 2026 08:32:20 +0100
+Subject: [PATCH] fix(unpack): improve UnpackSync symlink error "into" path
+ accuracy
+
+UnpackSync[ENSURE_NO_SYMLINK] previously constructed SymlinkError's
+"into" path using the full original linkpath parts array, which could
+produce misleading diagnostics.
+
+Build the "into" path from the original `cwd` value and the `parts`
+list.
+
+Reviewed-By: Daniel Leidert <dleidert at debian.org>
+Origin: https://github.com/isaacs/node-tar/commit/2cb1120bcefe28d7ecc719b41441ade59c52e384
+Bug: https://github.com/isaacs/node-tar/security/advisories/GHSA-83g3-92jg-28cx
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-26960
+Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-26960
+---
+ lib/unpack.js  |  2 +-
+ test/unpack.js | 12 ++++++++++++
+ 2 files changed, 13 insertions(+), 1 deletion(-)
+
+diff --git a/lib/unpack.js b/lib/unpack.js
+index aa36bfa..5e8b970 100644
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -642,7 +642,7 @@ class Unpack extends Parser {
+       if (er) return done()
+       if (st.isSymbolicLink()) {
+         return onError(
+-          new SymlinkError(t, path.resolve(t, parts.join('/'))),
++          new SymlinkError(t, path.resolve(cwd, parts.join('/'))),
+         )
+       }
+       this[ENSURE_NO_SYMLINK](entry, t, parts, done, onError)
+diff --git a/test/unpack.js b/test/unpack.js
+index 2337377..5c6d3a2 100644
+--- a/test/unpack.js
++++ b/test/unpack.js
+@@ -3421,6 +3421,12 @@ t.test('no linking through a symlink', t => {
+         t.teardown(cleanup)
+         t.throws(() => {
+           new UnpackSync({ cwd, strict: true }).end(exploit)
++        },
++        {
++          name: 'SylinkError',
++          message: /^Cannot extract through symbolic link/,
++          path: /a.b.escape.exploited-file$/,
++          symlink: /a.b.escape$/,
+         })
+         check(t)
+         t.end()
+@@ -3434,6 +3440,12 @@ t.test('no linking through a symlink', t => {
+             .on('finish', res)
+             .on('error', rej)
+             .end(exploit)
++        },
++        {
++          name: 'SylinkError',
++          message: /^Cannot extract through symbolic link/,
++          path: /a.b.escape.exploited-file$/,
++          symlink: /a.b.escape$/,
+         }))
+         check(t)
+       })
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-29786.patch node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-29786.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-29786.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-29786.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,77 @@
+From: isaacs <i at izs.me>
+Date: Wed, 4 Mar 2026 11:41:10 -0800
+Subject: [PATCH] parse root off paths before sanitizing .. parts
+
+Fix: https://github.com/isaacs/node-tar/security/advisories/GHSA-qffp-2rhf-9h96
+
+Reviewed-By: Daniel Leidert <dleidert at debian.org>
+Origin: https://github.com/isaacs/node-tar/commit/7bc755dd85e623c0279e08eb3784909e6d7e4b9f
+Bug: https://github.com/isaacs/node-tar/security/advisories/GHSA-qffp-2rhf-9h96
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-29786
+Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-29786
+---
+ lib/unpack.js               |  6 +++---
+ test/ghsa-8qq5-rm4j-mr97.js | 15 ++++++++++++++-
+ 2 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/lib/unpack.js b/lib/unpack.js
+index 5e8b970..f135b9c 100644
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -255,7 +255,9 @@ class Unpack extends Parser {
+     const { type } = entry
+     if (!p || this.preservePaths) return true
+ 
+-    const parts = p.split('/')
++    // strip off the root
++    const [root, stripped] = stripAbsolutePath(p)
++    const parts = stripped.replace(/\\/g, '/').split('/')
+ 
+     if (
+       parts.includes('..') ||
+@@ -296,8 +298,6 @@ class Unpack extends Parser {
+       }
+     }
+ 
+-    // strip off the root
+-    const [root, stripped] = stripAbsolutePath(p)
+     if (root) {
+       // ok, but triggers warning about stripping root
+       entry[field] = String(stripped)
+diff --git a/test/ghsa-8qq5-rm4j-mr97.js b/test/ghsa-8qq5-rm4j-mr97.js
+index 9b96a40..0c77f9d 100644
+--- a/test/ghsa-8qq5-rm4j-mr97.js
++++ b/test/ghsa-8qq5-rm4j-mr97.js
+@@ -75,11 +75,19 @@ const getExploitTar = () => {
+   }).encode(absWithDotDotHeader, 0)
+   chunks.push(absWithDotDotHeader)
+ 
++  const winAbsWithDotDotHeader = Buffer.alloc(512)
++  new Header({
++    path: 'a/winrootdotslink',
++    type: 'SymbolicLink',
++    linkpath: 'c:..\\foo\\bar',
++  }).encode(winAbsWithDotDotHeader, 0)
++  chunks.push(winAbsWithDotDotHeader)
++
+   chunks.push(Buffer.alloc(1024))
+   return Buffer.concat(chunks)
+ }
+ 
+-t.test('hardlink escape does not clobber target', t => {
++t.test('writefile exploits fail', t => {
+   mkdirp.sync(out)
+   writeFileSync(secretFile, 'ORIGINAL DATA')
+   writeFileSync(tarFile, getExploitTar())
+@@ -122,6 +130,11 @@ t.test('absolute symlink with .. has prefix stripped', t => {
+       '../a/target',
+       'symlink target should be normalized',
+     )
++    t.equal(
++      readlinkSync(resolve(out, 'a/winrootdotslink')),
++      '..\\foo\\bar',
++      'symlink target should be normalized',
++    )
+     t.end()
+   })
+ })
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-31802.patch node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-31802.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-31802.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/CVE-2026-31802.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,59 @@
+From: isaacs <i at izs.me>
+Date: Sun, 8 Mar 2026 22:52:09 -0700
+Subject: [PATCH] prevent escaping symlinks with drive-relative paths
+
+Fix: https://github.com/isaacs/node-tar/security/advisories/GHSA-9ppj-qmqm-q256
+
+Reviewed-By: Daniel Leidert <dleidert at debian.org>
+Origin: https://github.com/isaacs/node-tar/commit/f48b5fa3b7985ddab96dc0f2125a4ffc9911b6ad
+Bug: https://github.com/isaacs/node-tar/security/advisories/GHSA-9ppj-qmqm-q256
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-31802
+Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-31802
+---
+ lib/unpack.js               |  2 +-
+ test/ghsa-8qq5-rm4j-mr97.js | 12 ++++++++++++
+ 2 files changed, 13 insertions(+), 1 deletion(-)
+
+diff --git a/lib/unpack.js b/lib/unpack.js
+index f135b9c..a76e02f 100644
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -281,7 +281,7 @@ class Unpack extends Parser {
+         // tar paths, not a filesystem.
+         const entryDir = path.posix.dirname(entry.path)
+         const resolved = path.posix.normalize(
+-          path.posix.join(entryDir, p),
++          path.posix.join(entryDir, parts.join('/'))
+         )
+         // If the resolved path escapes (starts with ..), reject it
+         if (resolved.startsWith('../') || resolved === '..') {
+diff --git a/test/ghsa-8qq5-rm4j-mr97.js b/test/ghsa-8qq5-rm4j-mr97.js
+index 0c77f9d..4aff87d 100644
+--- a/test/ghsa-8qq5-rm4j-mr97.js
++++ b/test/ghsa-8qq5-rm4j-mr97.js
+@@ -83,6 +83,14 @@ const getExploitTar = () => {
+   }).encode(winAbsWithDotDotHeader, 0)
+   chunks.push(winAbsWithDotDotHeader)
+ 
++  const winAbsWithDotDotEscapeHeader = Buffer.alloc(512)
++  new Header({
++    path: 'a/winrootdotsescapelink',
++    type: 'SymbolicLink',
++    linkpath: 'c:..\\..\\..\\..\\foo\\bar',
++  }).encode(winAbsWithDotDotEscapeHeader, 0)
++  chunks.push(winAbsWithDotDotEscapeHeader)
++
+   chunks.push(Buffer.alloc(1024))
+   return Buffer.concat(chunks)
+ }
+@@ -135,6 +143,10 @@ t.test('absolute symlink with .. has prefix stripped', t => {
+       '..\\foo\\bar',
+       'symlink target should be normalized',
+     )
++    t.throws(
++      () => lstatSync(resolve(out, 'a/winrootdotsescapelink')),
++      'escaping symlink is not created',
++    )
+     t.end()
+   })
+ })
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/fix-tests.patch node-tar-6.1.13+~cs7.0.5/debian/patches/fix-tests.patch
--- node-tar-6.1.13+~cs7.0.5/debian/patches/fix-tests.patch	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/fix-tests.patch	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1,20 @@
+From: isaacs <i at izs.me>
+Date: Fri, 12 May 2023 14:43:44 -0700
+Subject: remove parallelism causing test/pack.js to be flaky
+
+---
+ test/pack.js | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/test/pack.js b/test/pack.js
+index 8906ae3..bae126f 100644
+--- a/test/pack.js
++++ b/test/pack.js
+@@ -1135,6 +1135,7 @@ t.test('prefix and hard links', t => {
+       cwd: dir + '/in',
+       prefix: 'out/x',
+       noDirRecurse: true,
++      jobs: 1,
+     })
+     const out = []
+     p.on('data', d => out.push(d))
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/patches/series node-tar-6.1.13+~cs7.0.5/debian/patches/series
--- node-tar-6.1.13+~cs7.0.5/debian/patches/series	2022-11-10 14:49:38.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/patches/series	2026-04-30 04:21:06.000000000 +0200
@@ -1 +1,10 @@
 api-backward-compatibility.patch
+CVE-2024-28863.patch
+fix-tests.patch
+CVE-2026-23745.patch
+CVE-2026-23745-regression-fix.patch
+CVE-2026-24842.patch
+CVE-2026-26960-1.patch
+CVE-2026-26960-2.patch
+CVE-2026-29786.patch
+CVE-2026-31802.patch
diff -Nru node-tar-6.1.13+~cs7.0.5/debian/source/include-binaries node-tar-6.1.13+~cs7.0.5/debian/source/include-binaries
--- node-tar-6.1.13+~cs7.0.5/debian/source/include-binaries	1970-01-01 01:00:00.000000000 +0100
+++ node-tar-6.1.13+~cs7.0.5/debian/source/include-binaries	2026-04-30 04:21:06.000000000 +0200
@@ -0,0 +1 @@
+debian/patches/CVE-2024-28863.patch


More information about the Pkg-javascript-devel mailing list