Skip to content

fix: validateOutputPath symlink bypass — resolve real path before safe-dir check#745

Open
Gonzih wants to merge 1 commit intogarrytan:mainfrom
Gonzih:fix/validateOutputPath-symlink-bypass
Open

fix: validateOutputPath symlink bypass — resolve real path before safe-dir check#745
Gonzih wants to merge 1 commit intogarrytan:mainfrom
Gonzih:fix/validateOutputPath-symlink-bypass

Conversation

@Gonzih
Copy link
Copy Markdown

@Gonzih Gonzih commented Apr 1, 2026

The Bug

`validateOutputPath()` calls `path.resolve(filePath)` and then checks if the result is within the safe directories. `path.resolve` normalizes `..` components lexically but does not follow symlinks.

Attack:
```bash
ln -s /etc /tmp/evil

then: screenshot /tmp/evil/cron.d/payload

```
`path.resolve('/tmp/evil/cron.d/payload')` returns `/tmp/evil/cron.d/payload` which passes `isPathWithin(_, '/tmp')`, but the actual `fs.writeFile` follows the symlink and writes to `/etc/cron.d/payload`.

Issue #665. Also related to #667.

Fix

After the lexical check, resolve the real path of the nearest existing ancestor directory using `fs.realpathSync` and re-validate against safe directories. Handles the case where the output file doesn't exist yet (walks up the tree to find the nearest existing parent).

```
/tmp/evil/ → realpathSync → /etc/
/tmp/evil/cron.d/ → /etc/cron.d/ (not in SAFE_DIRECTORIES) → BLOCKED
```


sent from mStack

…cking safe dirs

path.resolve() normalizes .. components but does NOT follow symlinks.
A symlink inside /tmp or cwd pointing outside those dirs would pass
the isPathWithin() check while the actual write goes to the symlink target.

e.g.: ln -s /etc /tmp/evil; screenshot /tmp/evil/cron.d/payload
  → path.resolve gives /tmp/evil/cron.d/payload (passes check)
  → fs.writeFile writes to /etc/cron.d/payload (security fail)

Fix: after the lexical check, call fs.realpathSync on the nearest
existing ancestor directory and re-validate the real path. Falls back
safely up the directory tree if the parent doesn't exist yet.

Closes garrytan#665
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant