Back to skill

Security audit

Joe's Markdown to DOCX Converter

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent Markdown-to-DOCX purpose, but image handling can make arbitrary network requests and read files outside the document folder.

Review before installing if you may convert Markdown from other people. Run it only on trusted input or inside a sandbox with limited filesystem and outbound network access, and consider patching it to disable remote images by default, block private/internal network targets, enforce time and size limits, and constrain local image paths to the Markdown document directory. Prefer npm ci and review the third-party registry mirror and dependency versions before use.

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
scripts/convert.js:260
Finding
Unrestricted Remote Image Fetching Enables Server-Side Request Forgery and Denial of Service## Vulnerability Details **File Location**: `scripts/convert.js`, lines 260-264 **Vulnerability Type**: Server-Side Request Forgery (SSRF) and unbounded network resource consumption **Risk Level**: High ```javascript if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { // 网络图片 const response = await fetch(imageUrl); const arrayBuffer = await response.arrayBuffer(); imageBuffer = Buffer.from(arrayBuffer); } ``` ### Technical Analysis An image URL taken directly from the Markdown syntax tree is passed to `fetch()` without security restrictions. Although downloading remote images is part of the declared functionality, the implementation does not: - Restrict destination hosts or ports. - Reject loopback, private, link-local, or cloud metadata addresses. - Validate destinations after DNS resolution. - Validate every HTTP redirect destination. - Enforce a request timeout. - Limit the downloaded response size. - Check the HTTP response status. - Verify that the response is a supported image through its MIME type and file signature. Consequently, an attacker who controls the Markdown input can cause the converter to send requests from the host running the Skill. This exceeds the minimum network privilege needed to retrieve ordinary public images. ### Attack Path 1. An attacker prepares a Markdown document containing an image URL that targets an internal service, a loopback endpoint, a link-local metadata endpoint, or an attacker-controlled redirect. 2. A user invokes the converter on that document. 3. The parser places the attacker-controlled URL in `node.url`. 4. `convertImage()` passes the URL directly to `fetch()`. 5. The request originates from the converter host and can reach resources unavailable to the attacker. 6. The response is fully buffered in memory and passed to DOCX generation. Depending on the content and library behavior, this can expose returned data through th ...[truncated 686 chars]
Remediation
## Remediation Suggestions - Permit remote retrieval only when explicitly enabled by the user. - Prefer an allowlist of approved HTTPS hosts; reject plain HTTP unless there is a documented requirement. - Resolve hostnames before connecting and reject loopback, private, link-local, multicast, reserved, and cloud metadata address ranges for both IPv4 and IPv6. - Disable redirects or validate the resolved destination of every redirect hop. - Protect against DNS rebinding by connecting only to the validated resolved address while preserving correct TLS hostname verification. - Enforce strict connection and total-request timeouts with cancellation. - Stream responses while applying a small maximum byte limit instead of calling `arrayBuffer()` without bounds. - Require a successful HTTP status and validate both the declared MIME type and image file signature. - Run conversion in a sandbox with restricted outbound networking when processing untrusted Markdown.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/convert.js:270
Finding
Markdown Image Paths Permit Reads Outside the Input Document Directory## Vulnerability Details **File Location**: `scripts/convert.js`, lines 270-282 **Vulnerability Type**: Path traversal and arbitrary local file read **Risk Level**: High ```javascript } else { // 本地文件 const imagePath = path.resolve(baseDir, imageUrl); if (!fs.existsSync(imagePath)) { console.warn(`⚠️ 图片不存在: ${imagePath}`); return new Paragraph({ children: [new TextRun({ text: `[图片: ${altText}]`, italics: true, color: '999999' })], spacing: { before: 200, after: 200 } }); } imageBuffer = fs.readFileSync(imagePath); } ``` ### Technical Analysis `path.resolve(baseDir, imageUrl)` normalizes a path but does not confine it to `baseDir`. An absolute path discards the base directory, while traversal components such as `../` can resolve outside it. The resulting path is read without checking that its canonical location remains under an approved image directory. The implementation also does not reject symbolic-link escapes, non-regular files, oversized files, or content that is not a supported image. Local image support only requires access to explicitly approved files associated with the Markdown document; unrestricted access to every file readable by the process exceeds that requirement. ### Attack Path 1. An attacker creates Markdown containing an image reference with an absolute path or traversal sequence, such as a path beginning with `../../`. 2. A user runs the converter on the attacker-controlled document. 3. `path.resolve()` produces a path outside the Markdown file's directory. 4. `fs.existsSync()` confirms that the target exists, but performs no authorization or containment check. 5. `fs.readFileSync()` reads the target with the converter process's operating-system privileges. 6. The resulting bytes are passed to `ImageRun` and DOCX generation. If the content is accepted or partially preserved, sensitive data may be exposed in the ...[truncated 790 chars]
Remediation
## Remediation Suggestions - Reject absolute image paths and paths containing traversal outside the approved root. - Canonicalize the approved base directory and target with `fs.realpath()` before reading. - Verify containment using a path-component-safe comparison, for example by checking that `path.relative(canonicalBase, canonicalTarget)` is neither absolute nor begins with `..`. - Reject symbolic links or verify the canonical target after resolving all links. - Require the target to be a regular file. - Enforce a conservative maximum local image size before reading it. - Validate supported image signatures rather than trusting the filename extension. - Consider requiring users to opt in before any local file referenced by untrusted Markdown is accessed. - Run conversion under a minimally privileged account with access limited to the input workspace.

