This is a small, very old bug that is still being introduced in new code. It deserves its own post because the obvious fix is more dangerous than no fix at all.
What the API is for
`wordexp(3)` is the POSIX API for performing shell-style expansions from C. People reach for it to do something tiny: turn `~/foo` into `/home/me/foo`.
But the trailing `0` argument is flags, and by default **every expansion feature is enabled** — including command substitution. So if the input comes from a user, `$(whoami)/foo` runs `whoami`, and `$(cat /etc/passwd)/foo` reads the password file and returns its contents inside a path.
The obvious fix is not enough
Disabling command substitution requires passing `WRDE_NOCMD`. On two common libc implementations, that is only a palliative:
- musl implements `wordexp(3)` by delegating to the system shell. It does a perfunctory scan for command-substitution patterns before delegating, but misses some patterns the shell underneath (GNU Bash) still treats as valid.
- macOS libc also delegates, to `/usr/lib/system/wordexp-helper`. According to Julian Squires the same bypass was still effective as recently as 2023.
- So even when you follow the documentation exactly, untrusted input can still reach a shell.
What to do instead
- Do not use `wordexp(3)` on untrusted input — even with `WRDE_NOCMD`.
- If all you need is `~` expansion, write it: match the `~/` prefix and join with `$HOME`. That is a few lines and calls no shell.
- If you need environment variables, use `getenv` against an allow-list of names, not a general-purpose shell expansion API.
- Treat `wordexp(3)` like `system(3)`: an API that executes commands, not a string-processing function.
The original is a short TIL and worth reading verbatim, since it carries code samples for all three cases. Sources: POSIX wordexp(3) is dangerous, the wordexp spec, musl wordexp.c, Julian Squires on wordexp.