{"record":{"id":"07fd2dfb05019ef5","repo":"NousResearch/hermes-agent","slug":"invalid-pdf-file-header","errorCode":null,"errorMessage":"Invalid PDF file header","messagePattern":"Invalid PDF file header","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/desktop/src/app/chat/right-rail/preview-file.tsx","lineNumber":249,"sourceCode":"    .split(';')\n    .map(part => part.trim().toLowerCase())\n\n  const payload = dataUrl.slice(comma + 1)\n\n  if (metadata[0] !== 'application/pdf' || !metadata.slice(1).includes('base64')) {\n    throw new Error('Invalid PDF data URL type')\n  }\n\n  let binary: string\n\n  try {\n    binary = atob(decodeURIComponent(payload))\n  } catch {\n    throw new Error('Invalid PDF data URL payload')\n  }\n\n  if (!binary.startsWith('%PDF-')) {\n    throw new Error('Invalid PDF file header')\n  }\n\n  const bytes = new Uint8Array(binary.length)\n\n  for (let index = 0; index < binary.length; index += 1) {\n    bytes[index] = binary.charCodeAt(index)\n  }\n\n  return new Blob([bytes], { type: 'application/pdf' })\n}\n\nasync function readTextPreview(filePath: string) {\n  try {\n    return await readDesktopFileText(filePath)\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error)\n\n    if (!message.includes(\"No handler registered for 'hermes:readFileText'\")) {","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/src/app/chat/right-rail/preview-file.tsx#L231-L267","documentation":"Final content guard in dataUrlToBlob: after successful base64 decode it requires the decoded bytes to start with the literal '%PDF-' magic. Anything else means the payload decoded fine but is not a PDF (HTML error page, PNG, JSON, plain text). This stops the preview from handing a non-PDF blob to the PDF renderer, which would otherwise fail with a cryptic renderer error.","triggerScenarios":"An application/pdf data URL whose bytes are actually HTML (common when a download endpoint returned an error page but the caller labeled it as PDF), a screenshot image mislabeled as PDF, or an encrypted/patched PDF variant without the standard header offset 0.","commonSituations":"Signed/expired download links returning HTML login pages saved as .pdf; tools labeling all document artifacts 'pdf'; zero-byte or header-shifted payloads after bad decoding.","solutions":["Re-fetch the source document and verify with `curl -I` / `file` that it really is a PDF.","Fix upstream classification so the attachment kind reflects actual bytes, not the filename extension.","If the payload is HTML/text, route it to the text/HTML preview instead.","For producers: set the data URL MIME from the real content type of the streamed bytes."],"exampleFix":"// before\nif (!binary.startsWith('%PDF-')) {\n  throw new Error('Invalid PDF file header')\n}\n\n// after — give the user the actual detected type in the error\nconst header = binary.slice(0, 8)\nif (!binary.startsWith('%PDF-')) {\n  throw new Error(`Invalid PDF file header (got: ${JSON.stringify(header)})`)\n}","handlingStrategy":"validation","validationCode":"const decodedHead = atob(payload.slice(0, 8))\nif (!decodedHead.startsWith('%PDF-')) {\n  // route to text/html/image preview based on sniffed magic bytes instead of failing\n}","typeGuard":"const hasPdfMagic = (bytes: Uint8Array): boolean =>\n  bytes[0] === 0x25 /* % */ && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46","tryCatchPattern":"try { blob = dataUrlToBlob(url) } catch (e) { if (e.message === 'Invalid PDF file header') { offerDownload(url) /* let the user open it externally */ } }","preventionTips":["Sniff magic bytes at download time and label artifacts by real content type","Treat HTML responses from download endpoints as errors, not PDFs","Offer a raw download fallback when preview validation fails"],"tags":["desktop","preview","pdf","magic-bytes","validation"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}