league/flysystem: Flysystem: WhitespacePathNormalizer's control-character (CorruptedPathDetected) check is bypassed by malformed UTF-8 in the path, affecting every adapter
What fixes it
- 3.35.3league/flysystem, composerGitHub
composer require "league/flysystem:^3.35.3" - 3.35.3league/flysystem, PackagistOSV
composer require "league/flysystem:^3.35.3"
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
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 PathTraversalDetectedRecommended 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
Text from GitHub Advisory Database
Affected versions 53
- league/flysystem, composer<= 3.35.2GitHub
- flysystemaffected: < 3.35.3CVE List
- league/flysystem, Packagistfrom first release, fixed in 3.35.3OSV
- league/flysystem, Packagist0.1.0OSV
- league/flysystem, Packagist0.1.1OSV
- league/flysystem, Packagist0.1.10OSV
- league/flysystem, Packagist0.1.11OSV
- league/flysystem, Packagist0.1.12OSV
- league/flysystem, Packagist0.1.13OSV
- league/flysystem, Packagist0.1.14OSV
- league/flysystem, Packagist0.1.15OSV
- league/flysystem, Packagist0.1.16OSV
- league/flysystem, Packagist0.1.17OSV
- league/flysystem, Packagist0.1.18OSV
- league/flysystem, Packagist0.1.19OSV
- league/flysystem, Packagist0.1.2OSV
- league/flysystem, Packagist0.1.20OSV
- league/flysystem, Packagist0.1.3OSV
- league/flysystem, Packagist0.1.4OSV
- league/flysystem, Packagist0.1.5OSV
- league/flysystem, Packagist0.1.6OSV
- league/flysystem, Packagist0.1.7OSV
- league/flysystem, Packagist0.1.8OSV
- league/flysystem, Packagist0.1.9OSV
- league/flysystem, Packagist0.2.0OSV
- league/flysystem, Packagist0.2.1OSV
- league/flysystem, Packagist0.2.10OSV
- league/flysystem, Packagist0.2.11OSV
- league/flysystem, Packagist0.2.12OSV
- league/flysystem, Packagist0.2.13OSV
- league/flysystem, Packagist0.2.14OSV
- league/flysystem, Packagist0.2.15OSV
- league/flysystem, Packagist0.2.2OSV
- league/flysystem, Packagist0.2.3OSV
- league/flysystem, Packagist0.2.4OSV
- league/flysystem, Packagist0.2.5OSV
- league/flysystem, Packagist0.2.6OSV
- league/flysystem, Packagist0.2.7OSV
- league/flysystem, Packagist0.2.8OSV
- league/flysystem, Packagist0.2.9OSV
- league/flysystem, Packagist0.3.0OSV
- league/flysystem, Packagist0.3.1OSV
- league/flysystem, Packagist0.3.2OSV
- league/flysystem, Packagist0.3.3OSV
- league/flysystem, Packagist0.3.4OSV
- league/flysystem, Packagist0.3.5OSV
- league/flysystem, Packagist0.4.0OSV
- league/flysystem, Packagist0.4.1OSV
- league/flysystem, Packagist0.4.2OSV
- league/flysystem, Packagist0.4.3OSV
- league/flysystem, Packagist0.4.4OSV
- league/flysystem, Packagist0.4.5OSV
- league/flysystem, Packagist0.5.0OSV
References 5
- github.com/thephpleague/flysystem/commit/ef4a9a557d769b5d472c403125716706a0d9cc77Fixvia CVE List
- github.com/thephpleague/flysystem/releases/tag/3.35.3Fixvia CVE List
- github.com/advisories/GHSA-cxf4-7mrp-vvprAdvisoryvia GitHub
- github.com/thephpleague/flysystem/security/advisories/GHSA-cxf4-7mrp-vvprExploitvia CVE List
- nvd.nist.gov/vuln/detail/CVE-2026-102601Referencevia GitHub