Back to skill

Security audit

xiaohongshu card generator

Security checks for vulnerabilities and agentic risk

Overview

This is a real local card-rendering skill, but it can execute active MDX/HTML and make under-scoped network requests while rendering user content.

Install only if you will render trusted Markdown/MDX or can sandbox the renderer. Avoid using --mdx-mode or raw HTML from other people, avoid remote-image processing unless egress is contained, and update dependencies before using it in CI or on sensitive projects.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/src/core/markdown.ts:21
Finding
Arbitrary Server-Side JavaScript Execution Through Untrusted MDX Evaluation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/src/core/markdown.ts:21-53`; exposed through `scripts/src/cli.ts:220` **Vulnerability Type**: Untrusted MDX evaluation **Risk Level**: High ### Vulnerable Code ```ts async function mdxToHtml(markdown: string): Promise<string> { const mdxComponentNames = [...markdown.matchAll(/<([A-Z][A-Za-z0-9_]*)\b/g)].map((match) => match[1]); const fallbackComponents: Record<string, React.ComponentType<Record<string, unknown>>> = {}; for (const componentName of mdxComponentNames) { if (fallbackComponents[componentName]) { continue; } fallbackComponents[componentName] = function UnknownMdxComponent(props: Record<string, unknown> = {}) { const { children } = props; return React.createElement( 'div', { 'data-mdx-component': componentName }, children as React.ReactNode ); }; } const module = (await evaluate(markdown, { Fragment, jsx, jsxs, development: false, remarkPlugins: [remarkGfm] })) as { default: React.ComponentType<Record<string, unknown>> }; const Component = module.default; const html = renderToStaticMarkup( React.createElement(Component, { components: fallbackComponents }) ).trim(); return html.length > 0 ? html : '<p><br></p>'; } ``` The feature is exposed as a normal CLI option: ```ts .option('--mdx-mode', 'Enable mdx mode', false) ``` ### Technical Analysis `@mdx-js/mdx` evaluation is not equivalent to parsing Markdown as inert text. MDX supports executable JavaScript expressions and compiles the supplied document into a JavaScript module. Calling `evaluate()` on content from the input file therefore crosses a code-versus-data boundary. The CLI reads a user-selected file and passes its contents directly into this evaluation path when `--mdx-mode` is enabled. There is no trust check, isolation boundary, sandbox, capability restriction, or validation that limits ...[truncated 1361 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not call `evaluate()` on untrusted or externally supplied MDX. 2. Prefer parsing Markdown as data and support only an explicit allowlist of passive formatting constructs. 3. If MDX support is essential, clearly classify it as a trusted-input-only feature and reject its use for downloaded, shared, or otherwise untrusted documents. 4. Run MDX compilation and rendering in a separate, disposable sandbox with: - No inherited environment variables. - No filesystem access except a read-only input and isolated output directory. - No network access. - No process-spawning capability. - Strict CPU, memory, and execution-time limits. 5. Do not rely on JavaScript language-level sandboxes alone as the primary security boundary. Use operating-system or container isolation. 6. Add negative security tests using MDX expressions that attempt to access runtime globals, environment data, the filesystem, and the network. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/src/core/markdown.ts:67
Finding
Raw HTML Injection Leads to Script Execution in the Playwright Rendering Browser<![CDATA[ ## Vulnerability Details **File Location**: `scripts/src/core/markdown.ts:67-72`, `scripts/src/core/template.ts:162-169`, and `scripts/src/core/render.ts:34-47` **Vulnerability Type**: Active HTML injection in a JavaScript-enabled browser **Risk Level**: High ### Vulnerable Code Raw HTML is explicitly accepted and retained during Markdown processing: ```ts const file = await unified() .use(remarkParse) .use(remarkGfm) .use(remarkRehype, { allowDangerousHtml: true }) .use(rehypeRaw) .use(rehypeStringify, { allowDangerousHtml: true }) .process(markdown); ``` The resulting HTML is inserted into the browser document without sanitization: ```ts const normalizedContent = /class=(["'])[^"']*\bmarkdown-body\b[^"']*\1/i.test(contentHtml) ? contentHtml : `<div class="markdown-body">${contentHtml}</div>`; const bodyHtml = theme.bodyTemplate .replaceAll('{{CARD_WIDTH}}', String(width)) .replaceAll('{{CARD_HEIGHT}}', String(height)) .replaceAll('{{MAIN_TITLE}}', mainTitle ? `<div class="main-title">${maybeHtml(mainTitle)}</div>` : '') .replaceAll('{{TITLE}}', title ? `<div class="title">${maybeHtml(title)}</div>` : '') .replaceAll('{{TEXT}}', maybeHtml(normalizedContent)) .replaceAll('{{PAGE_NUM}}', showPager ? pageNum : ''); ``` The assembled document is then loaded into a JavaScript-enabled Chromium page: ```ts const documentHtml = buildCardDocument({ theme: args.theme, contentHtml: args.pages[index], width: args.width, height: args.height, pageNum: `${pageNumber} / ${total}`, showPager: args.showPager, title: index === 0 ? args.title : undefined, mainTitle: index === 0 ? args.mainTitle : undefined }); const outputPath = resolve(args.outputDir, `card_${pageNumber}.png`); await page.setContent(documentHtml, { waitUntil: 'load' }); ``` ### Technical Analysis The Markdown pipeline enables dangerous raw HTML through `allowDangerousHtml` and `rehypeRaw`. No sanitizer is applied after parsing. The generated HTML ...[truncated 1982 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sanitize generated HTML before it enters templates or Playwright. 2. Use a strict element and attribute allowlist suitable for static card content. 3. Remove at minimum: - `script`, `iframe`, `object`, `embed`, and active SVG elements. - All inline event-handler attributes such as `onload` and `onerror`. - `javascript:` and other active URL schemes. - Unnecessary `style`, `link`, and metadata elements. 4. Disable JavaScript in the rendering browser when card generation does not require it: ```ts const context = await browser.newContext({ javaScriptEnabled: false }); ``` 5. Intercept browser requests and deny all network destinations by default. Explicitly allow only resources required by the renderer. 6. Add a restrictive Content Security Policy to generated documents as defense in depth. 7. Add regression tests covering script tags, event handlers, SVG payloads, iframes, remote resources, and active URL schemes. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/src/core/structure.ts:19
Finding
Remote Image Fetching Permits SSRF and Unbounded Response Buffering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/src/core/structure.ts:19-33` and `scripts/src/core/structure.ts:76-87`; enabled through `scripts/src/cli.ts:221` **Vulnerability Type**: Server-side request forgery and resource exhaustion **Risk Level**: Medium ### Vulnerable Code ```ts async function fetchRemoteImageAsDataUrl(url: string): Promise<string> { const response = await fetch(url, { redirect: 'follow', cache: 'force-cache' }); if (!response.ok) { throw new Error(`Failed to fetch image: ${response.status}`); } const arrayBuffer = await response.arrayBuffer(); const contentType = response.headers.get('content-type') || 'image/png'; const base64 = Buffer.from(arrayBuffer).toString('base64'); return `data:${contentType};base64,${base64}`; } ``` ```ts if (options.processRemoteImages && /^https?:\/\//i.test(src)) { try { const dataUrl = await fetchRemoteImageAsDataUrl(src); $image.attr('src', dataUrl); } catch (error) { const message = error instanceof Error ? error.message : String(error); warnings.push(`Remote image fallback to original URL: ${src} (${message})`); } } ``` The behavior is enabled through: ```ts .option('--wechat-mode', 'Enable wechat mode', false) ``` ### Technical Analysis When remote-image processing is enabled, every HTTP or HTTPS image source in the input document is fetched by the Node.js process. The implementation accepts arbitrary hosts, follows redirects, and performs no destination validation. It does not reject loopback, private, link-local, multicast, or cloud metadata address ranges. It also does not revalidate the destination after redirects or DNS resolution. An attacker can therefore supply a URL that makes the renderer request services unavailable to the attacker directly. Additionally, the complete response is loaded into memory using `arrayBuffer()` and then copied again during Base64 conversion. There is no response-size limit, streaming limit, ...[truncated 1829 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable remote image retrieval by default and require an explicit trusted-source policy. 2. Prefer a strict hostname allowlist for remote assets. 3. Resolve destination hostnames before connecting and reject: - Loopback addresses. - RFC 1918 private ranges. - Link-local ranges. - Multicast and reserved ranges. - IPv6 local, unique-local, and mapped private addresses. - Known cloud metadata addresses. 4. Validate the resolved destination again after every redirect. Prefer disabling redirects unless they are required. 5. Require HTTPS for external resources. 6. Apply an abort timeout with `AbortController`. 7. Stream responses while enforcing a strict maximum byte count rather than calling unrestricted `arrayBuffer()`. 8. Validate both the declared MIME type and actual file signature against an allowlist of supported image formats. 9. Do not leave a failed remote URL in HTML that Playwright will subsequently load. Remove it or replace it with a safe local placeholder. 10. Configure Playwright request interception to block loopback, private, link-local, and unauthorized external destinations. 11. Add tests for redirects to private addresses, DNS rebinding conditions, IPv4-mapped IPv6 addresses, oversized responses, and slow responses. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (59)

Known Vulnerable Dependency: undici==7.24.2 — 12 advisory(ies): CVE-2026-6733 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); CVE-2026-13697 (undici vulnerable to cross-user information disclosure and parse-time crash via ); CVE-2026-16728 (undici vulnerable to downstream response desynchronization via retry interceptor) +9 more

