Back to skill

Security audit

multi-writing-skills

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its publishing purpose, but its credential handling and unrestricted image upload paths create real account and local-data exposure risks.

Install only if you intend to let this tool handle publishing-account credentials and send article content, prompts, and media to external services. Use dedicated or low-privilege publishing accounts where possible, avoid pasting full browser cookies into shared shells, protect or restrict the config file, and do not pass untrusted cover paths or URLs. Review every publish or draft command before running it, especially on private or proprietary drafts.

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

T09 · Insecure Skill Coding Practices

Error
Location
src/multi_writing_skills/platforms/wechat.py:74
Finding

Arbitrary Local File Disclosure and SSRF Through WeChat Cover Upload

Content
View full analysis
ImageUploadResult: """上传图片到微信素材库""" token = await self._get_access_token() # 判断是 URL 还是本地文件 if image_path.startswith(("http://", "https://")): # 下载远程图片 resp = await self._client.get(image_path) image_data = resp.content filename = image_path.split("/")[-1].split("?")[0] else: # 读取本地文件 path = Path(image_path) if not path.exists(): return ImageUploadResult( success=False, message=f"图片文件不存在: {image_path}" ) image_data = path.read_bytes() filename = path.name # 上传到微信 url = f"{self.BASE_URL}/material/add_material" params = {"access_token": token, "type": "image"} files = {"media": (filename, image_data, "image/jpeg")} resp = await self._client.post(url, params=params, files=files) ``` ### Technical Analysis The cover path is treated as either an unrestricted HTTP(S) URL or an unrestricted local filesystem path. The implementation does not: - Restrict local paths to an approved content directory. - reject symbolic links or sensitive system files. - Decode and validate that the input is an actual image. - Validate the response MIME type. - Restrict response size. - Block loopback, private, link-local, or cloud metadata addresses. - Validate redirect destinations. All retrieved bytes are labeled as `image/jpeg` and uploaded to WeChat. Therefore, a non-image local file or internal HTTP response can be transferred to an external platform. This behavior exceeds the minimum privilege needed for cover-image publication because the process can read any file accessible to its operating-system account and connect to arbitrary netwo ...[truncated 1150 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
src/multi_writing_skills/platforms/zhihu.py:104
Finding

Arbitrary Local File Disclosure and SSRF Through Zhihu Image Upload

Content
View full analysis
ImageUploadResult: """上传图片到知乎图床""" try: # 判断是 URL 还是本地文件 if image_path.startswith(("http://", "https://")): resp = await self._client.get(image_path) image_data = resp.content filename = image_path.split("/")[-1].split("?")[0] else: path = Path(image_path) if not path.exists(): return ImageUploadResult( success=False, message=f"图片文件不存在: {image_path}" ) image_data = path.read_bytes() filename = path.name # 知乎图片上传 url = f"{self.BASE_URL}/images" headers = self._get_headers() headers["Content-Type"] = "image/jpeg" params = {"image_source": "bubian_m", "image_name": filename} resp = await self._client.post( url, headers=headers, params=params, content=image_data ) data = resp.json() if "upload_url" in data: # 获取上传结果 upload_resp = await self._client.put( data["upload_url"], content=image_data, headers={"Content-Type": "image/jpeg"}, ) ``` ### Technical Analysis The Zhihu provider accepts an arbitrary path or URL and reads the complete content without path restrictions, image decoding, content validation, response-size limits, or private-network protections. The bytes are then transmitted to Zhihu while being declared as JPEG regardless of their actual format. The function also performs a `PUT` request to an `upload_url` returned by the Zhihu API. Under normal operation that URL is expected to be a platform-controlled upload destination, but ...[truncated 943 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
src/multi_writing_skills/platforms/toutiao.py:55
Finding

Arbitrary Local File Disclosure and SSRF Through Toutiao Image Upload

