parallax/jsPDF · error · Error

Cannot read file '${url}'. Permission denied.

Error message

Cannot read file '${url}'. Permission denied.

What it means

When Node's permission model is active (process.permission exists), nodeReadFile checks process.permission.has('fs.read', url) against the resolved real path. If the Node allow-list does not cover that exact path, jsPDF throws 'Permission denied.' This check takes precedence over allowFsRead — even if allowFsRead lists the path, Node's deny wins.

Source

Thrown at src/modules/fileloading.js:150

    if (!process.permission && !this.allowFsRead) {
      throw new Error(
        "Trying to read a file from local file system. To enable this feature either run node with the --permission and --allow-fs-read flags or set the jsPDF.allowFsRead property."
      );
    }

    try {
      url = fs.realpathSync(path.resolve(url));
    } catch (e) {
      if (sync) {
        return undefined;
      } else {
        callback(undefined);
        return;
      }
    }

    if (process.permission && !process.permission.has("fs.read", url)) {
      throw new Error(`Cannot read file '${url}'. Permission denied.`);
    }

    if (this.allowFsRead) {
      const allowRead = this.allowFsRead.some(allowedUrl => {
        const starIndex = allowedUrl.indexOf("*");
        if (starIndex >= 0) {
          const fixedPart = allowedUrl.substring(0, starIndex);
          let resolved = path.resolve(fixedPart);
          if (fixedPart.endsWith(path.sep) && !resolved.endsWith(path.sep)) {
            resolved += path.sep;
          }
          return url.startsWith(resolved);
        } else {
          return url === path.resolve(allowedUrl);
        }
      });
      if (!allowRead) {
        throw new Error(`Cannot read file '${url}'. Permission denied.`);

View on GitHub (pinned to a3930ce03a)

Solutions

  1. Add the resolved realpath (or its parent with a trailing slash) to node's --allow-fs-read list.
  2. Resolve symlinks before granting permission, or avoid symlinks in the asset path.
  3. Grant the directory: node --permission --allow-fs-read=./assets/ so all files under it are readable.
  4. Verify with process.permission.has('fs.read', require('fs').realpathSync(path)) before calling loadFile.

Example fix

// before
// node --permission --allow-fs-read=./fonts app.js
// requesting ./linked/Roboto.ttf -> symlink outside ./fonts -> throws [114]

// after: allow the resolved directory
// node --permission --allow-fs-read=./fonts --allow-fs-read=./linked app.js
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
const path = require('path');
function canReadViaNodePerm(url) {
  if (!process.permission) return true; // model not active
  let real;
  try { real = fs.realpathSync(path.resolve(url)); } catch { return false; }
  return process.permission.has('fs.read', real);
}

Type guard

function nodePermActive() { return typeof process !== 'undefined' && !!process.permission; }

Try / catch

try {
  doc.loadFile(url, true);
} catch (e) {
  if (/Permission denied/.test(e.message) && process.permission) {
    // surface the missing --allow-fs-read flag to the operator; do not silently bypass
    throw new Error('Add --allow-fs-read=' + require('path').dirname(require('fs').realpathSync(url)));
  }
  throw e;
}

Prevention

When it happens

Trigger: Running `node --permission --allow-fs-read=./fonts` but requesting a file outside the allowed tree (e.g. ./secrets/key.pem, or a symlink resolving outside); allow-fs-read pattern that does not match the realpath returned by fs.realpathSync.

Common situations: Symlinks resolving outside the allowed directory; relative allow patterns vs absolute realpath mismatches; broadening read targets (new font folder) without updating the --allow-fs-read flags.

Related errors


AI-assisted analysis of parallax/jsPDF@a3930ce03a (2026-08-13). Data as JSON: /api/errors/e1c92f96c1469c1f. Report an issue: GitHub.