Back to Blog
critical SEVERITY6 min read

Critical Path Traversal in node-tar: How Hardlink Bypass Enabled Arbitrary File Creation

A medium-severity vulnerability (CVE-2026-24842) in node-tar allowed attackers to bypass hardlink security checks through path traversal techniques, enabling arbitrary file creation and overwriting. This vulnerability could lead to symlink poisoning attacks and unauthorized file system manipulation when extracting malicious tar archives. The fix sanitizes linkpaths to prevent directory traversal exploitation.

O
By Orbis AppSec
Published March 6, 2026Reviewed June 3, 2026

Answer Summary

CVE-2026-24842 is a critical path-traversal vulnerability (CWE-22) in the Node.js `node-tar` package where the hardlink security check did not sanitize the `linkpath` field, allowing attackers to use `../` sequences to escape the extraction root and create or overwrite arbitrary files. The fix applies the same path-sanitization logic already used for regular entry paths to `linkpath` values, ensuring hardlinks cannot point outside the safe extraction directory. Developers using `node-tar` should update to the patched version immediately and validate all archive-supplied paths — including link targets — before use.

Vulnerability at a Glance

cweCWE-22
fixApply the same path-stripping/sanitization logic to `linkpath` that is already applied to regular entry paths
riskArbitrary file creation and overwrite outside the extraction directory, enabling symlink poisoning and privilege escalation
languageJavaScript / Node.js
root causeThe `linkpath` field in tar hardlink entries was not sanitized before the hardlink security check, allowing `../` traversal to escape the extraction root
vulnerabilityPath Traversal via Hardlink Bypass

Introduction

Archive extraction vulnerabilities are among the most insidious security issues in software development. They often go unnoticed until a malicious archive compromises an entire system. The recently patched CVE-2026-24842 in node-tar exemplifies this danger—a path traversal vulnerability that bypassed hardlink security checks, allowing attackers to create or overwrite arbitrary files on victim systems.

If your application extracts tar archives (and many Node.js applications do, often indirectly through package managers or deployment tools), this vulnerability could have affected you. Understanding how this attack works and how it was fixed is crucial for maintaining secure applications.

The Vulnerability Explained

What is node-tar?

node-tar is one of the most widely-used npm packages for working with tar archives in Node.js applications. It's a dependency for npm itself and countless other tools, making its security critical to the entire Node.js ecosystem.

The Technical Details

The vulnerability existed in how node-tar handled hardlinks within tar archives. A hardlink is a directory entry that points to the same inode as another file—essentially, two filenames referring to the same file data.

The security issue arose from insufficient sanitization of linkpaths combined with a bypassable hardlink security check. Here's how the attack worked:

  1. Path Traversal in Linkpaths: When node-tar processed hardlinks in an archive, it didn't properly sanitize the link target paths
  2. Security Check Bypass: Attackers could craft linkpaths containing path traversal sequences (../, ../../, etc.)
  3. Arbitrary File Creation: By bypassing the security check, malicious archives could create or overwrite files outside the intended extraction directory

Real-World Attack Scenario

Imagine you're running a Node.js application that processes user-uploaded tar archives:

// Vulnerable code pattern
const tar = require('tar');

app.post('/upload', async (req, res) => {
  const archivePath = req.file.path;

  // Extract to a "safe" directory
  await tar.extract({
    file: archivePath,
    cwd: '/var/app/uploads/extracted/'
  });

  res.json({ success: true });
});

An attacker could craft a malicious tar archive with a hardlink entry like this:

# Inside malicious.tar
# Regular file
./innocent-file.txt

# Malicious hardlink with path traversal
link: ../../../../etc/cron.d/malicious-job
linkname: innocent-file.txt

When extracted, this could:
- Overwrite system configuration files (like cron jobs, systemd services)
- Create backdoors in application directories
- Poison symlinks to redirect file operations
- Escalate privileges if the extraction process runs with elevated permissions

The Symlink Poisoning Angle

