dompdf/dompdf · error · Dompdf\Exception
Error loading $file: $message
Error message
Error loading $file: $message
What it means
Every allowed protocol carries validation rules executed before loading; for file:// the default rule is the chroot check, which rejects any resolved path outside the directories configured in Options::chroot (by default the dompdf library directory). This exception wraps the failing rule's message — most commonly 'Permission denied. The file could not be found under the paths specified by Options::chroot.' It protects against path traversal/local file inclusion when rendering untrusted HTML.
Source
Thrown at src/Dompdf.php:370
$protocol = strtolower($this->protocol);
$uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file, $this->options->getChroot());
$allowed_protocols = $this->options->getAllowedProtocols();
if (!array_key_exists($protocol, $allowed_protocols)) {
throw new Exception("Permission denied on $file. The communication protocol is not supported.");
}
if ($protocol === "file://") {
$ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
if (!in_array($ext, $this->allowedLocalFileExtensions)) {
throw new Exception("Permission denied on $file: The file extension is forbidden.");
}
}
foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
[$result, $message] = $rule($uri);
if (!$result) {
throw new Exception("Error loading $file: $message");
}
}
[$contents, $http_response_header] = Helpers::getFileContent($uri, $this->options->getHttpContext());
if ($contents === null) {
throw new Exception("File '$file' not found.");
}
// See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
if (isset($http_response_header)) {
foreach ($http_response_header as $_header) {
if (preg_match("@Content-Type:\s*[\w/]+;\s*?charset=([^\s]+)@i", $_header, $matches)) {
$encoding = strtoupper($matches[1]);
break;
}
}
}
View on GitHub (pinned to b14267808b)
Solutions
- Whitelist the directories you actually load from: $dompdf->getOptions()->setChroot(['/var/www/site/templates']).
- Place the HTML under an already-allowed root or reference it relative to that root.
- Or fetch the content yourself (file_get_contents within your own security policy) and call loadHtml().
- Never widen chroot for user-supplied paths — validate them against your own allowlist first.
Example fix
// before
$dompdf->loadHtmlFile('/var/www/app/templates/invoice.html');
// throws: file not under default chroot (dompdf's own directory)
// after
$dompdf->getOptions()->setChroot(['/var/www/app/templates']);
$dompdf->loadHtmlFile('/var/www/app/templates/invoice.html'); Defensive patterns
Strategy: validation
Validate before calling
$chroots = $dompdf->getOptions()->getChroot(); // array of allowed roots
$real = realpath($file);
$ok = $real !== false;
foreach ($chroots as $root) {
$r = realpath($root);
if ($r === false || !$ok || strpos($real, $r . DIRECTORY_SEPARATOR) !== 0 && $real !== $r) {
continue;
}
$ok = $ok && true;
break;
}
if (!$ok) {
throw new InvalidArgumentException("File outside dompdf chroot: $file");
}
$dompdf->loadHtmlFile($file); Try / catch
try {
$dompdf->loadHtmlFile($file);
} catch (\Dompdf\Exception $e) {
if (strpos($e->getMessage(), 'Options::chroot') !== false) {
// either the file is genuinely disallowed (reject), or loadHtml() after your own validation
}
throw $e;
} Prevention
- Configure Options::setChroot() explicitly to your template directories at bootstrap.
- Validate user-supplied paths against your own allowlist before they reach dompdf.
- Beware symlinks and case differences on macOS/Windows when reasoning about chroot membership.
When it happens
Trigger: $dompdf->loadHtmlFile('/var/www/site/templates/x.html') where the chroot still points at dompdf's own directory; absolute paths on systems where the default chroot does not cover them; symlinks resolving outside the chroot; Windows paths where realpath/case handling differs.
Common situations: Loading app templates without ever configuring chroot; deployments that move dompdf via composer so the default root changes; hardening reviews that tighten (but misconfigure) chroot; mixed absolute/relative path handling in the calling code.
Related errors
- Permission denied on $file: The file extension is forbidden.
- Permission denied on $file. The communication protocol is no
- File '$file' not found.
AI-assisted analysis of dompdf/dompdf@b14267808b (2026-08-21).
Data as JSON: /api/errors/0476a30c1b06db18.
Report an issue: GitHub.