A Literal ~ in Your PATH Hijacks Commands

PATH entries aren't shell-expanded. A quoted tilde becomes a relative directory — and anyone who creates a dir named ~ owns your shell.

Share

There's a good chance your dotfiles contain PATH="~/bin:$PATH". In an interactive shell that line mostly works. In a crontab, a systemd unit, or a Dockerfile, that tilde is not your home directory — it's a one-character directory name, and it's relative. A security researcher's post that hit Hacker News last week lays out why that difference is exploitable, not just inconvenient.

When the tilde stays literal, command lookup searches a directory named ~ in whatever folder you happen to be standing in. Whoever creates that directory owns your commands.

Why this matters

Tilde expansion is a shell parsing step, not a property of paths. It only happens in unquoted words at assignment time. Cron, systemd's Environment=, and Docker's ENV never do it — they take your value literally, forever.

That turns one copied-and-pasted line into two different bugs: a dead entry that quietly hides your binaries, and a live one that runs other people's.

How it works

Shells and execvp resolve commands by walking PATH entries verbatim and trying <entry>/<command>. An entry of ~ builds the relative path ~/ls, which resolves to ./~/ls from your current directory.

Quoting is what bites. PATH=~/bin:$PATH expands the tilde at assignment. PATH="~/bin:$PATH" preserves the literal tilde — and once exported, every child process inherits it.

The attack is boringly simple: a repo or tarball contains a directory named ~ with an executable git inside. You clone, cd in, type git status, and run their code. ~ is a perfectly legal directory name; tar creates it without complaint.

Where this helps

  • Cron jobs that mysteriously can't find your scripts — crontab PATH= lines never tilde-expand. Use absolute paths.
  • systemd units and container images — Environment= and ENV values are literal; a tilde there is dead weight at best.
  • CI runners that execute PR code — a planted ~ directory plus a quoted-tilde PATH in the runner image is pre-merge code execution.
  • Security audits — any relative PATH entry is a finding. Empty entries (a leading or trailing :) mean CWD, the same class of bug.

Watch out

The fix isn't "stop quoting." Quote, but expand deliberately: $HOME in shell configs, absolute paths everywhere else.

Even $HOME lies. sudo often keeps your HOME, cron may reset