santifer/career-ops · error
Unsupported template format
Error message
Unsupported template format: ${format} (expected html or tex) What it means
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.
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.
Example fix
// before
loadTemplate('cv', { format: userInput }); // userInput = '../../etc/passwd'
// after
const format = ['html', 'tex'].includes(userInput) ? userInput : 'html';
loadTemplate('cv', { format }); Defensive patterns
Strategy: validation
Validate before calling
const VALID_FORMATS = new Set(['html', 'tex']);
function safeFormat(input) {
return VALID_FORMATS.has(input) ? input : 'html';
} Type guard
const isTemplateFormat = (v) => v === 'html' || v === 'tex';
Try / catch
try {
const tpl = loadTemplate(kind, { format });
} catch (e) {
if (e.message.startsWith('Unsupported template format:')) {
console.error(`${e.message}; refusing to build`);
process.exitCode = 2;
return null;
}
throw e;
} Prevention
- 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).
When it happens
Trigger: 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.
Common situations: Users attempting unsupported output formats; case-mismatched input; security testing or accidental traversal payloads in the format option; refactors that renamed the tex format.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- a 40-hex commit --sha is required
- arbeitnow: URL must use HTTPS
- eightfold: URL must use HTTPS
- ⚠️ Font reference escapes fonts/, keeping original…
- justjoin: untrusted hostname
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/47826fd00e34c472.
Report an issue: GitHub.
Appendix: source
Thrown at cv-templates.mjs:53
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ');
}
export function kebab(display) {
return String(display)
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
// The only template formats the resolver recognizes. `format` reaches path
// construction (fileFor) unmodified, so it must be allowlisted or a value like
// `--format=../../etc/passwd` would traverse out of the templates dir.
const VALID_FORMATS = new Set(['html', 'tex']);
function assertFormat(format) {
if (!VALID_FORMATS.has(format)) {
throw new Error(`Unsupported template format: ${format} (expected html or tex)`);
}
}
// filename → {name, format} | null. Base "cv-template.html" → name "standard";
// "cv-template.<name>.html" → that name. Only html/tex are recognized.
function parseFilename(prefix, file) {
const m = file.match(new RegExp(`^${prefix}(?:\\.([a-z0-9-]+))?\\.(html|tex)$`));
if (!m) return null;
return { name: m[1] || 'standard', format: m[2] };
}
export function parseMeta(path) {
let text;
try {
text = readFileSync(path, 'utf-8');
} catch {
return {};
}View on GitHub (pinned to aac998c7ed)