Safeguard
Vulnerability Analysis

Argument injection vulnerabilities explained

How argument injection (CWE-88) vulnerabilities work, real CVEs like PHPMailer and Git ssh URLs, and how teams detect and prevent CWE-88 flaws.

Bob
Application Security Engineer
7 min read

In December 2016, security researcher Dawid Golunski disclosed CVE-2016-10033, a critical flaw (CVSS 9.8) in PHPMailer, a library used in millions of PHP applications including WordPress and Drupal. The bug wasn't a buffer overflow or a deserialization flaw -- it was argument injection. PHPMailer passed a user-supplied "From" address straight into a sendmail command line, and a crafted value like -OQueueDirectory=/tmp -X/var/www/shell.php was read as command-line flags instead of an email address, letting an attacker write a PHP shell to disk. Argument injection vulnerabilities happen whenever untrusted input reaches a program's argument vector without being isolated from its option syntax, letting attackers redefine what a command does rather than just what it operates on. They show up in mail transfer agents, version control clients, container tooling, and CI/CD scripts alike. Here's how the class works, how it's been exploited in the wild, and how to stop it.

What Is an Argument Injection Vulnerability?

An argument injection vulnerability, tracked under CWE-88, occurs when an application builds a command's argument list from untrusted input and that input is interpreted as a flag, option, or positional override instead of a plain data value. Command-line tools like git, curl, tar, and sendmail distinguish "options" from "data" by a simple convention: anything starting with a hyphen is treated as a flag. If a program does exec("git", "clone", user_input) without checking that user_input can't start with -, an attacker who controls that string can pass --upload-pack=/bin/sh instead of a repository path. The program never meant to run arbitrary shell -- it just picked up a legitimate option it wasn't expecting. This is distinct from a program simply doing the wrong thing with bad data; here, the attacker is speaking the tool's own configuration language back to it through a channel the developer assumed was inert.

How Does Argument Injection Differ from Command Injection?

Argument injection differs from command injection (CWE-78) because the attacker never gets to inject shell metacharacters or chain a second command -- they only get to redefine which flags and arguments are passed to a single, already-fixed binary. Command injection looks like appending a semicolon and a new command, or a backticked subshell, to break out of a shell string, and it's what functions like PHP's escapeshellarg() and escapeshellcmd() are designed to stop. Argument injection is what's left over after those defenses work. In CVE-2016-10033, PHPMailer did call escapeshellarg() on the From address, which correctly prevented shell metacharacter escapes -- but it did nothing to stop the escaped string from still being read as a -O or -X sendmail flag once it reached execve(), because escapeshellarg() only quotes a value, it doesn't strip a leading hyphen. That gap is why the same codebase can pass a command-injection security review and still ship a critical RCE.

What Are Real-World Examples of Argument Injection Vulnerabilities?

Real-world argument injection has hit some of the most widely deployed developer tools. CVE-2016-10033 (PHPMailer, CVSS 9.8, fixed in 5.2.18) allowed unauthenticated RCE through a crafted sender address, and a follow-up bypass, CVE-2016-10045, showed the first patch was incomplete. SwiftMailer had an almost identical flaw, CVE-2016-10074, disclosed the same month. On the version-control side, CVE-2017-1000117 affected Git before 2.7.6, 2.8.6, 2.9.5, 2.10.4, 2.11.3, 2.12.4, 2.13.5, and 2.14.2 (patched August 10, 2017): a malicious ssh:// clone URL with a hostname beginning with a hyphen, such as ssh://-oProxyCommand=touch$IFS/tmp/pwned/repo, got passed straight to the local ssh binary as an option rather than a hostname, achieving code execution on the victim's machine just from running git clone. Outside of CVE databases, the "wildcard injection" technique documented by security researcher Leon Juranic in 2011 works the same way against tar, chown, and chmod: an attacker drops files named --checkpoint=1 and --checkpoint-action=exec=sh shell.sh into a shared directory, and when an admin's cron job runs tar -czf backup.tar.gz *, shell glob expansion feeds those filenames to tar as real arguments, triggering arbitrary command execution.

How Do Attackers Exploit Argument Injection in CI/CD Pipelines?

Attackers exploit argument injection in CI/CD by controlling values -- branch names, tags, commit messages, PR titles, package fields -- that a pipeline later hands to a command-line tool as an argument. A GitHub Actions workflow that runs git fetch origin $BRANCH_NAME using an attacker-supplied fork branch name, or a build script that calls docker build --build-arg VERSION=$TAG . using a Git tag pulled from an untrusted contributor, both create the same primitive as CVE-2017-1000117: a value the attacker fully controls, landing in argv without a leading-hyphen check. Because self-hosted runners and build agents typically execute with access to registry credentials, signing keys, or cloud IAM roles, a successful argument injection in a build script is frequently a direct path to supply-chain compromise rather than a contained local bug. Package manifests are another vector: npm, pip, and Maven all invoke helper binaries (compilers, linkers, git submodule fetchers) with fields read from package.json, setup.py, or pom.xml, and a malicious dependency can plant a hyphen-prefixed value in any field that ends up concatenated into a subprocess call during install. This is why argument injection sits squarely in the software supply chain risk category alongside typosquatting and dependency confusion: the injection point is rarely first-party code, it's a third-party tool or transitive dependency that a pipeline trusts to run without sanitizing its own inputs.

How Can Developers Detect and Prevent Argument Injection Vulnerabilities?

Developers prevent argument injection by refusing to let untrusted strings occupy an argument position without validation, and by using each tool's explicit end-of-options marker. Almost every POSIX-style CLI honors -- as a terminator that forces everything after it to be treated as a positional argument regardless of leading hyphens -- running Git clone with a -- before the repository URL, or tar with a -- before the file list, closes the exact gap that CVE-2017-1000117 and the tar wildcard technique both exploited. Where -- isn't supported, reject or prefix-strip any user-controlled value that starts with - before it reaches execve(), subprocess.run(), or Runtime.exec(), and pass arguments as an array rather than a concatenated string so quoting bugs can't reintroduce the shell-injection half of the problem. Static analysis rules for CWE-88 (available in Semgrep's p/security-audit pack and CodeQL's cwe-088 query pack) catch the pattern of untrusted input flowing into an argv-building call, and they should run in CI, not just at release time, since both PHPMailer patches (CVE-2016-10033 and CVE-2016-10045) show that a first fix attempt commonly misses an equivalent injection point. Manual code review should also treat any function name containing exec, spawn, popen, or system as a place to check the origin of every argument, not just the first one -- the PHPMailer bypass slipped through precisely because reviewers focused on the sendmail binary path rather than the flags trailing after it.

How Safeguard Helps

Safeguard's reachability analysis traces whether an argument-injection sink like execve(), subprocess.Popen(), or a git/tar/sendmail wrapper is actually reachable from an attacker-controlled input path in your code, rather than flagging every CWE-88 pattern match as equally urgent. Griffin AI, Safeguard's detection engine, understands the argv-construction idioms behind CVE-2016-10033 and CVE-2017-1000117 well enough to flag missing -- terminators and unvalidated leading-hyphen values across languages, and to generate auto-fix PRs that add the terminator or an allow-list check directly at the affected call site. Continuous SBOM generation and ingest give security teams a live inventory of which dependencies -- like an outdated PHPMailer or Git client -- carry known argument-injection CVEs, so a disclosure like CVE-2016-10045 triggers an immediate, scoped alert instead of a manual audit. Together, that turns a CWE-88 finding from a wall of low-confidence SAST noise into a short list of exploitable, fixable issues.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.