Back to skill

Security audit

Zhy Markdown2wechat

Security checks for vulnerabilities and agentic risk

Overview

This skill does the advertised Markdown-to-WeChat conversion, but it automatically installs npm packages at runtime and has unsafe file-handling behavior users should review before installing.

Install only if you are comfortable with the skill running npm during conversion and writing/deleting files in the working directory. Prefer a reviewed version that pins dependencies with a lockfile, disables install scripts or installs dependencies during setup, uses a unique OS temp directory, sanitizes generated HTML, and asks before overwriting output files.

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 (4)

T08 · Insecure Dependencies

Warning
Location
README.md:8
Finding
Unpinned installation from mutable remote sources<![CDATA[ ## Vulnerability Details **File Location**: `README.md:8` **Vulnerability Type**: Unpinned remote dependency installation **Risk Level**: Medium ### Vulnerable Code ```bash npx skills add https://github.com/zhylq/yuan-skills --skill zhy-markdown2wechat ``` ### Technical Analysis The documented installation command invokes the `skills` package through `npx` without specifying a package version and installs Skill content from a Git repository without pinning a tag or commit hash. Both sources are mutable. The command executed by `npx` can change when a new package version is published, while the content retrieved from the repository can change when its default branch is updated. Consequently, the code installed by a user may differ from the version covered by this audit. This is a supply-chain weakness rather than evidence that the currently reviewed repository is itself compromised. ### Attack Path 1. An attacker compromises the npm package used by `npx`, the referenced Git repository, or a maintainer account. 2. The attacker publishes a malicious CLI version or modifies the repository's default branch. 3. A user follows the installation command from the README. 4. `npx` retrieves and executes the mutable CLI package. 5. The installer retrieves the modified Skill content, allowing attacker-controlled code or instructions to be installed. ### Impact Assessment Successful exploitation could execute code with the privileges of the user running the installation command or install malicious Skill instructions and scripts for later execution. The affected scope includes files, credentials, network access, and other resources available to that user account. The command does not directly request administrative privileges, so privilege escalation beyond the invoking account is not established. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin the `skills` CLI to a reviewed, exact version instead of allowing `npx` to select a mutable release. - Pin the Git source to an immutable commit hash or signed release tag. - Publish checksums or signatures for released Skill artifacts and verify them during installation. - Document the exact reviewed version and provide a reproducible installation procedure. - Avoid installation commands that immediately execute remotely resolved packages where a verified local installer can be used instead. ]]>

T08 · Insecure Dependencies

Error
Location
scripts/convert.js:11
Finding
Automatic installation and execution of insufficiently pinned npm dependencies<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert.js:11-20, 31-32` **Vulnerability Type**: Runtime supply-chain code execution **Risk Level**: High ### Vulnerable Code ```javascript function installAndRequire(packageName, moduleName, tempDir) { try { return require(moduleName); } catch (e) { console.log(`Installing ${packageName} temporarily...`); cp.execSync(`npm install ${packageName} --no-save`, { cwd: tempDir, stdio: 'ignore' }); return require(path.join(tempDir, 'node_modules', moduleName)); } } ``` ```javascript const marked = installAndRequire('marked@4', 'marked', tempDir); const juice = installAndRequire('juice@8', 'juice', tempDir); ``` ### Technical Analysis When a dependency cannot be loaded globally or through Node.js module resolution, the converter automatically runs `npm install`. The dependency specifications `marked@4` and `juice@8` constrain only the major version and therefore permit future minor and patch releases. No lockfile, exact transitive dependency graph, integrity hash, or artifact signature is used. Npm installation can execute lifecycle scripts supplied by packages or their transitive dependencies. The command does not use `--ignore-scripts`, so compromise of an allowed dependency release can lead to code execution before the converter calls `require()`. The hardcoded package names prevent direct command injection through `packageName` in the current code path. The confirmed issue is unsafe runtime dependency retrieval and execution, not attacker-controlled shell interpolation. ### Attack Path 1. The converter runs in an environment where `marked` or `juice` is not already resolvable. 2. The fallback installation function invokes `npm install`. 3. Npm resolves a mutable release matching `marked@4` or `juice@8`, along with mutable transitive dependencies. 4. A compromised package or dependency supplies malicious package code or an npm lifecycle script. 5. Th ...[truncated 669 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Declare dependencies in a committed `package.json` and pin exact versions. - Commit a lockfile containing resolved versions and integrity metadata. - Install through a reproducible workflow such as `npm ci` before running the converter rather than installing dependencies automatically at runtime. - Disable lifecycle scripts with `--ignore-scripts` when dependency functionality does not require them. - Verify package provenance, checksums, and signatures where supported. - Regularly audit direct and transitive dependencies and update them through a reviewed change process. - If zero-install behavior is required, vendor reviewed dependency code or distribute a signed, reproducible bundle instead of resolving packages from the network during conversion. - Report installation failures explicitly and exit with a nonzero status rather than silently changing the runtime environment. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/convert.js:35
Finding
Unsanitized Markdown permits active content in generated HTML<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert.js:35-45` **Vulnerability Type**: HTML injection and script execution **Risk Level**: High ### Vulnerable Code ```javascript console.log('Reading files...'); const md = fs.readFileSync(path.resolve(mdPath), 'utf8'); const css = fs.readFileSync(path.resolve(cssPath), 'utf8'); console.log('Converting Markdown to HTML...'); const html = marked.parse(md); const wrapped = '<section id="MdWechat">' + html + '</section>'; console.log('Inlining CSS...'); const finalHtml = juice.inlineContent(wrapped, css); fs.writeFileSync(path.resolve(outPath), finalHtml); ``` ### Technical Analysis The converter parses potentially untrusted Markdown and writes the resulting HTML directly to disk. There is no HTML sanitization step between `marked.parse()` and output generation. Markdown processors can preserve raw HTML embedded in Markdown. Depending on the supplied input and browser behavior, the generated document may contain script elements, event-handler attributes, unsafe URL schemes, forms, frames, or other active content. CSS inlining performed by `juice.inlineContent()` is not a security sanitizer and must not be relied upon to remove executable HTML. The Skill documentation instructs users to open the generated file in a browser for preview, creating a concrete execution context for injected active content. ### Attack Path 1. An attacker prepares a Markdown file containing malicious raw HTML or unsafe attributes, for example an image with an error event handler or a link using an unsafe URL scheme. 2. The victim asks the Skill to convert that Markdown file. 3. `marked.parse()` preserves or translates the malicious content into HTML. 4. The converter wraps and writes the content without applying an allowlist sanitizer. 5. The victim opens the generated HTML file for preview. 6. Active content executes in the victim's browser context or deceives the victim into performing an unsafe action. ### ...[truncated 500 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Sanitize the generated HTML with a maintained allowlist-based HTML sanitizer before writing it to disk. - Define a minimal set of elements and attributes required by the WeChat editor. - Remove script elements, event-handler attributes, forms, frames, embedded objects, and dangerous metadata. - Validate URL-bearing attributes and allow only explicitly approved schemes such as `https`, with carefully justified support for any alternatives. - Remove or rewrite `javascript:`, unsafe `data:`, and other executable URL schemes. - Consider configuring the Markdown parser to reject or escape raw HTML if raw HTML is not required. - Add automated tests covering script tags, event handlers, SVG-based payloads, unsafe links, malformed markup, and encoded bypass attempts. - Treat Markdown and CSS supplied by users as untrusted input and document the sanitization guarantees. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/convert.js:24
Finding
Predictable temporary directory can cause deletion of pre-existing data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert.js:24-28, 50-53` **Vulnerability Type**: Unsafe temporary-directory handling and recursive deletion **Risk Level**: High ### Vulnerable Code ```javascript const tempDir = path.join(process.cwd(), '.wechat-temp'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir); fs.writeFileSync(path.join(tempDir, 'package.json'), '{"name":"temp","private":true}'); } ``` ```javascript } finally { try { if (fs.existsSync(tempDir)) { fs.rmSync(tempDir, { recursive: true, force: true }); } } catch (e) { } } ``` ### Technical Analysis The temporary directory uses a fixed, predictable path under the current working directory. If `.wechat-temp` already exists, the script does not create it and does not verify its ownership, type, contents, or whether it was created by the current process. Nevertheless, the `finally` block recursively deletes that path unconditionally. This creates a direct data-loss condition when a legitimate pre-existing directory has that name. It also permits a local attacker who can modify the working directory to prepare the path and influence the environment used for dependency installation. The empty cleanup catch block suppresses deletion errors, reducing visibility into unsafe or incomplete cleanup. The implementation also lacks protections against filesystem race conditions between existence checks and subsequent use or deletion. ### Attack Path 1. A user or another application has a directory named `.wechat-temp` in the converter's working directory, or a local attacker creates one there. 2. The converter detects that the path already exists and therefore does not initialize a fresh private temporary directory. 3. Dependency installation may use the attacker-prepared directory and its existing npm-related files. 4. Regardless of whether conversion succeeds, the `finally` block recursively removes `.wechat-temp`. 5. All files rea ...[truncated 615 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create a unique temporary directory with `fs.mkdtempSync()` under `os.tmpdir()`. - Record whether the current process successfully created the directory and only remove that exact directory. - Never recursively delete a predictable path that may predate the current process. - Use `fs.lstatSync()` and appropriate filesystem checks to reject unexpected path types. - Avoid check-then-act patterns where possible, and account for symlink and replacement races. - Apply restrictive permissions to temporary directories. - Keep dependency installation isolated from attacker-controlled project directories. - Do not suppress cleanup errors silently; report them without masking the original conversion failure. - Add regression tests confirming that pre-existing directories and files are never deleted. ]]>
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (13)

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The helper dynamically installs packages at runtime by invoking npm through a shell command. This creates unnecessary code execution and supply-chain exposure for a task that is supposed to be a local format conversion, and it can also alter the host environment in ways the user did not explicitly approve.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
Running npm install during conversion introduces network access and remote package retrieval unrelated to the core document-processing function. That expands the trust boundary from local files to the npm ecosystem at execution time, enabling supply-chain compromise or unexpected script execution if a dependency or registry path is abused.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill explicitly instructs the agent to directly execute a local Node.js script that reads a user-specified Markdown file and writes a user-specified HTML output file, but it provides no safety checks, path restrictions, confirmation step, or warning that filesystem state will be modified. In an agent setting, this creates a real risk of unauthorized file creation or overwriting, especially if a user supplies sensitive paths or if the agent executes commands automatically without adequate sandboxing.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The subprocess invocation is not disclosed to the user even though it may trigger shell execution, package download, and install-time scripts. In a skill whose stated purpose is markdown conversion, this mismatch makes the behavior more dangerous because users are less likely to anticipate or sandbox it appropriately.

Description-Behavior Mismatch

Medium
Confidence
86% confidence
Finding
The script creates a temporary npm project in the current working directory and mutates local state despite being presented as a simple converter. While not as severe as arbitrary command execution, this hidden filesystem modification can violate user expectations, interfere with surrounding workflows, and serves as enabling behavior for the later package installation.

Missing User Warnings

Low
Confidence
88% confidence
Finding
For markdown files, missing-warning findings apply when the description omits warnings about behaviors that could affect user data or system integrity. The README states that the skill reads Markdown and outputs an HTML file, but it does not explicitly disclose that this involves file creation/writes or where the file will be written.

Natural-Language Policy Violations

Low
Confidence
73% confidence
Finding
The operational instructions and prescribed user-facing output are entirely in Chinese, including a fixed Chinese response template, which implies a language constraint. The file does not state that Chinese is optional, user-selected, or required for a documented region-specific reason.

Natural-Language Policy Violations

Low
Confidence
83% confidence
Finding
The stylesheet inserts the English word "Reference" via the `content` property, which forces a specific language in the rendered UI. This can violate locale/language policy when users have not opted into English or when the skill is expected to be language-neutral.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
This file is a code/config-like asset, so SQP-3 applies. The font-family includes "Microsoft YaHei" and PingFangSC-Regular, which encode a specific language/locale preference in rendering behavior, but the file provides no user opt-in or documented region-specific justification.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
This stylesheet hard-codes the generated label content as "Reference", which imposes a specific language in rendered output. Under the language/locale policy, forcing a locale-specific term without opt-in or documented justification is a natural-language policy concern.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
This CSS file contains natural-language comments such as '方案二' and 'Notion 优雅手帐风' only in Chinese, and there is no indication that the skill is intentionally region-specific or that users can opt into this locale. Under the language/locale policy rule, forcing a specific language without user choice can be a policy concern even when it appears in comments or descriptive text.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
This CSS file contains natural-language comments in Chinese, including the theme description and section labels, with no indication that the skill is region-specific or that users may choose another language. Under the policy, forcing a specific language without opt-in is a locale/language policy concern.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/convert.js:19