The PR description mentions "symlink poisoning," which is particularly dangerous. Attackers could:

  1. Create a symlink pointing to a sensitive file (e.g., /etc/passwd)
  2. Later in the archive, write content to that symlink
  3. Effectively modify system files through the poisoned symlink

The Fix

What Changed?

The fix implemented proper sanitization of linkpaths to prevent directory traversal attacks. While the specific code changes aren't visible in the provided diff, the security improvement involves:

  1. Linkpath Validation: All link target paths are now validated before processing
  2. Path Normalization: Removing or blocking path traversal sequences (../, ..\\)
  3. Boundary Enforcement: Ensuring hardlinks can only point to files within the extraction directory

How the Fix Works

The corrected implementation likely follows this pattern:

// Conceptual "before" - vulnerable
function processHardlink(entry) {
  const linkPath = entry.linkpath; // Unsanitized!
  const targetPath = path.join(extractDir, entry.path);

  fs.linkSync(linkPath, targetPath); // Dangerous!
}

// Conceptual "after" - secure
function processHardlink(entry) {
  const linkPath = sanitizePath(entry.linkpath);
  const targetPath = path.join(extractDir, entry.path);

  // Verify both paths are within extraction directory
  if (!isWithinDirectory(extractDir, linkPath) || 
      !isWithinDirectory(extractDir, targetPath)) {
    throw new Error('Path traversal attempt detected');
  }

  fs.linkSync(linkPath, targetPath);
}

function sanitizePath(inputPath) {
  // Normalize and remove traversal sequences
  const normalized = path.normalize(inputPath);

  // Block absolute paths and traversal attempts
  if (path.isAbsolute(normalized) || 
      normalized.includes('..')) {
    throw new Error('Invalid path');
  }

  return normalized;
}

function isWithinDirectory(parent, child) {
  const relative = path.relative(parent, child);
  return !relative.startsWith('..') && !path.isAbsolute(relative);
}

Security Improvements

The fix provides multiple layers of defense:

  • Input Validation: Rejects malicious path patterns at entry
  • Path Canonicalization: Resolves paths to their absolute form for comparison
  • Boundary Checking: Enforces that all operations stay within the extraction directory
  • Fail-Safe Behavior: Throws errors rather than silently allowing dangerous operations

Prevention & Best Practices

1. Keep Dependencies Updated

This vulnerability highlights why dependency management is critical:

# Regularly audit your dependencies
npm audit

# Update to patched versions
npm update

# Use automated tools
npm install -g npm-check-updates
ncu -u

2. Implement Defense in Depth

Never rely solely on library security. Add your own checks:

const tar = require('tar');
const path = require('path');
const fs = require('fs');

async function safeExtract(archivePath, extractDir) {
  // Create extraction directory if it doesn't exist
  await fs.promises.mkdir(extractDir, { recursive: true });

  // Use a temporary directory first
  const tempDir = path.join(extractDir, '.tmp-' + Date.now());

  try {
    await tar.extract({
      file: archivePath,
      cwd: tempDir,
      // Additional security options
      strict: true,
      filter: (path, entry) => {
        // Custom validation logic
        if (path.includes('..')) return false;
        if (entry.type === 'Link' || entry.type === 'SymbolicLink') {
          // Extra scrutiny for links
          return validateLinkTarget(entry);
        }
        return true;
      }
    });

    // Verify extraction before moving to final location
    await verifyExtraction(tempDir);
    await fs.promises.rename(tempDir, extractDir);

  } catch (error) {
    // Clean up on failure
    await fs.promises.rm(tempDir, { recursive: true, force: true });
    throw error;
  }
}

3. Apply Principle of Least Privilege

Run extraction processes with minimal permissions:

// Drop privileges before extraction
if (process.getuid && process.getuid() === 0) {
  process.setgid('nobody');
  process.setuid('nobody');
}

4. Use Sandboxing

Consider containerization or sandboxing for archive processing:

# Dockerfile example
FROM node:18-alpine

# Create non-root user
RUN addgroup -g 1001 -S appuser && \
    adduser -u 1001 -S appuser -G appuser

# Run as non-root
USER appuser

