POSIX wordexp(3) Runs Commands by Default, and WRDE_NOCMD Is a Weak Fix on musl
Software / explainer
POSIX wordexp(3) Runs Commands by Default, and WRDE_NOCMD Is a Weak Fix on musl
An Oct. 25 write-up on yossarian.net says no flag makes the C function safe for untrusted input, while the glibc manual's flag list carries no such warning.
A C programmer who calls wordexp(3) to expand ~/foo into a home directory path has also written a function that runs $(whoami). A short post dated Oct. 25, 2026 on yossarian.net, filed under c, posix and security, argues that the function is unsafe on untrusted input even with its protective flag set. The post names no author and cites no CVE.
The conclusion is blunt: "Bottom line: don't use wordexp(3) on untrusted inputs, even with WRDE_NOCMD."
What wordexp(3) does by default
wordexp is a POSIX function that performs shell-style expansion on a string. The glibc manual describes word expansion as splitting a string into words and substituting variables, commands and wildcards, as the shell does.
By default every one of those expansions is on. The post's examples are $(whoami)/foo and $(cat /etc/passwd)/foo: the first returns the current user's name in the path, the second returns the contents of the password file. A program that expands a configuration value or a user-typed path has handed the string to a shell.
What the flags promise
The glibc manual's "Flags for Word Expansion" page lists the switches that narrow this. The wording below is the manual's.
| Flag | Manual's description |
|---|---|
| WRDE_NOCMD | "Don’t do command substitution; if the input requests command substitution, report an error." |
| WRDE_UNDEF | "If the input refers to a shell variable that is not defined, report an error." |
| WRDE_SHOWERR | "Do show any error messages printed by commands run by command substitution." |
The manual page does not carry a security warning next to these flags. The only caution in that section concerns WRDE_REUSE, where the result vector may move, so an old pointer is not safe to keep. That is a memory warning, not a trust warning, and a reader who stops at the flag table will conclude that WRDE_NOCMD is the answer.
Why WRDE_NOCMD does not hold on musl
The post's case against the flag rests on how other C libraries implement the function. musl libc delegates the expansion to the system shell and adds a basic scan of the input for command substitutions. That scan misses some patterns that Bash still treats as command substitutions, so input can pass the check and still execute.
macOS takes a similar delegating route. The post says its libc now calls /usr/lib/system/wordexp-helper, which it says replaced an earlier Perl helper, and that Julian Squires reported the same bypass as musl in 2023. The author did not test whether the bypass still works now that zsh is the default shell on macOS, so the macOS claim is open.
The post gives no exploit string and no list of affected program versions, and it does not say whether any shipped software is vulnerable. It is a warning about a calling pattern, which makes it hard to size. A program that passes only constant strings, such as a hard-coded ~/.config, is not exposed. A program that expands a path from a config file, a URL or a network message is the case the author describes.
How to check your own code
Search a codebase for wordexp( and read the flags argument at each call site. Three questions sort the results. Does the string come from outside the program: a config file, an environment variable, a download, a message? Is WRDE_NOCMD set, and if so, is the program built against musl or on macOS, where the post says the flag leans on a scan of the input? And does anything downstream run with more privilege than the person who supplied the string?
A call that answers yes to the first and yes to the third is the one to replace first. A call on a literal string can wait. This article did not test glibc's behaviour. The post's objection, as reported above, is to the musl and macOS implementations of the same flag.
What to use instead
The post stops at the warning and does not name a replacement. For tilde expansion alone, the usual alternative is to read the HOME environment variable or call getpwnam for ~user forms, and to do any further splitting without a shell. That recommendation is this article's, not the post's, and the post does not endorse it.
The pattern recurs across recent disclosures where a string meant as data reached a shell, including PHP2Shell and the Plex2Shell patch deadlines CISA split in two. wordexp is a quieter version: the shell is the library itself, and the dangerous behaviour is the documented default.
The post is dated Oct. 25, and no libc maintainer response was linked from it when this was written.
Sources
More in Software
- 01Reddit's r/opensource Bans AI Projects and Anything Under a Year OldThe moderators' Oct. 24 rules would also turn away three repositories with 516 to 88,538 GitHub stars, all created within the past eight months.
- 02PaperPilot's Rust PDF Engine Quotes 4.5 ms; Its Own Table Says 179The Apache-2.0 toolkit hit 212 points on Hacker News on Oct. 26, where commenters questioned its parser choices and its provenance.
- 03NVIDIA's SkillSpector Lists 64, 68 or 71 Rules Depending on the PageThe Apache-2.0 scanner for AI agent skills has 21,918 GitHub stars, a 0-to-100 risk score and an optional LLM pass that sends file contents to a provider.
- 04WorldTUI's Fake Terminal Scores 80 of 106 on a Shell Test Its Author WroteSimon Edwardsson's 360-million-parameter model predicts screen changes from keypresses with no shell behind it, and he calls it a weak world model.