High
Category
Supply Chain
Confidence
86% confidence
Finding
The lockfile pins undici 7.24.2 as a transitive dependency of cheerio, and the listed advisories indicate multiple HTTP client flaws including response desynchronization, queue poisoning, and possible cross-user information disclosure. In a skill that may fetch or process remote content before rendering Markdown/HTML into cards, a vulnerable HTTP stack can increase risk if attacker-controlled endpoints are contacted or shared connections are reused.

Unvalidated Output Injection

High
Category
Output Handling
Content
<head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <style dangerouslySetInnerHTML={{ __html: styleText }} />
      </head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: bodyHtml }} />
Confidence
98% confidence
Finding
The code injects raw CSS into a <style> tag via dangerouslySetInnerHTML using styleText with no validation or sanitization. If an attacker can influence this value, they may break out of the style context in some renderers or abuse CSS/HTML injection to alter the rendered page, exfiltrate data in browser contexts, or interfere with downstream screenshot/rendering behavior.

Unvalidated Output Injection

High
Category
Output Handling
Content
<style dangerouslySetInnerHTML={{ __html: styleText }} />
      </head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: bodyHtml }} />
      </body>
    </html>
  );
Confidence
100% confidence
Finding
The code injects bodyHtml directly into the DOM via dangerouslySetInnerHTML without sanitization. In a skill designed to render Markdown/text into shareable cards, this is especially risky because user-controlled content is likely to flow here; malicious HTML can execute scripts or event handlers in browser-like renderers, manipulate output, or attack any privileged rendering environment.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The activation description lists phrases such as "制作卡片" and references users merely mentioning rendering Markdown/text into Xiaohongshu-style cards. These triggers are broad enough to match ordinary conversation and the file does not provide explicit exclusion examples beyond a general non-applicable list, which may lead to unintended invocation.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The template fixes the font stack to include "PingFang SC" and later includes multiple Chinese-language comments and Xiaohongshu-specific theme labels, indicating a China-specific presentation choice. Under the policy, forcing a specific language/locale without offering user choice or documenting the constraint is a natural-language policy concern.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
This HTML/CSS template includes multiple Chinese comments and labels such as the section headers at L0161-L0162, indicating a fixed language/locale choice embedded in the skill asset. Under the policy, locale-specific language should not be forced without user opt-in or clear justification, and this file provides neither.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest describes this skill as rendering Markdown/text into '小红书风格卡片' (Xiaohongshu-style cards), but this file is explicitly an Instagram-themed template. The filename, CSS class names, and comments repeatedly indicate Instagram styling, which is a substantive behavior mismatch rather than an implementation detail.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The template explicitly defines a Traditional Chinese themed presentation and includes Chinese-language content/styling cues, but there is no indication in the file that this locale-specific output is optional or user-selected. This can violate language/locale policy when a skill forces a specific language or regional presentation without opt-in.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The helper fetchRemoteImageAsDataUrl performs unrestricted outbound fetches to attacker-controlled URLs and follows redirects, with no validation of destination, protocol beyond http/https at call site, content size, or internal address ranges. This is a classic SSRF-adjacent design issue for a renderer because untrusted document content can induce network requests from the host environment.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
When rendering content with remote images, the implementation silently issues outbound requests without any user-facing indication in this code path. That can disclose the renderer's IP, user agent/runtime behavior, and timing to third parties, which is especially problematic for a tool whose advertised purpose is local Markdown/text-to-card transformation.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The code optionally dereferences arbitrary remote image URLs found in input HTML and rewrites them into data URLs. In a Markdown/text-to-card renderer, this creates server-side network access based on untrusted content, which can leak IP/network metadata and enable SSRF-style probing if the renderer runs in a privileged environment.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The entire markdown file is written only in Chinese, with no indication that the skill supports other languages or that Chinese is a required locale. Under the policy rule for natural-language violations, fixed language use without opt-in or documented justification can be a locale policy issue.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The stylesheet sets the page font stack to prefer "PingFang SC", which is a Simplified Chinese system font, and the template also includes Chinese-language comments elsewhere. For a general-purpose skill/template, this creates a locale-specific presentation choice without any visible user choice or documented justification.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The template includes the literal text "备忘录" as a fixed interface label, which forces a specific language presentation. The policy allows locale constraints only when they are documented and justified or when the user is given a choice, neither of which is evident in this file.

