Back to skill

Security audit

xhs-image-note-release

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent with its stated purpose, but it can automatically publish live Xiaohongshu posts from a logged-in account while weakening several safety boundaries.

Review this skill before installing. Use it only when you intentionally want an agent to post to a live Xiaohongshu account, confirm the title/body/images/topics yourself before running, avoid untrusted SVG illustrations or topic values, and do not disable sandboxing unless you can isolate the browser and files involved.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish_note.sh:234
Finding
JavaScript Injection Through Topic Values<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish_note.sh:234-245` **Vulnerability Type**: Browser-context JavaScript injection **Risk Level**: High ### Vulnerable Code ```javascript const topicResult = await js(`((topic) => { const container = document.getElementById('creator-editor-topic-container') if (!container) { return { error: 'topic container not found' } } const items = [...container.querySelectorAll('.item')] if (!items.length) { return { error: 'no topic items', containerText: container.innerText.slice(0, 200) } } let item = items.find(el => { const nameEl = el.querySelector('.name') if (!nameEl) return false const text = nameEl.innerText return text === '#' + topic || text === topic || text.includes(topic) }) if (!item) item = items[0] item.click() return { clicked: true, text: item.querySelector('.name')?.innerText || item.innerText } })('${topic.replace(/'/g, "\\'")}')`) ``` ### Technical Analysis The topic value is interpolated directly into JavaScript source executed by the `js()` browser-automation API. The code escapes apostrophes but does not safely serialize backslashes, line terminators, or other JavaScript syntax. Escaping only `'` is insufficient because an attacker can place a backslash before an apostrophe. The transformation adds another backslash, potentially leaving the quote effectively unescaped in the resulting JavaScript source. The attacker can then terminate the string literal and append arbitrary JavaScript. The shell-level validation does not restrict these characters. Passing the value through JSON before this point does not provide protection because the parsed value is subsequently inserted into executable source code. ### Attack Path 1. An attacker influences a topic passed through the `TOPICS` environment variable or generated publishing parameters. 2. The shell script stores the topic in the JSON parameter file. 3. The Node.js publishing code pa ...[truncated 992 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not construct executable JavaScript by concatenating topic values. 1. Pass the topic through a structured argument mechanism provided by the browser automation API. 2. If the API cannot accept arguments, serialize the complete JavaScript literal with `JSON.stringify`: ```javascript const topicLiteral = JSON.stringify(topic) const topicResult = await js(`((topic) => { // Existing lookup logic })(${topicLiteral})`) ``` 3. Prefer DOM APIs that accept structured selector or text arguments without evaluating generated source. 4. Validate topics with a conservative allowlist and enforce platform-compatible length limits. 5. Add tests covering apostrophes, backslashes, newlines, Unicode separators, template-literal characters, and attempted source termination. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/card-generator/card_generator.py:803
Finding
Unsanitized Active SVG Embedded Into Headless Browser Content<![CDATA[ ## Vulnerability Details **File Location**: `references/card-generator/card_generator.py:803-865` **Vulnerability Type**: Unsafe processing of active SVG content **Risk Level**: High ### Vulnerable Code ```python def load_illustration(path): p = Path(path) if not p.exists(): raise FileNotFoundError(f"Illustration file does not exist: {path}") ext = p.suffix.lower() if ext == ".svg": content = p.read_text(encoding="utf-8") try: root = ET.fromstring(content.encode("utf-8")) ns = {"svg": "http://www.w3.org/2000/svg"} g = root.find(".//svg:g", ns) if g is None: g = root.find(".//{http://www.w3.org/2000/svg}g") if g is not None: if "transform" in g.attrib: del g.attrib["transform"] ET.register_namespace("", "http://www.w3.org/2000/svg") inner = ET.tostring(g, encoding="unicode") return ("svg_inner", inner) except ET.ParseError: pass return ("svg_raw", content) ``` ```python if illust_type == "svg_inner": illustration_block = ( f'<g transform="translate({width/2}, {illustration_y})">' f'{illust_payload}</g>' ) elif illust_type == "svg_raw": illustration_block = ( f'<g transform="translate({width/2}, {illustration_y})">' f'{illust_payload}</g>' ) ``` The English error message above is a faithful translation of the original message; the execution logic is unchanged. ### Technical Analysis SVG is active document content rather than a passive image format. It can contain or reference: - Script elements. - Event-handler attributes. - `foreignObject` HTML. - External images, stylesheets, fonts, or other resources. - Animation and navigation-related elements. - Dangerous `href` or namespace-qualified link attributes. The parser extracts the first `<g>` subtree and serializes it without ...[truncated 1604 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the raw-SVG fallback. Reject malformed or unsupported SVG files. 2. Apply a strict SVG allowlist before serialization: - Permit only required geometric and styling elements. - Remove `script`, `foreignObject`, animation, link, and embedded HTML elements. - Remove all attributes whose names begin with `on`. - Reject external URLs in `href`, `xlink:href`, CSS `url()`, and style declarations. - Reject external stylesheets and font declarations. 3. Prefer converting SVG to a raster image in a dedicated sandboxed process before embedding it. 4. Render under a low-privilege account or container with no access to sensitive directories. 5. Disable network access for the renderer. 6. Add malicious SVG regression tests covering scripts, event handlers, external images, CSS imports, and `foreignObject`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/publish_note.sh:63
Finding
Predictable Shared Temporary File Exposes and Permits Tampering With Publishing Parameters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish_note.sh:63-81` **Vulnerability Type**: Unsafe predictable temporary file **Risk Level**: Medium ### Vulnerable Code ```bash PARAMS_FILE="/tmp/xhs_publish_params.json" python3 - "$IMAGE_DIR" "$IMAGES" "$TITLE" "$BODY" "$TOPICS" "$PARAMS_FILE" <<'PYEOF' import json, sys image_dir, images_str, title, body, topics_str, path = sys.argv[1:7] params = { "imageDir": image_dir, "images": [s.strip() for s in images_str.split(',') if s.strip()], "title": title, "body": body, "topics": [t.strip().lstrip('#') for t in topics_str.split(',') if t.strip()] } with open(path, 'w', encoding='utf-8') as f: json.dump(params, f, ensure_ascii=False) PYEOF ``` ```javascript import fs from 'fs' const params = JSON.parse( fs.readFileSync('/tmp/xhs_publish_params.json', 'utf8') ) ``` ### Technical Analysis Every invocation uses the same globally predictable path in a shared temporary directory. The file is opened with normal write semantics rather than exclusive creation. The script also does not explicitly set a restrictive `umask`, verify that the path is a regular file owned by the current user, or protect the interval between writing and reading. This creates several weaknesses: - A local attacker can pre-create the path as a symbolic link. - A local process can replace or modify the file before Node.js reads it. - Concurrent publishing runs can overwrite each other's parameters. - Draft titles, bodies, topics, and local image paths may be exposed if resulting permissions are permissive. - Cleanup occurs only at the normal end of the script; `set -e` can terminate execution before `rm -f` runs. ### Attack Path 1. A local attacker predicts `/tmp/xhs_publish_params.json`. 2. The attacker pre-creates a symbolic link at that path or monitors and replaces the file. 3. The Python process follows the path and writes publishing parameters, or the attacker modifies the file after creation. 4. The bro ...[truncated 757 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use a unique, securely created temporary file and guarantee cleanup: ```bash umask 077 PARAMS_FILE="$(mktemp "${TMPDIR:-/tmp}/xhs_publish_params.XXXXXX.json")" trap 'rm -f -- "$PARAMS_FILE"' EXIT export PARAMS_FILE ``` Then read the generated path rather than a hardcoded path: ```javascript const paramsPath = process.env.PARAMS_FILE const params = JSON.parse(fs.readFileSync(paramsPath, 'utf8')) ``` Additional controls should include: 1. Create the file exclusively and fail if secure creation is unavailable. 2. Verify that it is a regular file owned by the current user. 3. Avoid reopening the path where possible; pass content through standard input or an already-open descriptor. 4. Ensure concurrent runs use independent files. 5. Keep the `EXIT` trap so cleanup occurs after errors and signals. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
references/card-generator/card_generator.py:1198
Finding
Headless Chrome Rendering Runs With the Browser Sandbox Disabled<![CDATA[ ## Vulnerability Details **File Location**: `references/card-generator/card_generator.py:1198-1215` **Vulnerability Type**: Disabled browser isolation boundary **Risk Level**: Medium ### Vulnerable Code ```python cmd = [ chrome, "--headless", "--disable-gpu", "--no-sandbox", "--hide-scrollbars", "--force-device-scale-factor=1", "--virtual-time-budget=10000", f"--window-size={width},{height}", "--screenshot=" + os.path.abspath(output_path), "file://" + tmp_html.name, ] result = subprocess.run(cmd, capture_output=True, text=True) ``` ### Technical Analysis The `--no-sandbox` argument disables Chrome's process sandbox. That sandbox is a core defense intended to contain renderer compromise and isolate hostile web content from the host operating system. The renderer processes generated HTML that can include user-controlled text and illustrations. In particular, the Skill accepts SVG illustrations without robust active-content sanitization and loads remote font resources for some themes. Disabling isolation therefore increases the consequences of hostile rendering input and compromised remote resources. The option is not necessary for the declared card-generation functionality in a correctly configured desktop environment and exceeds least privilege. ### Attack Path 1. An attacker supplies a malicious illustration or causes the renderer to process hostile browser content. 2. Headless Chrome parses and renders that content. 3. The content triggers active behavior or a browser vulnerability. 4. Because `--no-sandbox` is enabled, normal renderer containment is absent. 5. A successful browser exploit can operate with the permissions of the user running the generator. ### Impact Assessment This setting does not itself execute a payload, but it weakens a critical boundary. In combination with malicious SVG, compromised remote CSS, or a browser vulnerability, it can increase impact from browser-context behavior to ...[truncated 208 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--no-sandbox` argument. 2. Run Chrome under a dedicated, unprivileged account. 3. Use a container or operating-system sandbox with: - A read-only filesystem where possible. - Access only to the generated temporary HTML and output path. - No access to credentials or unrelated user files. - Network access disabled unless explicitly required. 4. Sanitize or rasterize untrusted SVG before rendering. 5. Keep Chrome patched and fail safely if sandboxed rendering cannot be initialized. 6. Do not silently fall back to unsandboxed execution. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:31
Finding
Skill Documentation Directs Users to Disable the Agent Sandbox<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:31-41` **Additional Locations**: `SKILL.md:430`, `README.md:113`, `references/publish-method.md:18`, `references/publish-method.md:155` **Vulnerability Type**: Removal of a platform security boundary **Risk Level**: Medium ### Vulnerable Instruction The documentation repeatedly instructs WorkBuddy users to disable its sandbox if `ego-browser` is interrupted. The instruction is summarized in English as follows: ```text In newer WorkBuddy environments, disable the sandbox in settings; otherwise ego-browser may be interrupted. ``` The supporting publication reference states that the WorkBuddy sandbox must be disabled for the workflow. ### Technical Analysis The recommendation removes a security boundary for the entire Agent execution environment rather than granting narrowly scoped permissions needed for browser automation. Once disabled, the Skill and its external browser dependency can receive broader access to host files, processes, and other operating-system resources. This behavior exceeds least privilege. The legitimate requirement is access to an authenticated browser session and selected image files, not unrestricted execution outside the Agent sandbox. The risk compounds other findings, including JavaScript injection, unsafe SVG processing, and unsandboxed Chrome rendering. ### Attack Path 1. A user follows the documented setup or troubleshooting instructions. 2. The user disables the WorkBuddy sandbox globally. 3. The Skill, `ego-browser`, and related subprocesses execute with expanded host access. 4. A separate injection flaw, malicious input, compromised dependency, or browser vulnerability is triggered. 5. The resulting code gains access to a broader set of host resources than the publishing task requires. ### Impact Assessment The instruction does not itself install a backdoor or elevate operating-system identity. However, it substantially increases the scope available to a ...[truncated 321 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not recommend globally disabling the Agent sandbox. 2. Implement a sandbox-compatible browser integration. 3. Document the minimum required permissions: - Access to the selected image files. - Access to the local browser automation endpoint. - Network access only to the Xiaohongshu creator domain and required platform resources. 4. If a sandbox exception is unavoidable, make it narrowly scoped to a specific executable, directory, and duration. 5. Clearly warn users about the security consequences and require explicit confirmation. 6. Restore the security boundary immediately after the operation. 7. Treat sandbox initialization failure as an actionable configuration error rather than defaulting to unrestricted execution. ]]>

T08 · Insecure Dependencies

Note
Location
references/card-generator/card_generator.py:1101
Finding
Unpinned Remote Font CSS Is Loaded From Multiple External Sources<![CDATA[ ## Vulnerability Details **File Location**: `references/card-generator/card_generator.py:1101-1125` **Vulnerability Type**: Mutable remote rendering dependency **Risk Level**: Low ### Vulnerable Code ```python google_fonts = t.get("google_fonts", []) fonts_links = [] if google_fonts: families = "&".join( f"family={f.replace(' ', '+')}" for f in google_fonts ) mirrors = [ ("fonts.loli.net", "https://fonts.loli.net"), ("fonts.googleapis.cn", "https://fonts.googleapis.cn"), ("fonts.googleapis.com", "https://fonts.googleapis.com"), ] for domain, base_url in mirrors: fonts_links.append( f'<link href="{base_url}/css2?{families}&display=swap" ' f'rel="stylesheet">' ) ``` ```javascript var mirrors = [ 'https://fonts.loli.net/css2?{families}&display=swap', 'https://fonts.googleapis.cn/css2?{families}&display=swap' ]; ``` ### Technical Analysis For themes requiring external fonts, the generated local HTML loads mutable CSS from three remote services simultaneously. One source, `fonts.loli.net`, is a community-operated mirror. The fetched CSS and referenced font resources are not version-pinned or integrity-checked. This creates an avoidable supply-chain and privacy dependency during local card generation. A compromised service or network path could return modified CSS referencing additional attacker-controlled resources. The service also receives the renderer's IP address, request metadata, and requested font families. The exposure is more significant because the same renderer is launched with Chrome's sandbox disabled. ### Attack Path 1. A user selects a theme whose configuration contains remote Google Fonts. 2. `build_html()` inserts stylesheet links for all configured mirrors. 3. Headless Chrome contacts each remote service while rendering the local card. 4. A compromised or malicious service returns altered CSS or redirects resource loading. 5. Chrom ...[truncated 617 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bundle vetted font files with the Skill and load them locally. 2. Pin font versions and verify cryptographic hashes during packaging. 3. Remove the community mirror from the runtime trust path. 4. If remote retrieval is unavoidable: - Fetch fonts in a separate sandboxed preparation step. - Allowlist a single trusted source. - Verify downloaded content against expected hashes. - Cache the verified files locally. 5. Disable renderer network access after dependencies are prepared. 6. Document any unavoidable outbound requests and provide an offline mode. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
描述把该技能的核心能力定义为“小红书图文笔记自动发布”,且声称通过 ego-browser 完成发布全流程;卡片生成器只是附带能力。给出的代码却完全聚焦于卡片图片生成:定义多种主题,构建 SVG/HTML,支持背景图、插画、装饰元素、字体与布局参数,并用本地 Chrome headless 截图输出 PNG。代码没有任何 ego-browser 依赖、没有网络请求、没有访问小红书、没有自动化脚本与页面交互,也没有账号状态检查或发布动作。因此这不是轻微偏差,而是主用途与关键能力均不一致。描述中提到的“多样式风格卡片生成器”与代码部分吻合,但声明的首要功能——小红书自动发布——在此代码块中完全缺失,构成明显不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
发布自动化部分与声明基本一致:代码确实依赖 ego-browser、要求已登录小红书,并执行图片上传、标题/正文填写、话题选择和最终发布。但声明中的一个重要功能块——多样式卡片生成器及其大量视觉参数配置——在提供的代码中完全不存在。由于这是描述中明确宣传的核心能力之一,而非实现细节,因此属于描述与实际行为不一致。未发现代码存在与小红书发布无关的额外高风险能力;不匹配主要在于描述夸大了内容生成/卡片设计功能。

Missing User Warnings

High
Confidence
96% confidence
Finding
The skill clearly automates submission to a logged-in Xiaohongshu account and even documents directly invoking the publish action, yet it does not require an explicit final confirmation step. This is dangerous because any accidental trigger, prompt ambiguity, or agent mistake can result in irreversible public posting under the user's identity.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The README advertises fully automated publishing, posting, and cleanup on a logged-in social media account, but does not prominently warn that running the skill will perform irreversible account actions and may remove local artifacts as part of cleanup. In an agent/skill context, insufficient disclosure increases the risk of unintended posting, reputational harm, and accidental data loss because users may trigger the skill without understanding that it will act directly on their authenticated account.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill documents use of shell commands, local file access, and generated image artifacts, but it does not declare any explicit tool scope or permission boundaries. In an agent environment, this increases the chance that the skill is invoked with broader-than-necessary capabilities, making unintended file modification or command execution harder to constrain and review.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger conditions are broad enough to match many general Xiaohongshu-related requests, not just explicit publishing requests. In this skill's context, that is risky because the documented behavior culminates in real account actions, so an incidental invocation could lead to unintended draft manipulation or posting workflows being started.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The generator injects remote font CDN URLs and JavaScript-based fallback loading into HTML that is supposed to render a local card image. This creates unexpected outbound network access, leaks environment metadata such as IP/user-agent/render timing, and weakens the trust boundary by depending on third-party remote resources during local rendering.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"file://" + tmp_html.name,
        ]

        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode != 0:
            print(f"[ERROR] 渲染失败: {result.stderr}", file=sys.stderr)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The document explicitly recommends enumerating prototype methods and invoking the undocumented internal `_onPublish()` method to trigger content publication while bypassing the normal UI interaction path. This is dangerous because it relies on hidden application internals rather than supported automation surfaces, defeating expected UX safeguards and making unauthorized or accidental posting easier if the skill is triggered on unreviewed content.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The documented flow performs a final publish action through an internal method call without any documented user-facing warning, review, or confirmation checkpoint. In the context of an auto-posting skill for a logged-in social media account, this increases the risk of unintended publication, account misuse, reputational harm, and irreversible posting of malformed or sensitive content.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script directly triggers publication by calling the page component’s internal `_onPublish()` method with no interactive confirmation, dry-run mode, or last-moment user acknowledgment. Because this skill is explicitly designed to post to a logged-in social media account, accidental execution can cause unintended public disclosure, reputational harm, or policy violations, and the action is not easily reversible once published.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
The text states the feature is specifically for 'China network' and mainland China conditions, which imposes a locale/region-specific behavior in natural language. Because this file does not frame the constraint as an explicit region-scoped mode or user opt-in, it can be read as a locale-specific policy choice without documented choice.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The generated HTML always sets `lang="zh-CN"`, which enforces a specific language/locale regardless of user preference. The file does not provide any option to choose another locale or document that this is a China-specific tool, so this is a natural-language locale policy issue.

Missing User Warnings

Low
Confidence
79% confidence
Finding
The script writes the full note payload, including title, body, topics, image metadata, and local paths, to a predictable file in `/tmp`. On multi-user or weakly isolated systems this can expose sensitive draft content or local filesystem information to other local processes, and the file may persist if the script crashes before cleanup.

Static analysis

No suspicious patterns detected.