Content
View full analysis
ImageUploadResult: """上传图片到头条图床""" try: # 判断是 URL 还是本地文件 if image_path.startswith(("http://", "https://")): resp = await self._client.get(image_path) image_data = resp.content filename = image_path.split("/")[-1].split("?")[0] else: path = Path(image_path) if not path.exists(): return ImageUploadResult( success=False, message=f"图片文件不存在: {image_path}" ) image_data = path.read_bytes() filename = path.name # 头条图片上传 url = f"{self.BASE_URL}/image/upload" headers = self._get_headers() del headers["Content-Type"] # multipart 上传需要删除这个 files = {"image": (filename, image_data, "image/jpeg")} resp = await self._client.post(url, headers=headers, files=files) ``` ### Technical Analysis The Toutiao image uploader reads arbitrary local files and retrieves arbitrary HTTP(S) resources. It does not enforce directory boundaries, check file type, decode images, restrict network destinations, check response status, or impose a body-size limit. It then sends the bytes to Toutiao under an authenticated session while falsely labeling all input as JPEG. ### Attack Path 1. An attacker supplies a local sensitive path or internal URL as the cover/image path. 2. The Skill reads the resource with the privileges and network reachability of its process. 3. The complete resource is uploaded to the Toutiao image API. 4. The returned platform URL can make the data available through the authenticated publishing account or generated draft. ### Impact Assessment Exploitation permits discl ...[truncated 299 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
src/multi_writing_skills/config.py:153
Finding

Publishing Credentials Stored in Plaintext and Exposed Through Command-Line Arguments

Content
View full analysis
None: """设置配置项 示例: multi-writing-skills config set wechat.app_id your_app_id multi-writing-skills config set wechat.app_secret your_secret multi-writing-skills config set ai.api_key your_api_key """ settings.load() parts = key.split(".") if len(parts) != 2: console.print("[red]错误: 配置键格式应为 section.key[/red]") raise typer.Exit(1) section, subkey = parts if section == "wechat": if subkey == "app_id": settings.wechat.app_id = value elif subkey == "app_secret": settings.wechat.app_secret = value else: console.print(f"[red]未知配置项: {key}[/red]") raise typer.Exit(1) elif section == "zhihu": if subkey == "cookie": settings.zhihu.cookie = value else: console.print(f"[red]未知配置项: {key}[/red]") raise typer.Exit(1) elif section == "toutiao": if subkey == "cookie": settings.toutiao.cookie = value else: console.print(f"[red]未知配置项: {key}[/red]") raise typer.Exit(1) elif section == "ai": if subkey == "provider": settings.ai.provider = value elif subkey == "api_key": settings.ai.api_key = value elif subkey == "base_url": settings.ai.base_url = value elif subkey == "model": settings.ai.model = value else: ...[truncated 2881 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
Findings (90)

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

This line defines access to ~/.openclaw/.env, a credential-bearing file outside the skill's own configuration namespace. Accessing another application's secret store is credential collection behavior and is dangerous because it broadens the set of secrets this skill can consume without a clear need-to-know boundary.

Content

Scanner excerpt · src/multi_writing_skills/config.py (reported line 21)May include surrounding context.

python
def load_openclaw_env() -> dict[str, str]:
    """从 ~/.openclaw/.env 加载环境变量"""
    env_file = Path.home() / ".openclaw" / ".env"
    env_vars = {}
    if env_file.exists():

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

Opening and parsing ~/.openclaw/.env operationalizes credential harvesting from an unrelated environment. Even if intended for convenience, this creates a path for the skill to ingest secrets the user did not intend to share with it, which is especially sensitive in agent/skill ecosystems.

Content

Scanner excerpt · src/multi_writing_skills/config.py (reported line 22)May include surrounding context.

python
def load_openclaw_env() -> dict[str, str]:
    """从 ~/.openclaw/.env 加载环境变量"""
    env_file = Path.home() / ".openclaw" / ".env"
    env_vars = {}
    if env_file.exists():
        with open(env_file, encoding="utf-8") as f:

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

The load() routine conditionally imports secrets from ~/.openclaw/.env whenever an OpenClaw directory exists, making cross-context credential access automatic. In skill code, automatic secret discovery from unrelated tooling is more dangerous because users may install the skill for content publishing, not to grant it access to all OpenClaw-held credentials.

Content

Scanner excerpt · src/multi_writing_skills/config.py (reported line 91)May include surrounding context.

python
def load(self) -> None:
        """从文件和环境变量加载配置"""
        # 如果在 openclaw 环境中,先加载 ~/.openclaw/.env
        openclaw_env = {}
        if is_openclaw_env():
            openclaw_env = load_openclaw_env()

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · src/multi_writing_skills/converter/css_theme.py (reported line 47)May include surrounding context.

python
if properties:
            rules.append(CSSRule(selector=selector, properties=properties))

    return rules


def css_to_inline_style(rules: list[CSSRule]) -> Dict[str, str]:

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The guidance content from L07 onward is written in Chinese, including core project overview, commands, architecture, and design patterns, which effectively imposes a specific language on users of the skill file. The file does not offer an alternative language, opt-in mechanism, or justification that the repository is intentionally limited to a Chinese-language audience.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The documentation advertises automatic multi-platform publishing and image upload but does not warn users that article content and media will be transmitted to third-party services. This can create confidentiality and privacy risks if users process unpublished, proprietary, or personal material without realizing it will leave the local environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README instructs users to copy highly sensitive authentication material such as Zhihu/Toutiao cookies and WeChat app credentials from browser developer tools into the tool configuration, but provides no warning about the security risks of handling, storing, or reusing those secrets. Session cookies often grant direct account access, so unsafe storage, logging, or accidental sharing could lead to account takeover or unauthorized publishing.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill advertises one-click publishing to external platforms but does not clearly warn that it may transmit content over the network and write drafts or posts to third-party services. Users may interpret the capability as local formatting assistance only, which makes the external side effects insufficiently transparent.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The trigger conditions are broad natural-language descriptions that can match many ordinary writing or publishing requests, increasing the chance the skill activates outside a narrowly intended scope. Because the skill supports publishing and content transformation actions, over-triggering can cause unintended execution paths, including drafting or sending content to external services without sufficiently explicit user intent.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

Telling users they can invoke the skill with arbitrary natural-language requests, without clear scope boundaries, encourages the agent to interpret broad prompts as authorization to perform powerful actions. In a skill that includes AI writing, transformation, and multi-platform publishing, this ambiguity raises the risk of unintended or excessive operations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The CLI examples include a real publish/draft command, but there is no nearby warning that executing it will contact remote services and create or update content on a third-party platform. This omission can lead to accidental publication workflows or unintended disclosure of article content to external services.

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 · docs/skill-tutorial.html (reported line 799)May include surrounding context.

html
</div>
                <div class="nl-example">
                    <div class="label">系统自动执行:</div>
                    <div class="example">openclaw write "程序员成长" --style storytelling --length medium</div>
                </div>

                <h3>AI 去痕</h3>

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 · docs/skill-tutorial.html (reported line 799)May include surrounding context.

html
</div>
                <div class="nl-example">
                    <div class="label">系统自动执行:</div>
                    <div class="example">openclaw write "程序员成长" --style storytelling --length medium</div>
                </div>

                <h3>AI 去痕</h3>

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The tutorial instructs users to configure sensitive credentials and cookies for third-party publishing platforms, but it does not warn about secure storage, least privilege, token rotation, or the fact that publishing transmits content and authentication material to external services. In a skill context that automates CLI actions from natural language, this omission increases the chance users paste long-lived secrets into unsafe places, store them insecurely, or expose privileged session cookies.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The convert command mixes local transformation behavior with remote publishing when --draft is supplied, which creates a dangerous mismatch between user expectation and actual side effects. In a CLI that handles platform credentials and publishes to external services, unclear semantics can cause accidental disclosure or unintended posting of user content to third-party platforms.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The theme_remove command unlinks a CSS file directly once the computed path exists. Although it prints after deletion, there is no confirmation prompt or pre-action warning for this destructive filesystem operation in the command flow shown here.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The module explicitly reads ~/.openclaw/.env and imports whatever secrets are present there into this skill's configuration flow, even though those secrets belong to a separate OpenClaw environment. This creates an unintended credential boundary crossing: a skill can inherit unrelated secrets without user awareness, increasing the risk of secret exposure or downstream misuse if other parts of the skill transmit or persist them.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The save() method writes platform cookies, API keys, and app secrets directly into a local YAML config file under the user's home directory without any protection or warning. Storing plaintext credentials on disk increases exposure through local compromise, backups, accidental sharing, or overly permissive filesystem settings.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · src/multi_writing_skills/converter/__init__.py (reported line 41)May include surrounding context.

python
"""Markdown 转换器"""

    # mdnice API 端点
    MDNICE_API = "https://api.mdnice.com/api/v1/markdown"

    def __init__(self, options: Optional[ConvertOptions] = None) -> None:
        self.options = options or ConvertOptions()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

When use_api is enabled, the converter sends full Markdown content to an external service (mdnice or a caller-supplied endpoint). That can expose sensitive document contents, embedded secrets, internal links, or unpublished material to third parties without any disclosure, consent gate, or destination allowlisting in this module.

Content

No source excerpt is available for this finding.

Ssd 1

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

Untrusted Markdown is interpolated directly into the system prompt, which gives attacker-controlled document text higher-priority instruction context and enables prompt injection or semantic instruction takeover. Since the model is then asked to transform and return HTML, malicious Markdown can alter behavior, suppress formatting rules, or induce unsafe HTML output that downstream consumers may trust.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

This code transmits user content to an external OpenAI-compatible endpoint, including support for a caller-controlled base_url, which can send documents and prompts to third-party infrastructure. In this skill context, the behavior is security-relevant because document conversion may involve confidential content and there are no built-in restrictions, approvals, or trust controls on the destination.

Content

Scanner excerpt · src/multi_writing_skills/converter/ai.py (reported line 72)May include surrounding context.

python
async def _call_openai(self, system_prompt: str, user_content: str) -> str:
        """调用 OpenAI API"""
        base_url = self.config.base_url or "https://api.openai.com/v1"

        response = await self._client.post(
            f"{base_url}/chat/completions",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code sends user-provided Markdown, prompts, and potentially sensitive document contents to third-party AI providers without any built-in disclosure, consent, redaction, or data-classification guardrails. In a document-conversion skill, this creates a real confidentiality risk because users may reasonably submit private drafts, credentials, or internal content that is then transmitted off-host to external services.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

The Anthropic API call sends user-provided content to a third-party external service, which is a real data-exposure concern in a conversion component that may process private Markdown. Even if expected functionality, it remains a true vulnerability when no disclosure, consent, or data-handling safeguards are present.

Content

Scanner excerpt · src/multi_writing_skills/converter/ai.py (reported line 97)May include surrounding context.

python
async def _call_anthropic(self, system_prompt: str, user_content: str) -> str:
        """调用 Anthropic API"""
        response = await self._client.post(
            "https://api.anthropic.com/v1/messages",
            headers={
                "x-api-key": self.config.api_key,
                "anthropic-version": "2023-06-01",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The converter transmits user-supplied markdown and title to a remote service, which can expose sensitive or proprietary content to a third party without any visible consent flow, warning, or data handling controls. In this skill’s context, markdown may contain unpublished articles, embedded secrets, internal links, or personal data, making the external transmission materially risky rather than merely theoretical.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.