Natural-Language Policy Violations

Low
Confidence
83% confidence
Finding
The body font stack explicitly includes "PingFang SC", a Simplified Chinese locale-specific font, and the file also contains Chinese-language comments, suggesting the template is tailored to a specific language/locale. Because there is no visible opt-in, fallback selection guidance, or documentation that this template is intentionally region-specific, this can conflict with the policy against forcing a locale without user choice.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The .card-business font-family includes "PingFang SC", which is specific to Simplified Chinese rendering, but the template does not indicate that users can choose another locale or that the template is explicitly region-bound. This may violate language/locale policy when used as a general-purpose skill asset.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The CSS font stack explicitly includes "PingFang SC", which is a Simplified Chinese-oriented font, but the template provides no user opt-in, locale selection, or justification for this language-specific presentation choice. Under the policy rule, hardcoded locale preferences in natural-language or presentation settings can be a language/locale policy concern when not documented or optional.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
This HTML template includes the locale-specific font "PingFang SC" and elsewhere uses Chinese-language comments, indicating an assumed Chinese-language context. For a generally reusable skill asset, forcing a specific language/locale presentation without opt-in or clear regional justification can violate language/locale policy expectations.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
The template specifies "PingFang SC" at L020, a Simplified Chinese system font, and multiple in-file comments are written only in Chinese later in the file. Under the language/locale policy rule, this suggests a baked-in locale preference rather than offering a user language choice or documenting that the template is intentionally region-specific.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The template hard-codes a Chinese-first font stack beginning with "PingFang SC" and elsewhere includes multiple Chinese-language comments and Chinese-specific fonts, which indicates a locale preference embedded in the skill artifact. Because this HTML template does not document user choice or a region-specific justification, it can be read as forcing a specific language/locale convention.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The CSS injects the literal text "JP" into the rendered output, and additional decorative text such as "STYLE" is also hard-coded elsewhere, indicating a fixed locale/style choice rather than user selection. This can violate language/locale policy when a skill forces a specific language or locale presentation without explicit opt-in or configurability.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The pseudo-element content "STYLE" is rendered directly by the template with no mechanism for localization or user choice. Because this file is a presentation template, embedding fixed-language output can amount to forcing a language/locale convention without opt-in.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The base font stack explicitly prioritizes "PingFang SC", a Simplified Chinese system font, and the theme comments and styling later also target East Asian typography. In this file there is no natural-language notice, user choice, or justification that the template is intended only for a Chinese/Japanese locale, which can violate the language/locale policy for generally applicable skills.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The theme sets `font-family: "Noto Serif JP", serif;`, which is a Japanese-specific font choice. Because the file does not state that the theme is exclusively for Japanese content or give users a locale choice, this is a natural-language locale-policy concern under the provided rule.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/xhs-card.cjs:8