Automated Threat Modeling

PHPUnit CVE-2026-41570: Brief Summary of INI Directive Injection Leading to Remote Code Execution in CI Pipelines

A brief summary of CVE-2026-41570, a high-severity INI directive injection vulnerability in PHPUnit that enables remote code execution in child processes. Covers patch analysis and mitigation guidance for CI/CD environments.

CVE Analysis

8 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-08

PHPUnit CVE-2026-41570: Brief Summary of INI Directive Injection Leading to Remote Code Execution in CI Pipelines
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A single invisible newline character in a PHPUnit configuration file can give an attacker full remote code execution inside your CI pipeline's test runner. CVE-2026-41570 is a flaw in how PHPUnit forwards PHP INI settings to child processes. It allows INI directive injection, which turns a routine test run into a path to supply chain compromise.

PHPUnit is the de facto standard testing framework for PHP, and virtually every PHP project with automated tests uses it. Because it runs in so many CI/CD pipelines, a vulnerability in its process isolation mechanism has a broad blast radius across open-source and enterprise environments.

Technical Information

Root Cause: Unsanitized INI Value Forwarding

The vulnerability is in the settingsToParameters() method of src/Util/PHP/JobRunner.php. When PHPUnit forks child PHP processes for process-isolated tests or PHPT test execution, it forwards every PHP INI setting to the child process as -d name=value CLI arguments. Vulnerable versions (12.5.21 and 13.1.5) passed these values through with no escaping at all.

PHP's CLI INI parser gives several characters special meaning:

  • " (quotation mark) is a string delimiter
  • ; (semicolon) starts a comment, so the parser ignores everything after it
  • \n (newline) separates directives

None of these metacharacters were neutralized before the values reached the child process, so the child's INI parser would misread crafted values. The most dangerous case is a value containing a literal newline character. The child parses it as multiple distinct INI directives, which amounts to full INI directive injection.

This weakness maps to CWE-88 (Argument Injection) and CWE-93 (CRLF Injection).

Attack Flow

  1. Identify an injection point. The attacker needs to influence the content of an INI value that PHPUnit reads and forwards to child processes. The two primary sources are <ini name="..." value="..."/> entries in phpunit.xml or phpunit.xml.dist, and settings inherited from the host PHP runtime via ini_get_all().

  2. Craft a malicious INI value. The attacker puts a newline character inside a single INI value, followed by an arbitrary directive. For example, injecting auto_prepend_file=/tmp/payload.php after a newline makes the child process prepend and execute an attacker-controlled PHP file on startup.

  3. Deliver the payload. The most realistic delivery mechanism is a malicious pull request that modifies phpunit.xml to include the injected newline within an <ini> element. In a standard code diff, a newline character looks the same as legitimate whitespace, so reviewers are unlikely to notice it.

  4. Trigger execution. When a CI system picks up the pull request and runs PHPUnit without proper sandboxing, the child process spawned for isolated test execution receives the injected directives. The auto_prepend_file directive runs the attacker's payload before any test code.

  5. Achieve impact. With code execution inside the CI runner, the attacker can exfiltrate secrets and environment variables, tamper with build artifacts, establish persistence, or pivot to other systems reachable from the CI network.

This attack fits the broader category of Poisoned Pipeline Execution, in which adversaries target CI/CD systems by submitting malicious code that the build infrastructure then runs automatically. Besides auto_prepend_file, the injectable directives include extension, disable_functions, open_basedir, and memory_limit, and each can be weaponized for a different objective.

Patch Information

The fix for CVE-2026-41570 came in Pull Request #6592 from contributor kayw-geek. PHPUnit maintainer Sebastian Bergmann manually cherry-picked it into the 12.5 and 13.1 branches on April 17, 2026. The fix shipped in PHPUnit 12.5.22 and PHPUnit 13.1.6, both released the same day.

The patch spans two commits. Together they add a new private method, quoteSettingValue(), and wire it into the settings forwarding pipeline. This is the core change in the final commit (7b1b97a):

// In settingsToParameters(), each setting now passes through quoteSettingValue: foreach ($settings as $setting) { $buffer[] = '-d'; - $buffer[] = $setting; + $buffer[] = $this->quoteSettingValue($setting); }

The new quoteSettingValue() method quotes values only when they need it:

private function quoteSettingValue(string $setting): string { $position = strpos($setting, '='); if ($position === false) { return $setting; } $value = substr($setting, $position + 1); if (!str_contains($value, ';') && !str_contains($value, '"')) { return $setting; } $name = substr($setting, 0, $position); return $name . '="' . str_replace('"', '\\"', $value) . '"'; }

The method works in four steps:

  1. Find the = separator. If there is no = (a flag-style directive), the method returns the setting untouched.
  2. Extract the value (everything after =) and check whether it contains either of the dangerous metacharacters, ; or ".
  3. If neither metacharacter is present, return early without quoting. This early return is deliberate. Values like output_buffering=Off or error_reporting=E_ALL & ~E_NOTICE contain INI boolean keywords and bitwise expressions, and wrapping them in quotes would make PHP read them as literal strings instead of giving them their special INI meaning.
  4. If a metacharacter is present, wrap the value in double quotes and escape any internal double quote by replacing " with \".

The PR description gives a before-and-after example:

  • Before: -d highlight.string=(?:"foo";bar) leaves the child seeing (?:foo, because the parser treats everything after ; as a comment and " breaks parsing.
  • After: -d highlight.string="(?:\"foo\";bar)" lets the child see (?:"foo";bar) correctly, byte for byte.

As defense in depth, the quoting also closes the newline-based INI directive injection vector. Once the value sits inside double quotes, a newline character cannot break out of the quoted string, so it can no longer trick PHP into reading what follows as separate -d directives.

The PR also added a regression test in JobRunnerTest.php. It feeds a real-world 634-byte regex (from the Datadog ddtrace extension) through a forked process and asserts that the child's ini_get() output matches the input byte for byte, which checks the whole path end to end.

The patch also explicitly rejects any PHP setting value containing a newline or carriage return by throwing a PhpProcessException, so anomalous states fail loudly in CI output instead of passing through silently.

Affected Systems and Versions

PHPUnit Release SeriesVulnerable VersionsPatched VersionsStatus
13.x13.1.513.1.6Patch Available
12.x12.5.2112.5.22Patch Available
11.x and earlierNoneN/ANot Affected

Only PHPUnit 12.5.21 and 13.1.5 are affected. PHPUnit 11 and earlier versions do not contain the vulnerable code path and are not impacted. The vulnerability is relevant specifically to environments where PHPUnit runs process-isolated tests or PHPT tests, because those are the cases in which child processes receive forwarded INI settings.

The highest-risk configuration is a CI/CD environment that automatically runs PHPUnit against untrusted pull requests, particularly from forks.

Vendor Security History

PHPUnit has had severe vulnerabilities before. The most notable is CVE-2017-9841, a remote code execution flaw in eval-stdin.php that was widely exploited in the wild and is still commonly scanned for years after its disclosure.

The handling of CVE-2026-41570 shows a more mature security posture. Patches came out quickly, and the maintainer corrected false positives in the GitHub Advisory Database that wrongly flagged PHPUnit 11 as vulnerable. The patch itself was designed to make exploitation attempts visible through loud exceptions rather than let them fail silently.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization