{"record":{"id":"ceb009318f3a72a9","repo":"mjmlio/mjml","slug":"mjml-some-mj-include-paths-were-denied-because-t","errorCode":null,"errorMessage":"[MJML] Some mj-include paths were denied because they are outside the allowed directories.\\n  Files: ${filesWithDeniedIncludes.join(', ')}\\n  Add the include directories to --config.includePath to allow them.\\n  See https://documentation.mjml.io/#mj-include for security notes.","messagePattern":"\\[MJML\\] Some mj-include paths were denied because they are outside the allowed directories\\.\\\\n  Files: (.+?)\\\\n  Add the include directories to --config\\.includePath to allow them\\.\\\\n  See https://documentation\\.mjml\\.io/#mj-include for security notes\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/mjml-cli/src/client.js","lineNumber":333,"sourceCode":"      failedStream.push({ file: i.file, error: e })\n    }\n  }\n\n  convertedStream.forEach((s) => {\n    if (get(s, 'compiled.errors.length')) {\n      console.error(map(s.compiled.errors, 'formattedMessage').join('\\n')) // eslint-disable-line no-console\n    }\n  })\n\n  // Warn when includes were enabled but some include paths were denied (outside allowed roots).\n  if (config.ignoreIncludes === false) {\n    const DENIED_RE = /<!--\\s*mj-include denied\\s*-->/i\n    const filesWithDeniedIncludes = convertedStream\n      .filter((s) => s.compiled && s.compiled.html && DENIED_RE.test(s.compiled.html))\n      .map((s) => s.file)\n    if (filesWithDeniedIncludes.length) {\n      // eslint-disable-next-line no-console\n      console.warn(\n        `[MJML] Some mj-include paths were denied because they are outside the allowed directories.\\n` +\n          `  Files: ${filesWithDeniedIncludes.join(', ')}\\n` +\n          `  Add the include directories to --config.includePath to allow them.\\n` +\n          `  See https://documentation.mjml.io/#mj-include for security notes.`,\n      )\n    }\n  }\n\n  // Warn when mj-include tags are present but includes are still ignored.\n  if (config.ignoreIncludes !== false) {\n    const MJ_INCLUDE_RE = /<mj-include\\b/i\n    const filesWithIgnoredIncludes = inputs\n      .filter((i) => i && i.mjml && MJ_INCLUDE_RE.test(i.mjml))\n      .map((i) => i.file)\n    if (filesWithIgnoredIncludes.length) {\n      // eslint-disable-next-line no-console\n      console.warn(\n        `[MJML] mj-include tags were found but includes are disabled by default.\\n` +","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/mjmlio/mjml/blob/6c01d35af5da14108b86bbf052232e99de7ca755/packages/mjml-cli/src/client.js#L315-L351","documentation":"MJML's CLI scans the compiled HTML output of each input file for the sentinel comment '<!-- mj-include denied -->'. If found, it means some mj-include tags resolved to paths outside the directories allowed by includePath, and mjml-core replaced them with this denial marker instead of the included content. The CLI warns so the output is not silently missing included fragments.","triggerScenarios":"Running the mjml CLI (packages/mjml-cli) on files containing <mj-include path=\"...\" /> where the resolved path is outside the configured --config.includePath directories (or includePath is unset). The warning fires only when at least one compiled output matches DENIED_RE.","commonSituations":"CI builds after moving template files to a new directory; using absolute or ../ paths in mj-include; sharing templates across repos without extending includePath; upgrading MJML where includePath restrictions became stricter.","solutions":["Add the directory containing the included files to the CLI config, e.g. mjml --config.includePath ./src/templates ./shared/templates input.mjml","Change the mj-include paths so they resolve inside an allowed directory (relative paths within the project)","Verify which files are affected via the 'Files:' list and inspect their compiled output for the denied marker","If the includes are intentional and safe, explicitly enumerate only the needed folders rather than widening to a parent/root directory"],"exampleFix":"// before\nmjml templates/email.mjml\n// after\nmjml templates/email.mjml --config.includePath ./templates ./shared/partials","handlingStrategy":"validation","validationCode":"const DENIED_RE = /<!--\\s*mj-include denied\\s*-->/i\nconst denied = files.filter(f => DENIED_RE.test(fs.readFileSync(f, 'utf8')))\nif (denied.length) throw new Error(`Denied mj-include in: ${denied.join(', ')}`)","typeGuard":"const hasDeniedIncludes = (html) => typeof html === 'string' && /<!--\\s*mj-include denied\\s*-->/i.test(html)","tryCatchPattern":null,"preventionTips":["Always pass --config.includePath covering every folder containing included partials","Keep mj-include paths relative to the templates directory","Grep built HTML for 'mj-include denied' in CI","Document required includePath in the repo build docs"],"tags":["mjml","security","path-traversal","cli","mj-include"],"backgroundTag":"include-path-denied","analyzedSha":"6c01d35af5da14108b86bbf052232e99de7ca755","analyzedAt":"2026-09-02T21:31:03.649Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}