T08 · Insecure Dependencies

Warning
Location
package-lock.json:19
Finding
Dependency Lockfile Retrieves Packages Through an Undisclosed Third-Party Registry Mirror## Vulnerability Details **File Location**: `package-lock.json`, lines 19-23; the same mirror is used throughout the lockfile **Vulnerability Type**: Third-party dependency source and supply-chain exposure **Risk Level**: Medium ```json "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", ``` The associated installation instruction in `SKILL.md`, lines 22-26, is: ```markdown After installing this skill, run: ```bash cd ~/.openclaw/workspace/skills/markdown-to-docx npm install ``` ``` ### Technical Analysis The committed lockfile resolves dependency archives through `registry.npmmirror.com` rather than the default npm registry. Users are instructed to run `npm install` without being told that installation will contact and rely on this additional package-distribution service. The lockfile contains integrity hashes, which provide meaningful protection against archive modification when the expected hash is trustworthy. Nevertheless, use of a third-party mirror expands the supply-chain trust boundary and exposes package installation requests and associated network metadata to that service. The audited content did not show that the lockfile URLs themselves transmit credentials or other sensitive project data. ### Attack Path 1. A user follows the documented installation instructions and runs `npm install`. 2. npm reads the committed lockfile. 3. Package archives are requested from `registry.npmmirror.com`. 4. The third-party service observes installation traffic and participates in dependency delivery. 5. If the mirror, its infrastructure, or the dependency-resolution process is compromised, installation availability may be affected. Malicious replacement is constrained by the recorded integrity ...[truncated 619 chars]
Remediation
## Remediation Suggestions - Regenerate the lockfile using the official `https://registry.npmjs.org/` registry. - Document every non-default registry explicitly if organizational policy requires one. - Use `npm ci` to install exactly the reviewed lockfile contents. - Preserve and verify package integrity hashes. - Use `npm ci --ignore-scripts` where dependency lifecycle scripts are unnecessary and compatibility has been tested. - Pin and periodically review dependency versions and transitive dependencies. - Apply registry allowlisting and dependency caching in controlled build environments. - Treat changes to `resolved` URLs, integrity hashes, and lifecycle scripts as security-sensitive during review.
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Known Vulnerable Dependency: nanoid==5.1.7 — 2 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-73086 (nanoid: Integer Overflow or Wraparound)

