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

  1. Copy stream content to a real temp file first (File::temporaryFilename() + file_put_contents()), then load that path
  2. Reject or sanitize any input containing '://' at your trust boundary before it reaches the library
  3. 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

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


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/f14b08f06ac23e2b. Report an issue: GitHub.