{"record":{"id":"017df4631041cfb2","repo":"different-ai/openwork","slug":"office-xml-dtd-and-entity-declarations-are-not-sup","errorCode":null,"errorMessage":"Office XML DTD and entity declarations are not supported.","messagePattern":"Office XML DTD and entity declarations are not supported\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/server/src/opencode-plugins/openwork-office-attachments.ts","lineNumber":353,"sourceCode":"  if (kind === \"docx\") {\n    return name === \"word/document.xml\"\n      || /^word\\/header\\d+\\.xml$/.test(name)\n      || /^word\\/footer\\d+\\.xml$/.test(name)\n      || name === \"word/footnotes.xml\"\n      || name === \"word/endnotes.xml\"\n      || name === \"word/comments.xml\";\n  }\n  return /^ppt\\/slides\\/slide\\d+\\.xml$/.test(name) || /^ppt\\/notesSlides\\/notesSlide\\d+\\.xml$/.test(name);\n}\n\nfunction compareEntryName(left: ZipEntry, right: ZipEntry): number {\n  return left.name.localeCompare(right.name, undefined, { numeric: true, sensitivity: \"base\" });\n}\n\nfunction assertSafeOfficeXml(xml: string): void {\n  if (Buffer.byteLength(xml, \"utf8\") > MAX_ENTRY_UNCOMPRESSED_BYTES) throw new Error(\"Office XML exceeds the parser input limit.\");\n  const lower = xml.toLowerCase();\n  if (lower.includes(\"<!doctype\") || lower.includes(\"<!entity\")) throw new Error(\"Office XML DTD and entity declarations are not supported.\");\n}\n\nfunction xmlLocalName(name: string): string {\n  const colon = name.lastIndexOf(\":\");\n  return (colon === -1 ? name : name.slice(colon + 1)).toLowerCase();\n}\n\nfunction parsedXmlText(xml: string, tagSeparator: string): string {\n  assertSafeOfficeXml(xml);\n  let text = \"\";\n  let omittedDepth = 0;\n  const omittedSeparator = tagSeparator || \" \";\n  const parser = new Parser({\n    onopentag(name) {\n      if (omittedDepth > 0) {\n        omittedDepth += 1;\n      } else if (xmlLocalName(name) === \"script\" || xmlLocalName(name) === \"style\") {\n        text += omittedSeparator;","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/opencode-plugins/openwork-office-attachments.ts#L335-L371","documentation":"assertSafeOfficeXml rejects Office XML containing <!doctype or <!entity declarations. Entity expansion (billion laughs) and external DTDs are classic XXE attacks, so the extractor only accepts plain XML without DTD/entity constructs.","triggerScenarios":"extractOfficeText on a docx/pptx whose XML part includes a DOCTYPE declaration or entity definitions — crafted malicious attachments, or files produced by tools that embed custom entities.","commonSituations":"Security testing with XXE payloads; rare office generators emitting custom entity definitions; hand-edited XML parts.","solutions":["Remove DTD/entity declarations from the document (re-save with Word/LibreOffice).","Replace entity references with literal text before attaching.","If legitimate entity use is required, preprocess the XML (resolve entities safely, e.g. with a hardened parser) before extraction.","Do not disable this check — it is a security guard; sanitize the input instead."],"exampleFix":"// before\n<?xml version=\"1.0\"?><!DOCTYPE document [<!ENTITY x \"y\">]><w:document>...</w:document>\n// after\n<?xml version=\"1.0\"?><w:document>...</w:document>","handlingStrategy":"validation","validationCode":"function xmlHasDtdOrEntity(xml: string): boolean {\n  const lower = xml.toLowerCase();\n  return lower.includes(\"<!doctype\") || lower.includes(\"<!entity\");\n}","typeGuard":"function isPlainOfficeXml(xml: string): xml is string {\n  const lower = xml.toLowerCase();\n  return !lower.includes(\"<!doctype\") && !lower.includes(\"<!entity\");\n}","tryCatchPattern":"try {\n  const text = extractOfficeText(kind, bytes);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"DTD and entity\")) {\n    throw new ApiError(400, \"unsafe_xml\", \"Attachment contains DTD/entity declarations and was rejected.\");\n  }\n  throw err;\n}","preventionTips":["Never disable this check — it blocks XXE/billion-laughs attacks.","Re-save documents with Word/LibreOffice to normalize XML and drop custom entities.","Sanitize third-party-generated office files before ingesting.","Log rejected files for security review; they may be probing."],"tags":["xml","security","xxe","office"],"backgroundTag":"xml-dtd-not-allowed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}