Every CVE Search/ Get API key

CVE-2026-102601

Flysystem: WhitespacePathNormalizer's control-character (CorruptedPathDetected) check is bypassed by malformed UTF-8 in the path, affecting every adapter

3.5 LowFix available

Flysystem is an open source file storage library for PHP. Prior to 3.35.3, the default WhitespacePathNormalizer in src/WhitespacePathNormalizer.php used by Filesystem across adapters calls preg_match with the u modifier and treats both false and 0 as falsy. A path containing malformed UTF-8 causes PCRE to return false, so paths that also contain control characters bypass CorruptedPathDetected::forPath() in normalizePath(). Filesystem::write() can store such names and Filesystem::listContents() can return the raw ANSI escape sequences, allowing hidden or spoofed terminal file listings when an administrator displays them. This issue is fixed in version 3.35.3.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
Required

What an attacker can affect

Confidentiality
None
Integrity
Low
Availability
None

Exploit likelihood

0.34%Unchanged since Sep 30, 2026
90%50%10%1%

What each source reports

CVE List Score used 3.5

Flysystem: WhitespacePathNormalizer's control-character (CorruptedPathDetected) check is bypassed by malformed UTF-8 in the path, affecting every adapter

Flysystem is an open source file storage library for PHP. Prior to 3.35.3, the default WhitespacePathNormalizer in src/WhitespacePathNormalizer.php used by Filesystem across adapters calls preg_match with the u modifier and treats both false and 0 as falsy. A path containing malformed UTF-8 causes PCRE to return false, so paths that also contain control characters bypass CorruptedPathDetected::forPath() in normalizePath(). Filesystem::write() can store such names and Filesystem::listContents() can return the raw ANSI escape sequences, allowing hidden or spoofed terminal file listings when an administrator displays them. This issue is fixed in version 3.35.3.

Record
CVE-2026-102601
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Weakness
CWE-150
Updated
2026-11-02
EUVDEUVD-2026-88998 3.5

Flysystem is an open source file storage library for PHP. Prior to 3.35.3, the default WhitespacePathNormalizer in src/WhitespacePathNormalizer.php used by Filesystem across adapters calls preg_match with the u modifier and treats both false and 0 as falsy. A path containing malformed UTF-8 causes PCRE to return false, so paths that also contain control characters bypass CorruptedPathDetected::forPath() in normalizePath(). Filesystem::write() can store such names and Filesystem::listContents() can return the raw ANSI escape sequences, allowing hidden or spoofed terminal file listings when an administrator displays them. This issue is fixed in version 3.35.3.

Record
EUVD-2026-88998
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-cxf4-7mrp-vvpr 3.5

Flysystem: WhitespacePathNormalizer's control-character (CorruptedPathDetected) check is bypassed by malformed UTF-8 in the path, affecting every adapter

Related public issue (context, not a duplicate)

Closed issue #1429 ("Handle non-UTF-8 paths", 2024-03-24) raised exactly this general concern and even suggested detection via preg_match('//u', $path) !== 1 -- note the reporter's suggested check explicitly compares !== 1, which would correctly treat PCRE's false return as "reject." The maintainer's reply pointed to the PathNormalizer interface as the place to implement this. The control-character check that ended up shipping in WhitespacePathNormalizer (if (preg_match('#\p{C}+#u', $unixPath))) addresses the general concern but does not use the !== 1-style comparison the original issue suggested -- it uses a bare truthy check, which is exactly the gap this report demonstrates. So this is not a duplicate of #1429; it's a concrete bypass surviving in the fix that issue's concern led to.

Vulnerability Details

File: src/WhitespacePathNormalizer.php, lines 22-28 (normalizePath()) -- the default PathNormalizer used by Filesystem for every adapter (Local, FTP, SFTP, S3, AsyncAwsS3, Azure, GCS, ZipArchive, GridFS, InMemory) unless the application supplies a custom one.

Root Cause

