{"record":{"id":"086356fd03e12e12","repo":"santifer/career-ops","slug":"font-reference-escapes-fonts-keeping-origina","errorCode":null,"errorMessage":"⚠️  Font reference escapes fonts/, keeping original reference: ${name}","messagePattern":"⚠️  Font reference escapes fonts/, keeping original reference: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"generate-pdf.mjs","lineNumber":1394,"sourceCode":" *\n * @param {string} html - HTML that may reference url('./fonts/<file>').\n * @returns {Promise<string>} HTML with local font references inlined.\n */\nconst _fontDataUrlCache = new Map();\n\nexport async function inlineLocalFonts(html) {\n  const FONT_REF = /url\\(\\s*(['\"]?)\\.\\/fonts\\/([^'\")\\s]+)\\1\\s*\\)/g;\n  const MIME = { woff2: 'font/woff2', woff: 'font/woff', otf: 'font/otf', ttf: 'font/ttf' };\n  const fontsDir = resolve(__dirname, 'fonts');\n  const names = [...new Set([...html.matchAll(FONT_REF)].map((m) => m[2]))];\n  const dataUrls = new Map();\n  for (const name of names) {\n    // Containment check: \"..\" segments and absolute names (./fonts//etc/passwd)\n    // would otherwise resolve outside fonts/.\n    const fontPath = resolve(fontsDir, name);\n    const rel = relative(fontsDir, fontPath);\n    if (rel.startsWith('..') || isAbsolute(rel)) {\n      console.warn(`⚠️  Font reference escapes fonts/, keeping original reference: ${name}`);\n      continue;\n    }\n    if (_fontDataUrlCache.has(fontPath)) {\n      dataUrls.set(name, _fontDataUrlCache.get(fontPath));\n      continue;\n    }\n    try {\n      const buf = await readFile(fontPath);\n      const ext = name.slice(name.lastIndexOf('.') + 1).toLowerCase();\n      const dataUrl = `url('data:${MIME[ext] || 'application/octet-stream'};base64,${buf.toString('base64')}')`;\n      _fontDataUrlCache.set(fontPath, dataUrl);\n      dataUrls.set(name, dataUrl);\n    } catch (err) {\n      if (err?.code !== 'ENOENT') throw err;\n      console.warn(`⚠️  Font file not found, keeping original reference: fonts/${name}`);\n    }\n  }\n  return html.replace(FONT_REF, (match, _quote, name) => dataUrls.get(name) || match);","sourceCodeStart":1376,"sourceCodeEnd":1412,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/generate-pdf.mjs#L1376-L1412","documentation":"inlineLocalFonts() in generate-pdf.mjs rewrites CSS url(./fonts/...) references into base64 data URLs. As a containment check, it resolves each font name against the fonts/ directory and rejects any that would escape it (a path with '..' segments or an absolute component); the offending reference is kept as-is and warned about, so the font simply does not get inlined.","triggerScenarios":"CSS containing url('./fonts/../secret.woff2') or a crafted name like ./fonts//etc/passwd — relative(fontsDir, fontPath) starts with '..' or is absolute, the containment branch fires, and the original reference survives into the rendered HTML (typically breaking the font in the PDF).","commonSituations":"Hand-edited template CSS with ../ paths to fonts stored outside fonts/; copied CSS from another project whose font layout differs; deliberately directory-traversal-shaped test fixtures probing the inliner.","solutions":["Move the font file into the fonts/ directory and reference it as ./fonts/<name> so it can be inlined","Fix the CSS path to remove '..' segments or absolute components pointing outside fonts/","If the external reference is intentional (e.g. a CDN font), accept that it is not inlined and verify the PDF renders the fallback font acceptably"],"exampleFix":"/* before */\n@font-face { src: url('./fonts/../assets/inter.woff2'); }\n\n/* after — font moved into fonts/ */\n@font-face { src: url('./fonts/inter.woff2'); }","handlingStrategy":"validation","validationCode":"import { resolve, relative, isAbsolute } from 'node:path';\nfunction staysInsideFontsDir(fontsDir, name) {\n  const rel = relative(fontsDir, resolve(fontsDir, name));\n  return !(rel.startsWith('..') || isAbsolute(rel));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep all inlined font files inside fonts/ and reference them as ./fonts/<file>","Audit template CSS for ../ or absolute url() paths after copying snippets from other projects"],"tags":["fonts","path-traversal","security","css","cv-generation","warning"],"backgroundTag":"path-traversal-blocked","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}