High
Category
Supply Chain
Confidence
92% confidence
Finding
The lockfile pins nanoid to 5.1.7, which is flagged by the supplied advisories for denial-of-service style failure modes involving negative sizes and integer wraparound in non-secure generator paths. Even though this package is only a transitive dependency of docx, a vulnerable version remains part of the shipped dependency graph and could be reachable if the library or surrounding code passes attacker-influenced sizes or IDs through affected code paths.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: joe-markdown-to-docx
description: Convert Markdown documents to Word DOCX format with full support for tables, images, code blocks, and formatting. Use when: (1) User asks to convert .md files to .docx or Word format, (2) User needs to generate Word documents from Markdown content, (3) User wants to create professional documents with tables and images from Markdown source. Supports GFM (GitHub Flavored Markdown), local/remote images, table alignment, code syntax highlighting, and preserves all formatting.
author: Joe Cao
---
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly supports downloading remote images over HTTP/HTTPS during document conversion, but the documentation does not warn that processing untrusted Markdown can trigger outbound network requests. This can leak network metadata, enable SSRF-like access to internal resources if arbitrary URLs are allowed, and cause unexpected external data retrieval during what appears to be a local file conversion task.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This code contains multiple natural-language strings such as comments, usage text, status messages, and errors entirely in Chinese. Under the stated policy, forcing a specific language without user opt-in is a language/locale policy violation unless the locale restriction is clearly documented and justified, which is not present here.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The converter will fetch any HTTP(S) URL embedded in untrusted Markdown, which creates a server-side request capability. An attacker can use this to trigger outbound requests to internal services, cloud metadata endpoints, or attacker-controlled hosts, causing SSRF, privacy leakage, and unexpected network access during document conversion.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"type": "module",
  "main": "main.js",
  "dependencies": {
    "docx": "^9.0.0",
    "unified": "^11.0.0",
    "remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
Confidence
92% confidence
Finding
The dependency version uses a caret range, which allows newer compatible releases to be installed over time. This can introduce supply-chain risk because builds may silently consume a newly published version containing a vulnerability or malicious code, reducing reproducibility and reviewability.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"main": "main.js",
  "dependencies": {
    "docx": "^9.0.0",
    "unified": "^11.0.0",
    "remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
    "remark-math": "^6.0.0",
Confidence
92% confidence
Finding
The dependency version uses a caret range, which allows newer compatible releases to be installed over time. This can introduce supply-chain risk because builds may silently consume a newly published version containing a vulnerability or malicious code, reducing reproducibility and reviewability.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "docx": "^9.0.0",
    "unified": "^11.0.0",
    "remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
    "remark-math": "^6.0.0",
    "node-fetch": "^3.3.2"
Confidence
92% confidence
Finding
The dependency version uses a caret range, which allows newer compatible releases to be installed over time. This can introduce supply-chain risk because builds may silently consume a newly published version containing a vulnerability or malicious code, reducing reproducibility and reviewability.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"docx": "^9.0.0",
    "unified": "^11.0.0",
    "remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
    "remark-math": "^6.0.0",
    "node-fetch": "^3.3.2"
  }
Confidence
92% confidence
Finding
The dependency version uses a caret range, which allows newer compatible releases to be installed over time. This can introduce supply-chain risk because builds may silently consume a newly published version containing a vulnerability or malicious code, reducing reproducibility and reviewability.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"unified": "^11.0.0",
    "remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
    "remark-math": "^6.0.0",
    "node-fetch": "^3.3.2"
  }
}
Confidence
92% confidence
Finding
The dependency version uses a caret range, which allows newer compatible releases to be installed over time. This can introduce supply-chain risk because builds may silently consume a newly published version containing a vulnerability or malicious code, reducing reproducibility and reviewability.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
    "remark-math": "^6.0.0",
    "node-fetch": "^3.3.2"
  }
}
Confidence
92% confidence
Finding
The dependency version uses a caret range, which allows newer compatible releases to be installed over time. This can introduce supply-chain risk because builds may silently consume a newly published version containing a vulnerability or malicious code, reducing reproducibility and reviewability.

Static analysis

No suspicious patterns detected.