Should JavaScript task runners resolve allowlisted npm packages from custom runner images?

I investigated the implementation and found that:

  • allowedExternalModules is applied correctly.
  • The error changes from “Module is disallowed” to “Cannot find module”.
  • The failure happens during require() in require-resolver.js.

Before opening another GitHub issue or preparing a PR, I’d like to confirm whether the current behavior is intentional or whether this should be considered a bug.

Hi @Saif_Husain_Ansari Welcome!
That two stage behavior is by design, not a bug in the allowlist. NODE_FUNCTION_ALLOW_EXTERNAL (allowedExternalModules) is only a gate that decides which module names are permitted, it does not install or locate anything. Once a name passes, require-resolver.js runs ordinary Node resolution from the JS runner process, so “Cannot find module” means the package is not installed where that process resolves from. A global npm install -g, or installing into n8n’s node_modules while running an external runner, passes the allowlist and still fails at this exact point.
The supported fix is to install the package where the runner resolves and allowlist it in the same place. For an internal runner, install it into n8n’s own node_modules by extending the n8n image; for an external runner, extend the n8nio/runners image (needs 1.121.0 or later), install the package into the JS runner, and add it to n8n-task-runners.json.

If you have already installed it into the runner image correctly and still hit “Cannot find module” from require-resolver.js, that matches an open regression, so add your case there rather than filing a new one:

This isn’t probably a bug, but rather by design. Since allowedExternalModules only manages permission checking, it will not provide module resolution paths. The task runner’s process runs from separate working directory/node_modules. Even for allowlisted packages, they also need to be installed and be resolvable from there (for example, included in your custom runner image at the correct path, or in NODE_PATH which points to where they exist

Hi @Anshul_Namdev - thanks for the clear explanation.

That helped confirm the allowlist vs install/resolve split. I’ve solved it in my own environment by extending the runners image and baking packages in at build time.

Now adding a package is one command on the server:

./add-n8n-package.sh --node <package>
./add-n8n-package.sh --python <package>

Examples:

./add-n8n-package.sh --node jsonwebtoken
./add-n8n-package.sh --python pandas

That updates the package list, rebuilds the custom runners image, and restarts the runner - then the package is ready to use in the Code node.

Thanks again for the guidance.