{"record":{"id":"47826fd00e34c472","repo":"santifer/career-ops","slug":"unsupported-template-format-format-expected-h","errorCode":null,"errorMessage":"Unsupported template format: ${format} (expected html or tex)","messagePattern":"Unsupported template format: (.+?) \\(expected html or tex\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cv-templates.mjs","lineNumber":53,"sourceCode":"    .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n    .join(' ');\n}\n\nexport function kebab(display) {\n  return String(display)\n    .trim()\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-+|-+$/g, '');\n}\n\n// The only template formats the resolver recognizes. `format` reaches path\n// construction (fileFor) unmodified, so it must be allowlisted or a value like\n// `--format=../../etc/passwd` would traverse out of the templates dir.\nconst VALID_FORMATS = new Set(['html', 'tex']);\nfunction assertFormat(format) {\n  if (!VALID_FORMATS.has(format)) {\n    throw new Error(`Unsupported template format: ${format} (expected html or tex)`);\n  }\n}\n\n// filename → {name, format} | null. Base \"cv-template.html\" → name \"standard\";\n// \"cv-template.<name>.html\" → that name. Only html/tex are recognized.\nfunction parseFilename(prefix, file) {\n  const m = file.match(new RegExp(`^${prefix}(?:\\\\.([a-z0-9-]+))?\\\\.(html|tex)$`));\n  if (!m) return null;\n  return { name: m[1] || 'standard', format: m[2] };\n}\n\nexport function parseMeta(path) {\n  let text;\n  try {\n    text = readFileSync(path, 'utf-8');\n  } catch {\n    return {};\n  }","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/cv-templates.mjs#L35-L71","documentation":"assertFormat allowlists template formats to exactly 'html' or 'tex'. Because the raw format string flows into template file path construction, an unvalidated value like `../../etc/passwd` would enable path traversal out of the templates directory, so the resolver fails fast with this error for anything outside the set.","triggerScenarios":"Calling listTemplates/loadTemplate with format='pdf', 'HTML', or a user-controlled string like `--format=../../etc/passwd` reaching assertFormat; an unvalidated CLI/env value passed as the format option.","commonSituations":"Users attempting unsupported output formats; case-mismatched input; security testing or accidental traversal payloads in the format option; refactors that renamed the tex format.","solutions":["Use format 'html' or 'tex' only.","Normalize/validate user-supplied format values before passing them to the resolver.","Fix case ('HTML' → 'html', 'TEX' → 'tex').","If path-traversal input is the trigger, treat it as hostile input — the error is the intended security guard."],"exampleFix":"// before\nloadTemplate('cv', { format: userInput }); // userInput = '../../etc/passwd'\n\n// after\nconst format = ['html', 'tex'].includes(userInput) ? userInput : 'html';\nloadTemplate('cv', { format });","handlingStrategy":"validation","validationCode":"const VALID_FORMATS = new Set(['html', 'tex']);\nfunction safeFormat(input) {\n  return VALID_FORMATS.has(input) ? input : 'html';\n}","typeGuard":"const isTemplateFormat = (v) => v === 'html' || v === 'tex';","tryCatchPattern":"try {\n  const tpl = loadTemplate(kind, { format });\n} catch (e) {\n  if (e.message.startsWith('Unsupported template format:')) {\n    console.error(`${e.message}; refusing to build`);\n    process.exitCode = 2;\n    return null;\n  }\n  throw e;\n}","preventionTips":["Never pass raw user/CLI input as the format; normalize through an allowlist.","Treat this error on traversal-looking input as a security signal, not a bug.","Keep the VALID_FORMATS set and the templates-dir naming convention in sync.","Add tests for hostile format values (../, absolute paths, case variants)."],"tags":["security","path-traversal","validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}