GitHub Advisoryadvisory · npm
GHSA-p498-v437-472g humanfs: Recursive copy follows symlinked files and copies data from outside the source tree
MEDIUM
Official page ↗
- Ecosystem
- npm
- Packages
- @humanfs/node
- Severity
- Moderate
- CVSS vector
- CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
- Weaknesses
- CWE-22
- Reviewed
- 2026-09-02
Affected packages
• npm @humanfs/node: < 0.16.8 → fixed in 0.16.8
Description
Summary
`@humanfs/node` does not treat symlinks as a separate case during copy operations. A symlink placed inside an attacker-controlled source tree can make `copyAll()` read and copy the contents of any file readable by the process, even when that file is outside the directory being copied.
Details
The Node implementation exposes symlink state through `list()`, but `copyAll()` ignores it. During recursive copies, every non-directory entry is passed to `copy()`, which delegates to `fs.promises.copyFile()`.
On Node, `copyFile()` dereferences symlinks. As a result, a symlink inside the copied tree is handled as if it were an ordinary file, and the destination receives the contents of the symlink target rather than a copy of the link itself.
That breaks the expected boundary of a directory copy. A caller can point `copyAll()` at a seemingly self-contained directory and still end up copying data from elsewhere on the host filesystem if the source tree contains attacker-supplied symlinks.
The same dereference behavior also affects `copy()` when the source path itself is a symlink.
PoC
```js
import { NodeHfs } from "@humanfs/node";
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
const root = await fs.mkdtemp(path.join(os.tmpdir(), "humanfs-"));
const src = path.join(root, "src");
const dst = path.join(root, "dst");
const secret = path.join(root, "secret.txt");
await fs.mkdir(src);
await fs.writeFile(secret, "TOPSECRET");
await fs.symlink(secret, path.join(src, "link.txt"));
const hfs = new NodeHfs();
await hfs.copyAll(src, dst);
console.log(await fs.readFile(path.join(dst, "link.txt"), "utf8"));
// TOPSECRET
```
Impact
If an application uses `copyAll()` or `copy()` on attacker-controlled paths, a symlink can be used to pull arbitrary readable host files into the copied output. In practice, that can turn a normal workspace copy, export, or packaging step into a file disclosure primitive.
Related
Weakness · CWE (1)
- CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness