{"repro_id":"REPRO-2026-00065","version":5,"title":"node-tar Arbitrary File Overwrite via Hardlink Escape","repro_type":"security","status":"published","severity":"high","description":"The `node-tar` library (`<= 7.5.2`) fails to sanitize the `linkpath` of `Link` (hardlink) and `SymbolicLink` entries when `preservePaths` is false (the default secure behavior). This allows malicious archives to bypass the extraction root restriction, leading to **Arbitrary File Overwrite** via hardlinks and **Symlink Poisoning** via absolute symlink targets.","root_cause":"## Summary\n`node-tar` ≤ 7.5.2 does not sanitize the `linkpath` field of `Link` entries when extracting with `preservePaths=false`. A malicious TAR can hardlink a file inside the extraction root (e.g., `payload`) to an absolute path outside the root (e.g., `/tmp/secret.txt`). When the extractor later writes to the in-root path, it actually overwrites the arbitrary external file, enabling path-escape and arbitrary file overwrite.\n\n## Impact\n- **Affected component:** `node-tar` hardlink extraction logic (`unpack.ts`)\n- **Affected versions:** 7.5.2 and earlier (fixed in 7.5.3)\n- **Risk:** High. Attackers controlling TAR input can overwrite any writable file reachable by the extracting process, enabling configuration tampering or code execution in build/CI pipelines.\n\n## Root Cause\n`node-tar` resolves hardlink targets with `path.resolve(this.cwd, entry.linkpath)` but never checks that the resulting path still resides under `cwd`. Because `path.resolve` returns the second operand unchanged when it is absolute, an attacker can supply an absolute `linkpath`. The library then creates a hardlink from the in-root `entry.path` to the attacker-chosen absolute file. Subsequent writes to `entry.path` therefore modify the external target. Fix commit (upstream): https://github.com/isaacs/node-tar/commit/340eb285b6d986e91969a1170d7fe9b0face405e adds validation to reject absolute link targets.\n\n## Reproduction Steps\n1. Run `repro/reproduction_steps.sh`.\n2. Script installs Node dependencies (including `node-tar@7.5.2`), crafts a malicious TAR with a hardlink pointing to `secret.txt` outside the extraction root, extracts it with `tar.x({preservePaths:false})`, writes `EXPLOIT` into `out/payload`, and finally reads `secret.txt`.\n3. Reproduction succeeds when the script reports `secret.txt now contains: EXPLOIT` and exits 0.\n\n## Evidence\n- Logs: `logs/repro_20260117075559.log`, `logs/repro_20260117075603.log`\n- Key excerpt: `secret.txt now contains: EXPLOIT` followed by `Reproduction successful: secret.txt overwritten via hardlink escape`\n- Environment: Node v22.21.1, npm 10.9.4 as captured in the log prologue.\n\n## Recommendations / Next Steps\n- Upgrade to `node-tar` ≥ 7.5.3 where absolute link targets are rejected.\n- When possible, reject TAR archives containing `Link`/`SymbolicLink` entries or perform manual validation that resolved paths stay inside the extraction root.\n- Add regression tests that extract crafted archives with absolute `linkpath`s to ensure future sanitization.\n\n## Additional Notes\n- Script is idempotent: executed twice consecutively; both runs succeeded and produced separate timestamped logs.\n- Limitation: PoC demonstrates hardlink overwrite only; symlink poisoning is related but not exercised here.\n","ghsa_id":"GHSA-8qq5-rm4j-mr97","package":{"name":"tar","ecosystem":"npm"},"reproduced_at":"2026-01-17T08:40:59.437680+00:00","duration_secs":368.2825458049774,"tool_calls":102,"handoffs":2,"total_cost_usd":0.78994825,"agent_costs":{"repro":0.7673282499999999,"support":0.02262},"cost_breakdown":{"repro":{"gpt-5.1-codex":0.7673282499999999},"support":{"gpt-5.1-codex":0.02262}},"quality":{"idempotent_verified":false,"community_verifications":0},"key_moments":[{"agent":"repro","description":"The vulnerable version tar@7.5.2 was installed, setting up the environment for reproduction.","index":198,"time":188771.7363834381,"title":"Installed vulnerable tar version","type":"env_setup"},{"agent":"repro","description":"The proof-of-concept exploit code was written and saved as manual/poc.js.","index":219,"time":234871.38533592224,"title":"Created exploit code","type":"poc_created"},{"agent":"repro","description":"The exploit was first run using node manual/poc.js, attempting to trigger the vulnerability.","index":225,"time":241628.5879611969,"title":"Executed exploit script","type":"vuln_triggered"},{"agent":"repro","description":"A reproduction shell script was created to automate the steps for triggering the vulnerability.","index":234,"time":315926.5158176422,"title":"Wrote reproduction script","type":"poc_created"},{"agent":"repro","description":"The reproduction shell script was executed to verify the exploit and reproduce the vulnerability.","index":245,"time":328082.5369358063,"title":"Ran reproduction script","type":"vuln_triggered"},{"agent":"repro","description":"The root cause analysis report was written, confirming successful reproduction and understanding of the vulnerability.","index":293,"time":358763.28015327454,"title":"Generated RCA report","type":"confirmation"}],"published_at":"2026-01-17T08:41:11.852259+00:00","retracted":false,"artifacts":[{"path":"repro/rca_report.md","filename":"rca_report.md","size":2751,"category":"analysis"},{"path":"repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":3334,"category":"reproduction_script"},{"path":"bundle/ticket.md","filename":"ticket.md","size":4630,"category":"ticket"},{"path":"logs/repro_20260117075559.log","filename":"repro_20260117075559.log","size":1040,"category":"log"},{"path":"logs/repro_20260117075603.log","filename":"repro_20260117075603.log","size":1040,"category":"log"}]}