Back to skill

Security audit

Markdown To Word Skill

Security checks for vulnerabilities and agentic risk

Overview

This Markdown-to-Word skill is mostly purpose-aligned, but it needs Review because Markdown image paths can cause local image files to be embedded and its shared temporary-file cleanup is under-scoped.

Use this skill only for Markdown files you trust, or run conversion in a sandbox with access only to the intended input, image, and output folders. Be careful with image references, confirm output paths before batch conversion, install dependencies in a virtual environment, and avoid sudo unless you deliberately need system Python packages.

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/md2docx_with_images.py:207
Finding
Arbitrary Local File Inclusion Through Markdown Image References<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md2docx_with_images.py`, lines 207-241 **Vulnerability Type**: Unrestricted local file access and path traversal **Risk Level**: High ### Vulnerable Code ```python src = element.get('src', '') alt = element.get('alt', '图片') if not src: return # 查找图片 image_path = None if self.image_processor: image_path = self.image_processor.find_image(src) # 如果没找到,尝试其他方法 if not image_path and image_dir: # 相对于图片目录 potential_path = os.path.join(image_dir, src) if os.path.exists(potential_path): image_path = potential_path if not image_path: # 直接使用路径 image_path = src # 检查图片文件是否存在 if not os.path.exists(image_path): if self.debug: print(f"⚠️ 图片文件不存在: {image_path}") # 添加替代文本 p = self.doc.add_paragraph(f"[图片: {alt}]") p.alignment = WD_ALIGN_PARAGRAPH.CENTER return try: # 处理图片(调整大小等) processed_path = image_path image_data = {'original_path': image_path} if self.image_processor: processed_path, image_data = self.image_processor.process_image( image_path, max_width=1200, max_height=800, quality=85 ) self.image_info.append(image_data) # 添加图片到文档 from docx.shared import Inches self.doc.add_picture(processed_path, width=Inches(5.0)) ``` ### Technical Analysis The image source is derived directly from attacker-controlled Markdown. The implementation accepts absolute paths and paths containing parent-directory components such as `../`. It checks only whether the path exists; it does not canonicalize the path, reject absolute paths, or verify that the resolved file remains inside an approved image directory. If no image is found through the configured search logic, the code assigns the untrusted source directly to `image_path`. Any readable file that the DOCX library recognizes as an image can consequently be embedded in the generated document. ### Attack ...[truncated 1218 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject absolute image paths supplied by Markdown. 2. Resolve the configured image directory and candidate path with `os.path.realpath()`. 3. Verify containment with `os.path.commonpath()` before opening the file. 4. Reject paths containing traversal components or resolving through symlinks outside the approved directory. 5. Permit only explicitly supported image extensions and validate actual file content. 6. Run conversion under a dedicated, least-privileged account with access only to required input and output directories. Example containment check: ```python base_dir = os.path.realpath(image_dir) candidate = os.path.realpath(os.path.join(base_dir, src)) if os.path.isabs(src): raise ValueError("Absolute image paths are not permitted") if os.path.commonpath([base_dir, candidate]) != base_dir: raise ValueError("Image path escapes the approved image directory") if not os.path.isfile(candidate): raise FileNotFoundError(candidate) ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/md2docx_with_images.py:156
Finding
Predictable Shared Temporary Files and Overbroad Wildcard Cleanup<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md2docx_with_images.py`, lines 156-185 **Vulnerability Type**: Unsafe temporary-file handling and indiscriminate file deletion **Risk Level**: Medium ### Vulnerable Code ```python def _get_temp_image_path(self, original_path: str) -> str: """获取临时图片路径""" import tempfile import hashlib # 生成唯一文件名 file_hash = hashlib.md5(original_path.encode()).hexdigest()[:8] ext = os.path.splitext(original_path)[1] temp_dir = tempfile.gettempdir() temp_filename = f"md2docx_{file_hash}{ext}" return os.path.join(temp_dir, temp_filename) def cleanup_temp_files(self): """清理临时文件""" import tempfile import glob temp_dir = tempfile.gettempdir() temp_pattern = os.path.join(temp_dir, "md2docx_*") temp_files = glob.glob(temp_pattern) for temp_file in temp_files: try: os.remove(temp_file) if self.debug: print(f"🧹 清理临时文件: {temp_file}") except: pass ``` ### Technical Analysis Temporary output names are deterministically generated from the original path using only the first eight hexadecimal characters of an MD5 digest. Files are placed directly in the operating system's shared temporary directory without exclusive creation or a private per-process directory. A local attacker who can write to the shared temporary directory may predict or pre-create the destination. Depending on platform behavior and the image library's save semantics, this can create a symlink or file-replacement race that redirects writes or corrupts another conversion. The cleanup routine separately uses the global pattern `md2docx_*` and deletes every matching entry. It does not track file ownership or limit deletion to files created by the current converter instance. Concurrent conversions can therefore delete one another's temporary files, and an attacker can deliberately create matching files to trigger unintended de ...[truncated 1434 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a private temporary directory for each conversion using `tempfile.TemporaryDirectory()`. 2. Generate unpredictable names using `tempfile.mkstemp()` or `NamedTemporaryFile`. 3. Create files atomically and avoid reopening predictable paths. 4. Maintain an explicit collection of temporary files created by the current conversion. 5. Remove only the private temporary directory or explicitly tracked files. 6. Do not use a shared wildcard such as `/tmp/md2docx_*` for cleanup. 7. Use restrictive permissions on temporary directories and files. Example approach: ```python import os import tempfile with tempfile.TemporaryDirectory(prefix="md2docx_") as temp_dir: fd, output_path = tempfile.mkstemp( suffix=os.path.splitext(original_path)[1], dir=temp_dir ) os.close(fd) # Save the processed image to output_path. # The context manager removes only this conversion's private directory. ``` ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:62
Finding
Unpinned Third-Party Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 62; repeated at lines 318, 345, and 434 **Vulnerability Type**: Uncontrolled third-party dependency resolution **Risk Level**: Medium ### Vulnerable Code ```bash pip install python-docx markdown beautifulsoup4 pillow ``` The documentation also contains variants of the same installation pattern: ```bash pip install -q python-docx markdown beautifulsoup4 pillow ``` ```bash pip install python-docx ``` ### Technical Analysis The documented installation process resolves and executes the latest versions available from the configured Python package index. No exact versions, lock file, package hashes, or trusted-index restrictions are specified. The package names appear to be legitimate PyPI packages; there is no evidence in the audited artifact that they are intentionally malicious or typosquatted. Nevertheless, mutable dependency resolution means the code installed in the future may differ from the code originally reviewed. A compromised upstream release, maintainer account, transitive dependency, or package index could introduce malicious installation or runtime behavior. ### Attack Path 1. A listed package, one of its transitive dependencies, or its distribution account is compromised. 2. A malicious or vulnerable release becomes the version selected by `pip`. 3. A user follows the documented installation command without version or hash constraints. 4. `pip` downloads and installs the changed package. 5. Package-controlled code executes during installation or when the converter imports and uses the dependency. The attack depends on an upstream supply-chain compromise or unsafe package-index configuration; the project itself does not retrieve a known malicious package. ### Impact Assessment A compromised dependency would execute with the privileges of the user running installation or conversion. This could affect files, credentials, environment variables, and network resources avail ...[truncated 199 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to a reviewed version. 2. Generate and commit a lock file that includes transitive dependencies. 3. Require package hashes, for example through a hash-locked requirements file and `pip install --require-hashes`. 4. Install only from an explicitly approved package index. 5. Use automated vulnerability and update monitoring. 6. Review dependency changes before updating the lock file. 7. Install into an isolated virtual environment under a non-privileged account. Example: ```text python-docx==<reviewed-version> --hash=sha256:<reviewed-hash> Markdown==<reviewed-version> --hash=sha256:<reviewed-hash> beautifulsoup4==<reviewed-version> --hash=sha256:<reviewed-hash> Pillow==<reviewed-version> --hash=sha256:<reviewed-hash> ``` Install with: ```bash python -m pip install --require-hashes -r requirements.txt ``` ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# 解码base64图片
echo "🖼️  解码测试图片..."
base64 -d test_images/sample.jpg.base64 > test_images/sample.jpg
rm test_images/sample.jpg.base64

