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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
text

### 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
View 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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
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
---

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
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.

Content

Scanner excerpt · package.json (reported line 9)May include surrounding context.

json
"type": "module",
  "main": "main.js",
  "dependencies": {
    "docx": "^9.0.0",
    "unified": "^11.0.0",
    "remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",

Unpinned Dependencies

Low
Category
Supply Chain
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.

Content

Scanner excerpt · package.json (reported line 10)May include surrounding context.

json
"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",

Unpinned Dependencies

Low
Category
Supply Chain
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.

Content

Scanner excerpt · package.json (reported line 11)May include surrounding context.

json
"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"

Unpinned Dependencies

Low
Category
Supply Chain
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.

Content

Scanner excerpt · package.json (reported line 12)May include surrounding context.

json
"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"
  }

Unpinned Dependencies

Low
Category
Supply Chain
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.

Content

Scanner excerpt · package.json (reported line 13)May include surrounding context.

json
"unified": "^11.0.0",
    "remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
    "remark-math": "^6.0.0",
    "node-fetch": "^3.3.2"
  }
}

Unpinned Dependencies

Low
Category
Supply Chain
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.

Content

Scanner excerpt · package.json (reported line 14)May include surrounding context.

json
"remark-parse": "^11.0.0",
    "remark-gfm": "^4.0.0",
    "remark-math": "^6.0.0",
    "node-fetch": "^3.3.2"
  }
}