docx
- Type
- Skill
- Scanned
- Oct 29, 2026
- Version
- ca45fc496a34
- Files read
- 61
This skill provides instructions and Python helper scripts for creating, editing, commenting on, and validating Microsoft Word (.docx) documents using docx-js, LibreOffice, and Pandoc. The biggest risk is that the XML validators use lxml with default settings that resolve external entities, allowing a maliciously crafted document to read local files on the user's machine.
What it can reach
- Files
- Reads, unpacks, modifies, validates, and writes Office document archives (.docx, .dotx, .pptx, .xlsx) and temporary files in /tmp.
- Shell commands
- Executes external binaries including LibreOffice (soffice), gcc, git, pandoc, pdftoppm, zip, and unzip.
Findings
-
HighXML External Entity (XXE) resolution in document schema validators
skills/docx/scripts/office/validators/base.py:167
Although other parts of the codebase use defusedxml, BaseSchemaValidator and its subclasses (DOCXSchemaValidator and PPTXSchemaValidator) parse untrusted XML files from unpacked documents using lxml.etree.parse() and lxml.etree.fromstring() with default parser settings. By default, lxml resolves XML entities (including local file:// URIs). A stranger who supplies a crafted .docx file containing an external entity declaration can cause local files to be read and leaked into validation error messages (such as whitespace, deletion, or XSD error previews) or trigger denial-of-service via entity expansion.
Fix: Pass a hardened parser instance such as lxml.etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False) to all lxml.etree.parse() and lxml.etree.fromstring() calls.
-
MediumPredictable /tmp path for LibreOffice macro profile allows local code execution
skills/docx/scripts/accept_changes.py:16
accept_changes.py uses a hardcoded directory (/tmp/libreoffice_docx_profile) to store and run a LibreOffice Basic macro. In _setup_libreoffice_macro(), if Module1.xba already exists and contains the string 'AcceptAllTrackedChanges', the script reuses the file without overwriting it. On a multi-user system, a local attacker can pre-create this file with malicious StarBasic commands that will execute under the user's privileges when accept_changes.py runs.
Fix: Create a fresh per-run directory using tempfile.TemporaryDirectory() instead of a fixed path in /tmp.
-
MediumPredictable /tmp path for LD_PRELOAD shared library shim
skills/docx/scripts/office/soffice.py:50
When Unix domain sockets are blocked by a sandbox, soffice.py compiles a C shim to a fixed path (/tmp/lo_socket_shim.so) and injects it into LibreOffice via LD_PRELOAD. Because _ensure_shim() reuses the file if it already exists without checking ownership, another user on a shared environment could pre-plant a malicious /tmp/lo_socket_shim.so to run arbitrary code.
Fix: Compile and store the shared library inside a secure, user-owned temporary directory created with tempfile.mkdtemp().
-
MediumPost-extraction symlink deletion does not prevent symlink traversal during unzip
The skill instructions direct the agent to run 'unzip -q doc.docx -d unpacked/' on untrusted documents and only afterwards delete symlinks with 'find unpacked -type l -delete'. A malicious archive containing a symlink entry followed by a file entry targeting that symlink path can overwrite arbitrary files outside the target directory during extraction before the find command runs.
Fix: Instruct the agent to extract archives using the safe_extract() helper in scripts/office/helpers/__init__.py, which validates paths and rejects symlinks before extracting each entry.
-
LowMissing path containment check in relationship file reference validation
skills/docx/scripts/office/validators/base.py:364
In validate_file_references(), internal relationship Target paths from .rels files are resolved against unpacked_dir using target_path.resolve(), but the validator does not check whether the resolved path stays inside unpacked_dir. A crafted document with relative path traversal (../) in a .rels file can probe the existence of arbitrary files on the host filesystem based on whether a broken reference error is reported.
Fix: Check target_path.is_relative_to(self.unpacked_dir) (or use the opc_target() helper) before checking whether target_path exists.