{"record":{"id":"781286c0d58e6154","repo":"anthropics/skills","slug":"relationship-target-escapes-the-package-target-r-781286","errorCode":null,"errorMessage":"relationship target escapes the package: {target!r}","messagePattern":"relationship target escapes the package: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/xlsx/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/xlsx/scripts/office/helpers/__init__.py#L30-L66","documentation":"opc_target() resolves relationship targets against the source part and rejects any that walk above the package root: when a '..' segment pops an empty segment stack, the target escapes the OPC package. This guards against both malformed files and deliberate ../.. path traversal inside .rels.","triggerScenarios":"Loading a package whose .rels has Target=\"../../../../etc/passwd\" or any target that, resolved relative to the source part (e.g. xl/worksheets + ../..), leaves the package root; also absolute-looking targets that normalize to a parent traversal.","commonSituations":"Malicious or corrupt Office files (zip-slip via relationships); documents generated by buggy writers computing relative paths from the wrong base; merging .rels from different packages without recomputing targets.","solutions":["Unzip the file and inspect the Target values in the .rels named in the error — anything starting with ../ chains is the culprit","Discard files from untrusted sources that trigger this; it is a traversal red flag, not something to patch around","If you generate packages, compute targets with posixpath.relpath against the correct source part and assert they stay under the root"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import posixpath\n\ndef target_stays_in_package(target: str, source_part: str) -> bool:\n    if target.startswith(\"/\"):\n        joined = target.lstrip(\"/\")\n    else:\n        joined = posixpath.join(posixpath.dirname(source_part), target)\n    depth = 0\n    for seg in posixpath.normpath(joined).split(\"/\"):\n        if seg in (\"\", \".\"):\n            continue\n        depth = depth - 1 if seg == \"..\" else depth + 1\n        if depth < 0:\n            return False\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    part = opc_target(target, source_part, mode)\nexcept ValueError as e:\n    if \"escapes the package\" in str(e):\n        raise SuspiciousDocument(source_part, target) from e  # quarantine, don't sanitize\n    raise","preventionTips":["Treat traversal in .rels as a security signal: quarantine the file, never rewrite-and-load silently","When generating packages, assert resolved targets start with the package root before writing","Run untrusted Office files through this validator in a sandboxed pipeline step"],"tags":["ooxml","path-traversal","security","validation","rels"],"backgroundTag":null,"analyzedSha":"f6656c1256d5a8adfa37db9110046ef20bac644c","analyzedAt":"2026-08-14T16:09:17.493Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}