{"record":{"id":"b8767cd981a518a6","repo":"anthropics/skills","slug":"relationship-target-escapes-the-package-target-r","errorCode":null,"errorMessage":"relationship target escapes the package: {target!r}","messagePattern":"relationship target escapes the package: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/docx/scripts/office/helpers/__init__.py","lineNumber":48,"sourceCode":"        return None\n\n    target = urllib.parse.unquote(target)\n\n    if \"\\\\\" in target:\n        raise ValueError(f\"relationship target is not a POSIX part name: {target!r}\")\n\n    if target.startswith(\"/\"):\n        joined = target.lstrip(\"/\")\n    else:\n        joined = posixpath.join(posixpath.dirname(source_part), target)\n\n    parts: list[str] = []\n    for segment in posixpath.normpath(joined).split(\"/\"):\n        if segment in (\"\", \".\"):\n            continue\n        if segment == \"..\":\n            if not parts:\n                raise ValueError(f\"relationship target escapes the package: {target!r}\")\n            parts.pop()\n        else:\n            parts.append(segment)\n\n    if not parts:\n        raise ValueError(f\"relationship target resolves to nothing: {target!r}\")\n    return \"/\".join(parts)\n\n\ndef rels_source_part(rels_file: Path, unpacked_dir: Path) -> str:\n    owner_dir = rels_file.parent.parent.relative_to(unpacked_dir)\n    return posixpath.join(owner_dir.as_posix(), rels_file.name[: -len(\".rels\")]).lstrip(\"./\")\n\n\ndef part_text(data: bytes) -> str:\n    return data.decode(\"utf-8\", \"surrogateescape\")\n\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/anthropics/skills/blob/f6656c1256d5a8adfa37db9110046ef20bac644c/skills/docx/scripts/office/helpers/__init__.py#L30-L66","documentation":"opc_target() resolves a relative relationship target against the source part and rejects targets whose normalized path climbs above the package root ('..' more times than there are path segments). OPC part names are package-absolute; escaping the root means the target is malformed and any resolution would be arbitrary. Present in both docx and pptx copies of office/helpers/__init__.py.","triggerScenarios":"A relationship like Target=\"../../../../etc/passwd\" or \"../../outside.xml\" attached to a part near the root (e.g. source_part='_rels/.rels' with heavy '../' traversal); also '..' as the very first segment of an absolute-style target after normpath.","commonSituations":"Zip-slip-style traversal attempts in malicious or fuzzed OOXML files; hand-built .rels files where the author counted directory levels wrong; converters that emit '../../' prefixes relative to the wrong base part.","solutions":["Open the .rels file named in your processing log and fix the Target so it stays inside the package (e.g. '../media/image1.png' from ppt/slides/_rels/, not '../../../../media/image1.png').","Remember relative targets resolve against the RELATIONSHIP FILE'S owner part directory, not the package root — use a leading '/' for package-absolute targets.","If the input is untrusted, treat this error as a security signal and quarantine the file rather than repairing it.","Regenerate the package with a conformant tool (PowerPoint, python-pptx, openpyxl)."],"exampleFix":"<!-- before -->\n<Relationship Id=\"rId1\" Type=\".../slide\" Target=\"../../../../slides/slide1.xml\"/>\n\n<!-- after: package-absolute target -->\n<Relationship Id=\"rId1\" Type=\".../slide\" Target=\"/ppt/slides/slide1.xml\"/>","handlingStrategy":"validation","validationCode":"import posixpath\n\ndef target_stays_in_package(target: str, source_part: str) -> bool:\n    if not target or target.startswith(\"/\"):\n        return True  # absolute targets cannot escape via '..'\n    joined = posixpath.normpath(posixpath.join(posixpath.dirname(source_part), target))\n    return not joined.startswith(\"..\") and \"/..\" not in joined","typeGuard":null,"tryCatchPattern":"try:\n    opc_target(target, source_part, target_mode)\nexcept ValueError as e:\n    if \"escapes the package\" in str(e):\n        log.security(\"traversal target rejected: %r from %s\", target, source_part)\n        skip_relationship()  # never auto-repair untrusted traversal targets\n    else:\n        raise","preventionTips":["Prefer package-absolute targets (leading '/') when authoring .rels files.","Treat 'escapes the package' on untrusted input as a security event, not a bug to patch.","Run packages through an OOXML validator before processing.","Count '..' segments against the source part depth when hand-writing relative targets."],"tags":["ooxml","opc","security","path-traversal","relationships","docx","pptx"],"backgroundTag":null,"analyzedSha":"f6656c1256d5a8adfa37db9110046ef20bac644c","analyzedAt":"2026-08-14T16:09:17.493Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}