# 创建配置文件
echo "⚙️  创建配置文件..."
Confidence
95% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Vague Triggers

Medium
Confidence
93% confidence
Finding
L006-L007 仅说明“当用户需要将Markdown文件转换为Word文档时激活此技能”,这是功能描述式触发条件,而不是明确、受限的调用条件。文档没有提供具体触发短语、适用上下文或不应触发的负面示例,容易导致在泛泛提到转换需求时被误调用。

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
### 系统依赖(可选)
```bash
# Ubuntu/Debian
sudo apt-get install python3-pip python3-venv

# macOS
brew install python3
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
L074-L094 展示了多个将 Markdown 转换为 .docx 的命令用法,本质上会在指定路径创建或覆盖输出文件。虽然这是技能的核心用途,但当前说明缺少显式的用户提示,例如输出文件会写入磁盘、批量模式会在输出目录生成多个文件,以及应避免指向重要现有文件。

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The shell script uses Chinese comments, status messages, and generated template/config text throughout, including Chinese style labels and instructions. This enforces a specific language/locale for users without any opt-in mechanism or indication that the skill is intentionally limited to a Chinese-language context.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The function docstring and CLI indicate that callers can control max_width, max_height, image_quality, and add_captions. However, _add_image hard-codes max_width=1200, max_height=800, quality=85 at L238-L243, and always adds captions when alt exists at L250-L253, so the documented intent contradicts actual behavior.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
全文以中文编写,并以中文定义技能名称、说明、用法和输出示例,但没有说明该技能仅面向中文用户,或提供其他语言选项。若组织要求技能不应在未获用户选择时强制特定语言,这种单一语言呈现可能构成语言/locale 政策问题。

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
This code emits user-facing status, error, and CLI help text entirely in Chinese, including prints and argparse descriptions. Under the language/locale policy rule, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is explicitly justified.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
This code file contains natural-language strings and docstrings exclusively in Chinese, beginning with the module docstring and continuing through CLI help text. Under the policy, forcing a specific language without opt-in can be a locale-policy violation when no alternative language choice or justification is provided.

Missing User Warnings

Low
Confidence
80% confidence
Finding
This code ensures the output directory exists and then invokes a conversion routine that writes to the specified output path. Although file output is part of the tool's purpose, this file contains no confirmation prompt, visible logging, or explanatory comment/docstring warning the user that it will create directories and write files.

Static analysis

No suspicious patterns detected.