public function normalizePath(string $path): string
{
    $unixPath = str_replace('\\', '/', $path);

    if (preg_match('#\p{C}+#u', $unixPath)) {
        throw CorruptedPathDetected::forPath($path);
    }
    ...

preg_match() returns false (a PHP engine error) rather than 0 when the subject string is not valid UTF-8 and the pattern uses the /u modifier -- PCRE can't even attempt the match. false and 0 are both falsy in PHP, and if (preg_match(...)) does not distinguish them. So a path containing any single invalid UTF-8 byte anywhere in the string makes preg_match() fail with a "Malformed UTF-8 characters" engine error, the if evaluates false, and CorruptedPathDetected is silently not thrown -- even when the same string also contains literal control characters this exact check exists to catch.

the identical payload IS correctly rejected once it's valid UTF-8:

$n->normalizePath("foo\x1bbar");         // valid UTF-8, contains ESC -> throws CorruptedPathDetected (correct)
$n->normalizePath("foo\x80\x1bbar");    // 0x80 = invalid lone UTF-8 continuation byte -> NOT thrown (bypass)

The path-traversal protection is unaffected -- it's exact byte-string comparison on /-delimited segments, independent of UTF-8 validity:

$n->normalizePath("\x80/../../etc/passwd");   // still throws PathTraversalDetected

Recommended Fix

public function normalizePath(string $path): string
{
    $unixPath = str_replace('\\', '/', $path);
    $matched = preg_match('#\p{C}+#u', $unixPath);

    if ($matched !== 0) {
        // $matched === false means malformed UTF-8 -- must also be treated as corrupted,
        // not silently allowed through.
        throw CorruptedPathDetected::forPath($path);
    }
    ...

Verification

Dynamically confirmed on league/flysystem HEAD 6837e1d / tag 3.35.2, PHP 8.4.22 CLI, end-to-end through LocalFilesystemAdapter:

[1] write() succeeded -- normalizer did NOT reject the path.
[2] Actual bytes on disk: ...801b5b386d6e6f726d616c2d6c6f6f6b696e672d66696c652e7478741b5b306d...
[3] Filesystem::listContents() path contains raw ESC (0x1b): YES
[4] Raw terminal output (via `cat -v`): M-^@^[[8mnormal-looking-file.txt^[[0m^[[2K^[[1Aurgent-invoice.pdf
Record
GHSA-cxf4-7mrp-vvpr
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Weakness
CWE-150
Updated
2026-11-02
NVD 3.5

Flysystem is an open source file storage library for PHP. Prior to 3.35.3, the default WhitespacePathNormalizer in src/WhitespacePathNormalizer.php used by Filesystem across adapters calls preg_match with the u modifier and treats both false and 0 as falsy. A path containing malformed UTF-8 causes PCRE to return false, so paths that also contain control characters bypass CorruptedPathDetected::forPath() in normalizePath(). Filesystem::write() can store such names and Filesystem::listContents() can return the raw ANSI escape sequences, allowing hidden or spoofed terminal file listings when an administrator displays them. This issue is fixed in version 3.35.3.

Record
CVE-2026-102601
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Weakness
CWE-150
Updated
2026-11-02
OSV 3.5

Flysystem: WhitespacePathNormalizer's control-character (CorruptedPathDetected) check is bypassed by malformed UTF-8 in the path, affecting every adapter

Flysystem is an open source file storage library for PHP. Prior to 3.35.3, the default WhitespacePathNormalizer in src/WhitespacePathNormalizer.php used by Filesystem across adapters calls preg_match with the u modifier and treats both false and 0 as falsy. A path containing malformed UTF-8 causes PCRE to return false, so paths that also contain control characters bypass CorruptedPathDetected::forPath() in normalizePath(). Filesystem::write() can store such names and Filesystem::listContents() can return the raw ANSI escape sequences, allowing hidden or spoofed terminal file listings when an administrator displays them. This issue is fixed in version 3.35.3.

Record
CVE-2026-102601
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Weakness
CWE-150
Updated
2026-11-02
OSVDEBIAN-CVE-2026-102601 3.5

Flysystem is an open source file storage library for PHP. Prior to 3.35.3, the default WhitespacePathNormalizer in src/WhitespacePathNormalizer.php used by Filesystem across adapters calls preg_match with the u modifier and treats both false and 0 as falsy. A path containing malformed UTF-8 causes PCRE to return false, so paths that also contain control characters bypass CorruptedPathDetected::forPath() in normalizePath(). Filesystem::write() can store such names and Filesystem::listContents() can return the raw ANSI escape sequences, allowing hidden or spoofed terminal file listings when an administrator displays them. This issue is fixed in version 3.35.3.

Record
DEBIAN-CVE-2026-102601
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Updated
2026-11-02
Show 5 moreShow less
OSVGHSA-cxf4-7mrp-vvpr 3.5

Flysystem: WhitespacePathNormalizer's control-character (CorruptedPathDetected) check is bypassed by malformed UTF-8 in the path, affecting every adapter

Related public issue (context, not a duplicate)

Closed issue #1429 ("Handle non-UTF-8 paths", 2024-03-24) raised exactly this general concern and even suggested detection via preg_match('//u', $path) !== 1 -- note the reporter's suggested check explicitly compares !== 1, which would correctly treat PCRE's false return as "reject." The maintainer's reply pointed to the PathNormalizer interface as the place to implement this. The control-character check that ended up shipping in WhitespacePathNormalizer (if (preg_match('#\p{C}+#u', $unixPath))) addresses the general concern but does not use the !== 1-style comparison the original issue suggested -- it uses a bare truthy check, which is exactly the gap this report demonstrates. So this is not a duplicate of #1429; it's a concrete bypass surviving in the fix that issue's concern led to.

Vulnerability Details

File: src/WhitespacePathNormalizer.php, lines 22-28 (normalizePath()) -- the default PathNormalizer used by Filesystem for every adapter (Local, FTP, SFTP, S3, AsyncAwsS3, Azure, GCS, ZipArchive, GridFS, InMemory) unless the application supplies a custom one.

Root Cause

public function normalizePath(string $path): string
{
    $unixPath = str_replace('\\', '/', $path);

    if (preg_match('#\p{C}+#u', $unixPath)) {
        throw CorruptedPathDetected::forPath($path);
    }
    ...

preg_match() returns false (a PHP engine error) rather than 0 when the subject string is not valid UTF-8 and the pattern uses the /u modifier -- PCRE can't even attempt the match. false and 0 are both falsy in PHP, and if (preg_match(...)) does not distinguish them. So a path containing any single invalid UTF-8 byte anywhere in the string makes preg_match() fail with a "Malformed UTF-8 characters" engine error, the if evaluates false, and CorruptedPathDetected is silently not thrown -- even when the same string also contains literal control characters this exact check exists to catch.

the identical payload IS correctly rejected once it's valid UTF-8:

$n->normalizePath("foo\x1bbar");         // valid UTF-8, contains ESC -> throws CorruptedPathDetected (correct)
$n->normalizePath("foo\x80\x1bbar");    // 0x80 = invalid lone UTF-8 continuation byte -> NOT thrown (bypass)

The path-traversal protection is unaffected -- it's exact byte-string comparison on /-delimited segments, independent of UTF-8 validity:

$n->normalizePath("\x80/../../etc/passwd");   // still throws PathTraversalDetected

Recommended Fix

public function normalizePath(string $path): string
{
    $unixPath = str_replace('\\', '/', $path);
    $matched = preg_match('#\p{C}+#u', $unixPath);

    if ($matched !== 0) {
        // $matched === false means malformed UTF-8 -- must also be treated as corrupted,
        // not silently allowed through.
        throw CorruptedPathDetected::forPath($path);
    }
    ...

Verification

Dynamically confirmed on league/flysystem HEAD 6837e1d / tag 3.35.2, PHP 8.4.22 CLI, end-to-end through LocalFilesystemAdapter:

[1] write() succeeded -- normalizer did NOT reject the path.
[2] Actual bytes on disk: ...801b5b386d6e6f726d616c2d6c6f6f6b696e672d66696c652e7478741b5b306d...
[3] Filesystem::listContents() path contains raw ESC (0x1b): YES
[4] Raw terminal output (via `cat -v`): M-^@^[[8mnormal-looking-file.txt^[[0m^[[2K^[[1Aurgent-invoice.pdf
Record
GHSA-cxf4-7mrp-vvpr
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Weakness
CWE-150
Updated
2026-11-02
OSVUBUNTU-CVE-2026-102601 3.5

Flysystem is an open source file storage library for PHP. Prior to 3.35.3, the default WhitespacePathNormalizer in src/WhitespacePathNormalizer.php used by Filesystem across adapters calls preg_match with the u modifier and treats both false and 0 as falsy. A path containing malformed UTF-8 causes PCRE to return false, so paths that also contain control characters bypass CorruptedPathDetected::forPath() in normalizePath(). Filesystem::write() can store such names and Filesystem::listContents() can return the raw ANSI escape sequences, allowing hidden or spoofed terminal file listings when an administrator displays them. This issue is fixed in version 3.35.3.

Record
UBUNTU-CVE-2026-102601
CVSS 3.1
3.5 LowCVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N
Updated
2026-11-02
OSVCLSA-2026-1790868478 No score

TuxCare security update for league/flysystem (1 CVE)

TuxCare rebuilt league/flysystem to address 1 CVE. Fixed in 3.33.0-p1+tuxcare.

Record
CLSA-2026-1790868478
Updated
2026-11-02
OSVCLSA-2026-1791742369 No score

TuxCare security update for league/flysystem (1 CVE)

TuxCare rebuilt league/flysystem to address 1 CVE. Fixed in 1.0.70-p2+tuxcare.

Record
CLSA-2026-1791742369
Updated
2026-11-02
OSVCLSA-2026-1791742447 No score

TuxCare security update for league/flysystem (1 CVE)

TuxCare rebuilt league/flysystem to address 1 CVE. Fixed in 1.1.10-p2+tuxcare.

Record
CLSA-2026-1791742447
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
league/flysystem
Packagist
through 3.35.2
3.35.3
league/flysystem
TuxCare:Packagist
3.33.0 up to 3.33.0-p1+tuxcare
3.33.0-p1+tuxcare
league/flysystem
TuxCare:Packagist
1.0.70 up to 1.0.70-p2+tuxcare, plus listed versions
1.0.70-p1+tuxcare
1.0.70-p2+tuxcare
league/flysystem
TuxCare:Packagist
1.1.10 up to 1.1.10-p2+tuxcare, plus listed versions
1.1.10-p1+tuxcare
1.1.10-p2+tuxcare
php-league-flysystem
Debian:12
All versions, plus listed versions
1.1.10-11.1.10-23.28.0-13.29.1-13.29.1-23.36.0-1
None known
php-league-flysystem
Debian:13
All versions, plus listed versions
3.29.1-23.36.0-1
None known
php-league-flysystem
Debian:14
before 3.36.0-1, plus listed versions
3.29.1-2
3.36.0-1
league/flysystem
Packagist
before 3.35.3, plus listed versions
0.1.00.1.10.1.100.1.110.1.12
Show 45 moreShow less
0.1.130.1.140.1.150.1.160.1.170.1.180.1.190.1.20.1.200.1.30.1.40.1.50.1.60.1.70.1.80.1.90.2.00.2.10.2.100.2.110.2.120.2.130.2.140.2.150.2.20.2.30.2.40.2.50.2.60.2.70.2.80.2.90.3.00.3.10.3.20.3.30.3.40.3.50.4.00.4.10.4.20.4.30.4.40.4.50.5.0
3.35.3
php-league-flysystem
Ubuntu:16.04:LTS
All versions, plus listed versions
1.0.15-11.0.16-11.0.16-1build1
None known
php-league-flysystem
Ubuntu:22.04:LTS
All versions, plus listed versions
1.1.3-4
None known
php-league-flysystem
Ubuntu:24.04:LTS
All versions, plus listed versions
1.1.10-1
None known
php-league-flysystem
Ubuntu:26.10
All versions, plus listed versions
3.29.1-2
None known
php-league-flysystem
Ubuntu:26.04:LTS
All versions, plus listed versions
3.29.1-2
None known

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-102601 is a low vulnerability (CVSS 3.5) in Thephpleague Flysystem, published September 29, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. Fixed in league/flysystem 3.35.3.

References

Show 4 moreShow less
This page doesn't list everything

Left out here: 206 versions. The JSON record has them.

MoveEnter Open