PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception
Disallowed stream wrapper used for {$filename}
Error message
Disallowed stream wrapper used for {$filename} What it means
File::prohibitWrappers() blocks RCE-bearing PHP stream wrappers before any file operation (src/PhpSpreadsheet/Shared/File.php:152): literal phar:// prefixes, wrapper-looking prefixes containing whitespace/control characters, and any wrapper chain embedding phar: (e.g. compress.zlib://phar://evil.phar/x). assertFile() and testFileNoThrow() call it for every filename, so all readers reject such paths outright; data:// is intentionally allowed.
Source
Thrown at src/PhpSpreadsheet/Shared/File.php:152
return tempnam(self::sysGetTempDir(), 'phpspreadsheet') ?: throw new Exception('Could not create temporary file');
}
/**
* Blocks phar:// and similar RCE-bearing wrappers.
* Note that many protocols, including http and zip, will already
* return false for is_file.
* A whitelist of protocols may be added if needed in future.
* data: is intentionally allowed (see #4823); callers needing strict
* on-disk-only semantics must validate $filename themselves.
*/
public static function prohibitWrappers(string $filename): void
{
if (
Preg::IsMatch('~^phar://~i', $filename)
|| (Preg::isMatch('/^([\w.\s\x00-\x1f]+):/', $filename) && !Preg::isMatch('/^([\w.]+):/', $filename))
|| Preg::isMatch('~^[\w.]+://.*phar:~is', $filename)
) {
throw new Exception(
"Disallowed stream wrapper used for {$filename}"
);
}
}
/**
* Assert that given path is an existing file and is readable, otherwise throw exception.
*/
public static function assertFile(string $filename, string $zipMember = ''): void
{
self::prohibitWrappers($filename);
if (!is_file($filename) || !is_readable($filename)) {
throw new ReaderException('File "' . $filename . '" does not exist or is not readable.');
}
if ($zipMember !== '') {
$zipfile = "zip://$filename#$zipMember";
if (!self::fileExists($zipfile)) {View on GitHub (pinned to 65b080eef4)
Solutions
- Copy stream content to a real temp file first (File::temporaryFilename() + file_put_contents()), then load that path
- Reject or sanitize any input containing '://' at your trust boundary before it reaches the library
- Do not try to bypass the check — it is a deliberate remote-code-execution guard, not a bug
Example fix
// before
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('phar://archive.phar/sheet.xlsx'); // throws
// after
$data = file_get_contents('phar://archive.phar/sheet.xlsx'); // explicit, audited stream use
$tmp = \PhpOffice\PhpSpreadsheet\Shared\File::temporaryFilename();
file_put_contents($tmp, $data);
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($tmp); Defensive patterns
Strategy: validation
Validate before calling
function isSafeLocalPath(string $path): bool
{
if (preg_match('~^[\w.]+://~i', $path)) {
return false; // any stream wrapper, incl. phar://
}
return true;
}
if (!isSafeLocalPath($userPath)) {
throw new InvalidArgumentException('Stream wrappers are not accepted');
} Try / catch
try {
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($path);
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
if (str_contains($e->getMessage(), 'Disallowed stream wrapper')) {
// attacker-controlled or misused path: log & reject, do not retry
}
} Prevention
- Reject filenames containing '://' at the upload boundary
- Persist user/stream content to a temp file, then load the path
- Never feed phar:// or chained wrappers to file-based libraries
When it happens
Trigger: Passing 'phar://archive.phar/sheet.xlsx' to a reader's load(); smuggling phar through a wrapper chain (compress.zlib://phar://...); user-supplied filenames reused as read paths that happen to contain a colon-prefixed wrapper pattern with odd characters.
Common situations: Security-hardened apps after the 2018 phar deserialization CVE wave; request handlers that concatenate upload metadata into paths; test suites exercising wrapper paths; attempting to load remote/protocol URLs the reader never supported.
Related errors
- Cloning the calculation engine is not allowed!
- Locale file not found
- #VALUE!
- Invalid protocol for linked drawing
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/f14b08f06ac23e2b.
Report an issue: GitHub.