Back to skill

Security audit

Office To Md V2

Security checks for vulnerabilities and agentic risk

Overview

This document-conversion skill has a coherent purpose, but its PPTX path can run shell commands from crafted filenames and may install Python packages automatically during conversion.

Review before installing. Use this only in a constrained workspace, avoid converting untrusted PPTX files, do not run it with privileged accounts, install dependencies explicitly in a controlled environment, and check for existing .md files before conversion. Treat preview output as document content and avoid using it on confidential files unless logs and agent responses are controlled.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
office-to-md/utils/pptConverter.js:65
Finding
Shell Command Injection Through a Crafted PPTX File Path<![CDATA[ ## Vulnerability Details **File Location**: `office-to-md/utils/pptConverter.js:65-69` and `office-to-md/utils/pptConverter.js:80-83` **Vulnerability Type**: OS command injection through shell-string interpolation **Risk Level**: High ### Vulnerable Code ```javascript // Execute Python script const result = execSync(`python3 "${tempScriptPath}" "${filePath}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); ``` The fallback conversion path contains the same vulnerability: ```javascript // Alternative: use unzip to extract XML and parse try { const tempDir = `/tmp/pptx_${Date.now()}`; execSync(`unzip -q "${filePath}" -d "${tempDir}"`, { stdio: 'pipe' }); ``` ### Technical Analysis `filePath` originates from the command-line argument supplied to the converter. It is inserted directly into command strings passed to `child_process.execSync()`. By default, `execSync()` executes the supplied string through a system shell. Enclosing the value in double quotes does not provide adequate shell escaping. A filename containing a double quote can terminate the quoted argument, after which shell metacharacters can introduce an additional command. The file only needs to exist and retain a `.pptx` extension to pass the validation in `openclaw-skill.js`. Both the primary Python conversion command and the `unzip` fallback are affected. Therefore, exploitation does not depend solely on the fallback path. ### Attack Path 1. An attacker creates or uploads an existing file whose name contains a double quote and shell control characters while still ending in `.pptx`. 2. The attacker causes the Agent or another user to invoke the skill with that file path. 3. `openclaw-skill.js` resolves the path, confirms that the file exists, and accepts its `.pptx` extension. 4. `pptConverter.js` embeds the attacker-controlled path into an `execSync()` shell command. 5. The embedded quote terminates the intended argument, and the shell interprets the remaining fi ...[truncated 572 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not construct shell commands by interpolating file paths. Invoke executables directly with argument arrays: ```javascript const { execFileSync } = require('child_process'); const result = execFileSync( 'python3', [tempScriptPath, filePath], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } ); execFileSync( 'unzip', ['-q', filePath, '-d', tempDir], { stdio: ['ignore', 'pipe', 'pipe'] } ); ``` Additional hardening measures: 1. Replace `execSync()` with `execFileSync()` or `spawnSync()` wherever arguments can contain external input. 2. Never attempt to implement manual shell escaping as the primary defense. 3. Replace the shell-based cleanup command with: ```javascript fs.rmSync(tempDir, { recursive: true, force: true }); ``` 4. Confirm that the input is a regular file using `fs.statSync()` or `fs.lstatSync()`. 5. Consider validating the file signature rather than relying only on its extension. 6. Run document conversion in a restricted, non-privileged sandbox with filesystem and process limits. ]]>

T08 · Insecure Dependencies

Warning
Location
office-to-md/utils/pptConverter.js:53
Finding
Automatic Installation of an Unpinned Python Dependency During Conversion<![CDATA[ ## Vulnerability Details **File Location**: `office-to-md/utils/pptConverter.js:53-62` **Vulnerability Type**: Unsafe runtime dependency installation and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```javascript // Try to install python-pptx if not available try { execSync('python3 -c "import pptx"', { stdio: 'pipe' }); } catch (error) { console.warn('python-pptx not installed. Trying to install...'); try { execSync('pip3 install python-pptx', { stdio: 'pipe' }); } catch (installError) { throw new Error('Failed to install python-pptx. Please install manually: pip3 install python-pptx'); } } ``` ### Technical Analysis PPTX conversion automatically invokes `pip3 install python-pptx` when the `pptx` module is unavailable. The installation is performed without: - An exact version pin - Package hashes - A lock file - An isolated virtual environment - Explicit user approval - A controlled package-index configuration This causes ordinary document conversion to download mutable remote content and modify the current Python environment. Package resolution also inherits ambient pip configuration, including alternate or compromised indexes. Depending on the selected distribution format, package installation may execute build-system or setup logic with the privileges of the converter process. The behavior is not necessary for document conversion at runtime. Dependency provisioning should be an explicit, controlled installation step. ### Attack Path 1. A user invokes PPTX conversion on a system where the `pptx` Python module is absent or cannot be imported. 2. The failed import enters the automatic installation branch. 3. The skill executes `pip3 install python-pptx`. 4. Pip contacts its configured package index and resolves an unpinned package version and transitive dependencies. 5. Retrieved package or build logic executes during installation under the skill process's account. 6. A compromised index, malicious pip co ...[truncated 724 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove automatic installation from the conversion path. If the module is unavailable, return a prerequisite error: ```javascript try { execFileSync('python3', ['-c', 'import pptx'], { stdio: 'pipe' }); } catch { throw new Error( 'python-pptx is required. Install the audited dependency during setup.' ); } ``` Provision Python dependencies during an explicit setup or deployment phase: 1. Pin an audited version in a requirements file: ```text python-pptx==<reviewed-version> --hash=sha256:<reviewed-hash> ``` 2. Include hashes for all transitive dependencies and install with: ```bash python3 -m pip install --require-hashes -r requirements.txt ``` 3. Use a dedicated virtual environment owned by the application. 4. Configure an approved package index or internal mirror. 5. Require explicit administrative or user approval for dependency installation. 6. Run conversion without package-management privileges and, where practical, without outbound network access. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
office-to-md/utils/pptConverter.js:49
Finding
Predictable Shared Temporary Script Enables Symlink and Race-Condition Attacks<![CDATA[ ## Vulnerability Details **File Location**: `office-to-md/utils/pptConverter.js:49-51` and `office-to-md/utils/pptConverter.js:71-72` **Vulnerability Type**: Insecure temporary-file creation **Risk Level**: Medium ### Vulnerable Code ```javascript // Write Python script to temporary file const tempScriptPath = '/tmp/extract_pptx.py'; fs.writeFileSync(tempScriptPath, pythonScript); ``` The same shared path is later removed: ```javascript // Clean up fs.unlinkSync(tempScriptPath); ``` ### Technical Analysis Every invocation uses the fixed path `/tmp/extract_pptx.py`. The file is created with `writeFileSync()` without exclusive creation, ownership verification, a private containing directory, or protection against symbolic links. On a multi-user system, an attacker can pre-create that predictable path as a symbolic link to another file writable by the victim. `writeFileSync()` follows the link and overwrites the target with the generated Python source. The fixed path also permits race conditions: another process can replace or modify the script between its creation and execution, potentially causing attacker-controlled Python code to run under the victim's account. Concurrent legitimate conversions can also overwrite and delete one another's temporary script, resulting in unreliable output or denial of service. ### Attack Path #### Symlink overwrite path 1. A local attacker predicts the fixed temporary path `/tmp/extract_pptx.py`. 2. Before conversion starts, the attacker creates that path as a symbolic link to a target file writable by the skill's account. 3. The victim invokes PPTX conversion. 4. `fs.writeFileSync()` follows the symbolic link and overwrites the target with the generated Python script. 5. The target file is corrupted or, depending on its purpose, may later influence application behavior. #### Script replacement path 1. The attacker monitors the predictable temporary path. 2. The skill writes the Python script to `/tmp/extrac ...[truncated 732 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Create a unique, private temporary directory for every conversion and clean it up in a `finally` block: ```javascript const os = require('os'); const fs = require('fs'); const path = require('path'); const { execFileSync } = require('child_process'); const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'office-to-md-')); const tempScriptPath = path.join(tempDir, 'extract_pptx.py'); try { fs.writeFileSync(tempScriptPath, pythonScript, { encoding: 'utf8', mode: 0o600, flag: 'wx' }); return execFileSync('python3', [tempScriptPath, filePath], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); } finally { fs.rmSync(tempDir, { recursive: true, force: true }); } ``` Additional hardening measures: 1. Use `fs.mkdtempSync()` or `fs.promises.mkdtemp()` instead of timestamp-only or fixed names. 2. Set restrictive permissions on temporary directories and files. 3. Use exclusive creation mode (`flag: 'wx'`) to prevent overwriting an existing path. 4. Avoid shared temporary files across requests or processes. 5. Perform cleanup in `finally` so failures do not leave executable artifacts behind. 6. For stronger isolation, avoid writing a script at all by shipping a reviewed helper script with the package or passing controlled code through standard input to a directly spawned interpreter. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (16)

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The documentation describes conversion behavior but does not clearly warn that the process writes a new .md file adjacent to the source path and may overwrite an existing Markdown file with the same basename. In an agent or automation context, this can cause unintended file modification or data loss because users may treat the operation as read-only parsing rather than a write operation.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill reads full document contents and several examples encourage printing previews, statistics, and excerpts to logs or console output, but the documentation does not include a privacy warning. In environments handling sensitive documents, this increases the risk of accidental disclosure through logs, previews, agent responses, or persisted debug output.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The module invokes external commands and generates/executess a temporary Python script to process input files, which expands the attack surface beyond ordinary in-process parsing. While the immediate command strings quote the file path, this design still relies on shell/process execution, ambient system tools, and a world-accessible temporary location, making the behavior more dangerous in an agent skill handling untrusted files.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
This converter installs a Python package on the host at runtime using pip, which gives a file-conversion utility unnecessary software-management capability and changes the execution environment without user approval. In a shared or privileged environment, this can pull unpinned code from package indexes, enable dependency confusion or malicious package delivery, and create persistence or supply-chain risk well beyond document parsing.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Installing packages through a shell command without explicit user confirmation or policy checks is unsafe for a document conversion helper. It can unexpectedly mutate the host, download and execute third-party code during normal file handling, and violate least-privilege assumptions for users who only intended content extraction.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The code executes shell commands on user-supplied files during conversion, including running a generated Python script and, on fallback, unzip against the provided path. Even though the path is quoted, the behavior is not clearly disclosed and exposes the host to parser/tool vulnerabilities, denial-of-service conditions, and broader system interaction than users would expect from simple Markdown conversion.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The integration test accesses a hard-coded document under /root/clawd/downloads, causing the test script to read from a sensitive host-specific location outside its local test fixtures. Even though it first checks for existence, this still expands the skill's file-access scope and may expose private document contents in logs or downstream processing when the test is run in a privileged environment.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The usage example states `Output file: /path/to/document.md`, which indicates the skill creates a file on disk. The README does not provide any explicit user warning that running the skill will write output files into the source document's directory, which is a data-affecting behavior relevant for markdown-file warning requirements.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"author": "",
  "license": "ISC",
  "dependencies": {
    "mammoth": "^1.6.0",
    "office-text-extractor": "^2.0.0",
    "pdf-parse": "^1.1.1",
    "turndown": "^7.1.2",
Confidence
96% confidence
Finding
The dependency is specified with a caret range, which allows newer compatible releases to be installed than the one originally tested. This increases supply-chain risk and can also cause accidental uptake of a vulnerable or breaking release, especially for a document-conversion skill that processes untrusted files.

Unverifiable Dependency: mammoth has 1 known advisory(ies) (CVE-2025-11849 (Mammoth is vulnerable to Directory Traversal)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding
The manifest does not pin mammoth, and mammoth has a known directory traversal advisory affecting some versions. In a skill designed to convert user-supplied office documents, a vulnerable parser is more dangerous because attackers may be able to craft malicious files that read unintended files or escape expected paths during processing.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"license": "ISC",
  "dependencies": {
    "mammoth": "^1.6.0",
    "office-text-extractor": "^2.0.0",
    "pdf-parse": "^1.1.1",
    "turndown": "^7.1.2",
    "word-extractor": "^1.0.4"
Confidence
96% confidence
Finding
The dependency is specified with a caret range, so installs are not fully reproducible and may pull in different versions over time. For a package that extracts text from office documents, this creates avoidable supply-chain exposure if a later release introduces malicious code or a security regression.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "mammoth": "^1.6.0",
    "office-text-extractor": "^2.0.0",
    "pdf-parse": "^1.1.1",
    "turndown": "^7.1.2",
    "word-extractor": "^1.0.4"
  }
Confidence
96% confidence
Finding
Using a ranged version for pdf-parse means the installed code may vary between environments or over time. Because PDF parsing libraries handle complex untrusted input, silently absorbing dependency updates can increase exposure to newly introduced vulnerabilities or unexpected behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"mammoth": "^1.6.0",
    "office-text-extractor": "^2.0.0",
    "pdf-parse": "^1.1.1",
    "turndown": "^7.1.2",
    "word-extractor": "^1.0.4"
  }
}
Confidence
95% confidence
Finding
The turndown dependency is not pinned to a single exact version, which weakens build determinism and expands supply-chain risk. While the direct impact is lower than a parser handling binary office files, it still allows unreviewed code changes to enter the runtime during future installs.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"office-text-extractor": "^2.0.0",
    "pdf-parse": "^1.1.1",
    "turndown": "^7.1.2",
    "word-extractor": "^1.0.4"
  }
}
Confidence
96% confidence
Finding
The word-extractor dependency uses a caret range, so future installs may resolve to different versions than the author tested. Since this library processes document content, that nondeterminism increases supply-chain and parser-related security risk when handling untrusted files.

Intent-Code Divergence

Low
Confidence
90% confidence
Finding
The function is named `convertPdfToMd`, suggesting conversion from PDF to Markdown, yet the code only reads the PDF text via `pdf-parse`, removes excessive blank lines, and returns plain text. The inline comments at L8-L10 also acknowledge it is returning text 'as is', which contradicts the stated conversion intent in the function/module naming.

Context-Inappropriate Capability

Low
Confidence
71% confidence
Finding
The script repeatedly spawns a separate process to run another script rather than invoking local functions directly. Since no manifest is available establishing that subprocess execution is part of the skill's scope, this is an unjustified capability from a developer-intent perspective.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
office-to-md/utils/pptConverter.js:12

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/test-converter.js:83