Medium severityOSV Advisory· Published Oct 30, 2025· Updated Apr 15, 2026
CVE-2025-64118
CVE-2025-64118
Description
node-tar is a Tar for Node.js. In 7.5.1, using .t (aka .list) with { sync: true } to read tar entry contents returns uninitialized memory contents if tar file was changed on disk to a smaller size while being read. This vulnerability is fixed in 7.5.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tarnpm | >= 7.5.1, < 7.5.2 | 7.5.2 |
Affected products
1Patches
25e1a8e638600Fix sync tar.list when file size reduces while reading
2 files changed · +96 −3
src/list.ts+3 −2 modified@@ -64,13 +64,14 @@ const listFileSync = (opt: TarOptionsSyncFile) => { const readSize: number = opt.maxReadSize || 16 * 1024 * 1024 if (stat.size < readSize) { const buf = Buffer.allocUnsafe(stat.size) - fs.readSync(fd, buf, 0, stat.size, 0) - p.end(buf) + const read = fs.readSync(fd, buf, 0, stat.size, 0) + p.end(read === buf.byteLength ? buf : buf.subarray(0, read)) } else { let pos = 0 const buf = Buffer.allocUnsafe(readSize) while (pos < stat.size) { const bytesRead = fs.readSync(fd, buf, 0, readSize, pos) + if (bytesRead === 0) break pos += bytesRead p.write(buf.subarray(0, bytesRead)) }
test/list.ts+93 −1 modified@@ -1,4 +1,4 @@ -import fs, { readFileSync } from 'fs' +import fs, { readFileSync, Stats } from 'fs' //@ts-ignore import mutateFS from 'mutate-fs' import { dirname, resolve } from 'path' @@ -7,6 +7,7 @@ import { fileURLToPath } from 'url' import { list } from '../dist/esm/list.js' import { Parser } from '../dist/esm/parse.js' import { ReadEntry } from '../dist/esm/read-entry.js' +import { makeTar } from './fixtures/make-tar.js' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -276,3 +277,94 @@ t.test('typechecks', t => { t.type(p, Parser) t.end() }) + +// GHSA-29xp-372q-xqph +t.test('reduce file size while synchronously reading', async t => { + const data = makeTar([ + { + type: 'File', + path: 'a', + size: 1, + }, + 'a', + { + type: 'File', + path: 'b', + size: 1, + }, + 'b', + '', + '', + ]) + const dataLen = data.byteLength + const truncLen = 512 * 2 + const truncData = data.subarray(0, truncLen) + + const setup = async (t: Test) => { + const dir = t.testdir({ 'file.tar': data }) + const file = resolve(dir, 'file.tar') + const { list } = await t.mockImport< + typeof import('../src/list.js') + >('../src/list.js', { + 'node:fs': t.createMock(fs, { + fstatSync: (fd: number): Stats => { + const st = fs.fstatSync(fd) + // truncate the file before we have a chance to read + fs.writeFileSync(file, truncData) + return st + }, + }), + }) + + return { file, list } + } + + t.test( + 'gutcheck, reading normally reads the whole file', + async t => { + const dir = t.testdir({ 'file.tar': data }) + const file = resolve(dir, 'file.tar') + const entries: string[] = [] + list({ + file, + sync: true, + maxReadSize: dataLen + 1, + onReadEntry: e => entries.push(e.path), + }) + t.strictSame(entries, ['a', 'b']) + + entries.length = 0 + list({ + file, + sync: true, + maxReadSize: dataLen - 1, + onReadEntry: e => entries.push(e.path), + }) + t.strictSame(entries, ['a', 'b']) + }, + ) + + t.test('read in one go', async t => { + const { file, list } = await setup(t) + const entries: string[] = [] + list({ + file, + sync: true, + maxReadSize: dataLen + 1, + onReadEntry: e => entries.push(e.path), + }) + t.strictSame(entries, ['a']) + }) + + t.test('read in parts', async t => { + const { file, list } = await setup(t) + const entries: string[] = [] + list({ + file, + sync: true, + maxReadSize: dataLen / 4, + onReadEntry: e => entries.push(e.path), + }) + t.strictSame(entries, ['a']) + }) +})
5330eb04bc43fix: consistent TOCTOU behavior in sync t.list
1 file changed · +7 −5
src/list.ts+7 −5 modified@@ -57,16 +57,18 @@ export const filesFilter = (opt: TarOptions, files: string[]) => { const listFileSync = (opt: TarOptionsSyncFile) => { const p = new Parser(opt) const file = opt.file - let fd + let fd: number | undefined try { - const stat = fs.statSync(file) - const readSize = opt.maxReadSize || 16 * 1024 * 1024 + fd = fs.openSync(file, 'r') + const stat: fs.Stats = fs.fstatSync(fd) + const readSize: number = opt.maxReadSize || 16 * 1024 * 1024 if (stat.size < readSize) { - p.end(fs.readFileSync(file)) + const buf = Buffer.allocUnsafe(stat.size) + fs.readSync(fd, buf, 0, stat.size, 0) + p.end(buf) } else { let pos = 0 const buf = Buffer.allocUnsafe(readSize) - fd = fs.openSync(file, 'r') while (pos < stat.size) { const bytesRead = fs.readSync(fd, buf, 0, readSize, pos) pos += bytesRead
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-29xp-372q-xqphghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-64118ghsaADVISORY
- github.com/isaacs/node-tar/commit/5330eb04bc43014f216e5c271b40d5c00d45224dnvdWEB
- github.com/isaacs/node-tar/commit/5e1a8e638600d3c3a2969b4de6a6ec44fa8d74c9ghsaWEB
- github.com/isaacs/node-tar/issues/445nvdWEB
- github.com/isaacs/node-tar/pull/446nvdWEB
- github.com/isaacs/node-tar/security/advisories/GHSA-29xp-372q-xqphnvdWEB
News mentions
0No linked articles in our index yet.