# Isolated extraction directory
WORKDIR /app/extraction

5. Implement File System Monitoring

Detect suspicious extraction behavior:

const chokidar = require('chokidar');

function monitorExtraction(extractDir) {
  const watcher = chokidar.watch(extractDir, {
    ignored: /(^|[\/\\])\../, // ignore dotfiles
    persistent: true
  });

  watcher.on('add', (path) => {
    // Check if file is outside expected directory
    if (!isWithinDirectory(extractDir, path)) {
      console.error('Security violation: file created outside extraction dir');
      // Take action: stop process, alert, etc.
    }
  });
}

6. Security Standards & References

This vulnerability maps to several security standards:

  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
  • CWE-59: Improper Link Resolution Before File Access ('Link Following')
  • OWASP Top 10: A05:2021 – Security Misconfiguration
  • OWASP ASVS: V12.1 File Upload Requirements

7. Testing for Path Traversal

Include security tests in your CI/CD pipeline:

// Example test case
describe('Archive Extraction Security', () => {
  it('should reject archives with path traversal in hardlinks', async () => {
    const maliciousArchive = createMaliciousArchive({
      linkpath: '../../../../etc/passwd',
      path: 'innocent.txt'
    });

    await expect(
      tar.extract({ file: maliciousArchive, cwd: './safe-dir' })
    ).rejects.toThrow(/path traversal/i);
  });

  it('should reject absolute paths in linkpaths', async () => {
    const maliciousArchive = createMaliciousArchive({
      linkpath: '/etc/passwd',
      path: 'innocent.txt'
    });

    await expect(
      tar.extract({ file: maliciousArchive, cwd: './safe-dir' })
    ).rejects.toThrow();
  });
});

Conclusion

CVE-2026-24842 serves as a critical reminder that archive handling is a high-risk operation requiring careful security consideration. The path traversal vulnerability in node-tar's hardlink processing could have allowed attackers to compromise systems through seemingly innocent file extraction operations.

Key takeaways:

  1. Update immediately: Ensure you're using the patched version of node-tar
  2. Audit your dependencies: Regularly check for known vulnerabilities
  3. Implement defense in depth: Don't rely solely on library security
  4. Validate all file operations: Especially when handling untrusted archives
  5. Apply least privilege: Run extraction processes with minimal permissions

Archive extraction vulnerabilities aren't theoretical—they're actively exploited in the wild. By understanding how these attacks work and implementing proper defenses, you can protect your applications and users from compromise.

Stay vigilant, keep your dependencies updated, and always treat user-provided archives as potentially malicious. Security isn't a one-time fix; it's an ongoing practice of awareness, validation, and defense.


Resources:
- OWASP Path Traversal
- CWE-22: Path Traversal
- npm Security Best Practices
- Node.js Security Best Practices

Frequently Asked Questions

What is a hardlink bypass path traversal vulnerability?

It occurs when an archive extractor validates the destination path of a regular file but fails to apply the same validation to the `linkpath` (the target of a hardlink), letting an attacker use `../` sequences in the link target to point outside the safe extraction directory.

How do you prevent hardlink path traversal in Node.js tar libraries?

Always sanitize both the entry path and the `linkpath` field using the same normalization logic — strip leading slashes, resolve and reject `..` components, and confirm the resolved path remains inside the extraction root before creating any link.

What CWE is hardlink bypass path traversal?

CWE-22 (Improper Limitation of a Pathname to a Restricted Directory — "Path Traversal"), and potentially CWE-59 (Improper Link Resolution Before File Access — "Link Following").

Is checking only the entry path enough to prevent tar path traversal?

No. Hardlink entries carry a separate `linkpath` field that specifies the link target. If only the entry path is checked, an attacker can put a safe-looking entry path and a malicious `linkpath` with `../` sequences to escape the extraction root.

Can static analysis detect hardlink path traversal in node-tar?

Yes. Static analysis tools and SAST scanners like Semgrep can flag cases where `entry.linkpath` (or equivalent archive-supplied link-target fields) flows into file-system operations without prior path normalization or containment checks.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #65

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.