- Location
- categories/maps/bake-basemap.mjs:61
- Finding
- Directory Traversal Through the Environment-Controlled Output Name<![CDATA[
## Vulnerability Details
**File Location**: `categories/maps/bake-basemap.mjs:61`, `categories/maps/bake-basemap.mjs:111-113`, `categories/maps/bake-basemap.mjs:211`, `categories/maps/bake-basemap.mjs:226-230`, `categories/maps/bake-basemap.mjs:257`
**Vulnerability Type**: Path traversal and arbitrary file creation or overwrite
**Risk Level**: High
### Vulnerable Code
The output name is accepted directly from the environment:
```js
const NAME = process.env.NAME || "basemap";
```
It is then concatenated into multiple filesystem destinations without filename validation or containment checks:
```js
const OUT = process.env.OUT || process.cwd(); // artifacts → workspace (cwd), NOT the installed skill dir
const framesDir = join(OUT, "frames-" + NAME);
mkdirSync(framesDir, { recursive: true });
```
```js
await page.screenshot({
path: join(framesDir, `f${String(i).padStart(4, "0")}.png`),
clip: { x: 0, y: 0, width: 1920, height: 1080 },
optimizeForSpeed: true,
});
```
```js
const mp4 = join(OUT, NAME + ".mp4"),
pat = join(framesDir, "f%04d.png");
const ff = spawnSync(
"ffmpeg",
[
"-y",
"-framerate",
String(FPS),
"-i",
pat,
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-g",
"1",
"-crf",
"16",
"-movflags",
"+faststart",
mp4,
],
{ stdio: "ignore" },
);
```
```js
writeFileSync(join(OUT, NAME + "-coords.json"), JSON.stringify(coords));
```
### Technical Analysis
`NAME` is treated as a simple filename but is not restricted to filename-safe characters. Node.js `join()` normalizes path components, including `..`. A value containing path separators and enough parent-directory segments can therefore cause the normalized destination to escape `OUT`.
The MP4 path and coordinate JSON path are directly derived from `join(OUT, NAME + suffix)`. FFmpeg is invoked with `-y`, which authorizes overwriting an existing destination without prompting. The frame directory also incorporates `NAME`,
...[truncated 1492 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Restrict `NAME` to a basename-only allowlist:
```js
if (!/^[A-Za-z0-9_-]+$/.test(NAME)) {
throw new Error("NAME must contain only letters, digits, underscores, and hyphens");
}
```
2. Reject absolute paths, path separators, `.` components, `..` components, null bytes, and platform-specific separators.
3. Resolve and verify every output destination before use:
```js
import { resolve, relative, isAbsolute } from "node:path";
const outRoot = resolve(OUT);
const destination = resolve(outRoot, `${NAME}.mp4`);
const rel = relative(outRoot, destination);
if (rel.startsWith("..") || isAbsolute(rel)) {
throw new Error("Output path escapes OUT");
}
```
4. Apply the containment check independently to frame, MP4, and JSON paths.
5. Create outputs with non-overwrite semantics where practical. Avoid unconditional FFmpeg `-y`, or require explicit approval before replacing an existing file.
6. Run the helper with a filesystem sandbox that permits writes only beneath a dedicated output directory.
7. Treat environment variables as untrusted configuration and validate all of them at the program boundary.
]]>