Back to skill

Security audit

WeChat Article Writer

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent WeChat draft-writing purpose, but it also installs persistent agent behavior and uses sensitive publishing credentials in ways users should review before installing.

Review this skill before installing. Only use it in a workspace where persistent AGENTS.md and HEARTBEAT.md changes are acceptable, and remove those rules if you do not want future sessions to resume work automatically. Keep WeChat secrets in a protected location, avoid printing them, and confirm the target account before any draft creation. Do not render untrusted Markdown with remote images or PlantUML blocks unless network destinations are restricted, and pin or review external dependencies before setup.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (5)

T02 · Agent Memory Poisoning

Error
Location
scripts/setup.sh:94
Finding

Persistent modification of Agent instructions enables autonomous cross-session execution

Content
View full analysis
/dev/null; then ok "AGENTS.md 已包含写作规则" else cat >> "$AGENTS_FILE" << 'AGENTS_BLOCK' ## 微信公众号文章写作规则 ### 自动执行(不问人) - 选题调研(web_search) - 撰写大纲 - 生成初稿(spawn Writer 子代理,用 Opus 模型) - 评审打分(spawn Reviewer 子代理,用 Sonnet 模型) - 自动修改循环(最多2轮) - baoyu renderer 排版 (scripts/renderer/, default 主题) - 启动预览服务器 ... AGENTS_BLOCK ok "AGENTS.md 已追加写作规则" fi ``` ```bash HEARTBEAT_FILE="$WORKSPACE/HEARTBEAT.md" HB_MARKER="## 文章 Pipeline 检查" if [ -f "$HEARTBEAT_FILE" ] && grep -q "$HB_MARKER" "$HEARTBEAT_FILE" 2>/dev/null; then ok "HEARTBEAT.md 已包含 pipeline 检查" else cat >> "$HEARTBEAT_FILE" << 'HB_BLOCK' ## 文章 Pipeline 检查 每次心跳检查 ~/.wechat-article-writer/drafts/*/pipeline-state.json - 如果有 phase 不是 "done" 且不是等待人工的阶段 → 继续执行 - 如果 phase 是 "previewing_text" 或 "illustrating" → 不要自动继续,等用户 - 如果 pipeline 超过 24 小时没更新 → 提醒用户 HB_BLOCK ok "HEARTBEAT.md 已追加 pipeline 检查" fi ``` The corresponding state protocol further states: ```markdown ## Resume Protocol (MANDATORY at start of every turn) 1. Check if any `pipeline-state.json` exists with `phase` ≠ `done`/`blocked`. 2. If found, read it — this is your current task. 3. If `subagent_label` is set and `subagent_status` is `pending`: - Check via `subagents list`. ... 4. If no subagent pending → execute `pending_action`. ``` ### Technical Analysis The setup process appends durable workflow directives to workspace-level `AGENTS.md` and `HEARTBEAT.md` files. These are not ordinary Skill-local configuration files: they can influence Agent behavior ...[truncated 1955 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/renderer/md/extensions/plantuml.ts:120
Finding

PlantUML diagram source is disclosed to a public rendering service by default

Content
View full analysis
, ): string { const encoded = encodePlantUML(code) const formatPath = options.format === `svg` ? `svg` : `png` return `${options.serverUrl}/${formatPath}/${encoded}` } ``` ```typescript async function fetchSvgContent(svgUrl: string): Promise { try { const response = await fetch(svgUrl) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } const svgContent = await response.text() return svgContent .replace(/(]*)\swidth="[^"]*"/g, `$1`) .replace(/(]*)\sheight="[^"]*"/g, `$1`) .replace(/(]*style="[^"]*?)width:[^;]*;?/g, `$1`) .replace(/(]*style="[^"]*?)height:[^;]*;?/g, `$1`) } catch (error) { console.warn(`Failed to fetch SVG content from ${svgUrl}:`, error) return `
PlantUML图表加载失败
` } } ``` ```typescript const resolvedOptions: Required = { serverUrl: options.serverUrl || `https://www.plantuml.com/plantuml`, format: options.format || `svg`, className: options.className || `plantuml-diagram`, inlineSvg: options.inlineSvg || false, ... } ``` ...[truncated 2123 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/renderer/main.ts:36
Finding

Unrestricted remote image downloads permit server-side request forgery

Content
View full analysis
{ return new Promise((resolve, reject) => { const protocol = url.startsWith('https') ? https : http; const file = fs.createWriteStream(destPath); const request = protocol.get( url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (response) => { if (response.statusCode === 301 || response.statusCode === 302) { const redirectUrl = response.headers.location; if (redirectUrl) { file.close(); fs.unlinkSync(destPath); downloadFile(redirectUrl, destPath).then(resolve).catch(reject); return; } } if (response.statusCode !== 200) { file.close(); fs.unlinkSync(destPath); reject(new Error(`Failed to download: ${response.statusCode}`)); return; } response.pipe(file); file.on('finish', () => { file.close(); resolve(); }); }, ); request.on('error', (err) => { file.close(); fs.unlink(destPath, () => {}); reject(err); }); request.setTimeout(30000, () => { request.destroy(); reject(new Error('Download timeout')); }); }); } ``` ```typescript async function resolveImagePath( imagePath: string, baseDir: string, tempDir: string, ): Promise { if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) { const hash = createHash('md5').update(imagePath).digest('hex').slice(0, 8); const ext = getImageExtension(imagePath); const localPath = path.join(tempDir, `remote_${hash}.${ext}`); if (!fs.existsSync(localPath)) { console.error(`[markdown-to ...[truncated 2940 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
references/pipeline-state.md:41
Finding

Live WeChat session token is persisted in plaintext state

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/setup.sh:57
Finding

Unpinned external Skills and JavaScript dependencies create supply-chain exposure

Content
View full analysis
/dev/null; then openclaw skill install article-illustrator 2>/dev/null && ok "article-illustrator 已安装" || fail "安装失败,请手动安装: openclaw skill install article-illustrator" else fail "请手动安装: openclaw skill install article-illustrator" fi fi if ! check_skill "wechat-publisher"; then warn "wechat-publisher 未安装 — 尝试安装..." if command -v openclaw &>/dev/null; then openclaw skill install wechat-publisher 2>/dev/null && ok "wechat-publisher 已安装" || fail "安装失败,请手动安装: openclaw skill install wechat-publisher" else fail "请手动安装: openclaw skill install wechat-publisher" fi fi ``` ```bash if [[ -f "$RENDERER_MD/package.json" ]]; then ( cd "$RENDERER_MD" && "$HOME/.bun/bin/bun" install --frozen-lockfile --silent 2>/dev/null ) && ok "renderer deps installed" || warn "run: cd $RENDERER_MD && bun install" fi ``` ```json { "dependencies": { "fflate": "^0.8.2", "front-matter": "^4.0.2", "highlight.js": "^11.11.1", "juice": "^11.0.1", "marked": "^15.0.6", "reading-time": "^1.5.0", "remark-cjk-friendly": "^1.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.5" } } ``` No dependency lockfile was present in the audited project tree. ### Technical Analysis The setup script installs two external Skills by name without a pinned version, immutable source revision, checksum, or signature. The effective code installed by these commands can therefore change after this Skill has been reviewed. The bundled renderer also declares dependencies using caret ranges. These ranges permit futu ...[truncated 1690 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (118)

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

The skill is explicitly designed to access secrets.json containing WeChat API credentials, and the broader workflow also supports browser automation against an authenticated WeChat session. In this context, credential access is functionally necessary, but it remains security-sensitive because compromise, misuse, or overbroad agent access could let the skill publish content or act on behalf of the account owner.

Content

Scanner excerpt · SKILL.md (reported line 131)May include surrounding context.

md
| Field | Default | Description |
|-------|---------|-------------|
| `default_article_type` | `"教程"` | Default article type (科普/教程/观点/资讯) |
| `wechat_secrets_path` | `~/.wechat-article-writer/secrets.json` | Path to WeChat API credentials |
| `chrome_debug_port` | `18800` | Chrome CDP port for browser automation (Path B) |
| `wechat_author` | — | Author name shown in WeChat draft |
| `word_count_targets` | See defaults | Min/max word counts per article type |

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

The lesson instructs the workflow to check for and use locally stored WeChat API credentials (appid and appsecret) from a fixed path. In an agent environment, guidance that encourages automatic discovery and use of local secrets can enable unauthorized credential access and expansion of privileges beyond what the user explicitly approved.

Content

Scanner excerpt · references/LESSONS_LEARNED.md (reported line 159)May include surrounding context.

md
**Symptom:** We assumed browser automation (Path A/B) was required because the account is an unverified subscription account (未认证订阅号).

**Finding:** The `cgi-bin/draft/add` API works fine for unverified accounts. The restriction on unverified accounts applies to *publishing* (send to followers) but not to *saving drafts*. The API-based path requires only appid + appsecret, both available in `~/.wechat-article-writer/secrets.json`.

**Lesson:** Always check for WeChat API credentials first. If available, skip the browser entirely.

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

The pre-flight checklist operationalizes searching for ~/.wechat-article-writer/secrets.json and using its contents to select an API path, which is effectively an instruction to access sensitive credentials from disk. In a skill, this lowers the barrier to secret harvesting or misuse because the existence of the file is treated as a signal to proceed automatically.

Content

Scanner excerpt · references/LESSONS_LEARNED.md (reported line 225)May include surrounding context.

md
## Checklist Update: Path C Pre-flight

- [ ] `~/.wechat-article-writer/secrets.json` exists with `appid` + `appsecret`?
  - Yes → use Path C
  - No → fall back to Path A (macOS) or Path B (Linux)
- [ ] Access token fetched and valid (not expired)?

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The checklist includes fetching and validating an access token, which indicates active use of privileged authentication material. While token use is necessary for the API workflow, the document lacks safeguards around storage, masking, expiry handling, and preventing token leakage through logs or intermediate files.

Content

Scanner excerpt · references/LESSONS_LEARNED.md (reported line 228)May include surrounding context.

md
- [ ] `~/.wechat-article-writer/secrets.json` exists with `appid` + `appsecret`?
  - Yes → use Path C
  - No → fall back to Path A (macOS) or Path B (Linux)
- [ ] Access token fetched and valid (not expired)?
- [ ] All images uploaded to WeChat CDN (`media/uploadimg`)? Save URLs in `wechat_image_urls.json`.
- [ ] Cover image uploaded as permanent material? Save `thumb_media_id`.
- [ ] Content cleaned: no `<meta>`, no `<body>`, no `Preview build` banner, no verbose `style="..."`?

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

The instruction explicitly tells the operator to inspect a local secrets file containing app credentials. Direct credential file access is sensitive by nature and becomes dangerous in a skill because it conditions the agent/operator to retrieve secrets from disk without explicit access controls, purpose limitation, or secrecy safeguards.

Content

Scanner excerpt · references/browser-automation.md (reported line 386)May include surrounding context.

Check for credentials first:

bash
cat ~/.wechat-article-writer/secrets.json  # {"appid": "wx...", "appsecret": "..."}

If the file exists, use Path C. Otherwise fall back to Path A or B.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/browser-automation.md (reported line 390)May include surrounding context.

text
If the file exists, use Path C. Otherwise fall back to Path A or B.

### Step 1: Get Access Token

```python
import json, requests, os

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

The sample code programmatically reads app credentials from a predictable path in the user's home directory. Embedding credential access into the workflow enables automated use of privileged secrets and increases the chance of unauthorized API actions, accidental disclosure, or reuse in contexts beyond the user's intent.

Content

Scanner excerpt · references/browser-automation.md (reported line 394)May include surrounding context.

python
import json, requests, os
creds = json.load(open(os.path.expanduser("~/.wechat-article-writer/secrets.json")))
resp = requests.get(
    "https://api.weixin.qq.com/cgi-bin/token",
    params={"grant_type": "client_credential", "appid": creds["appid"], "secret": creds["appsecret"]},

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
85% confidence
Finding

Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Content

Scanner excerpt · references/fact-checker-prompt.md (reported line 7)May include surrounding context.

md
## Role

You are a fact-checker. Your job is to verify every factual claim in the article. You have access to `web_search` and `web_fetch`. You do NOT judge writing quality — only factual accuracy.

## Process

Natural-Language Policy Violations

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The section titled "Chinese-First Rule" requires translating any English passage longer than a phrase into Chinese and marks this as a mandatory HIGH-severity fix. This is a natural-language locale policy that forces a specific language output, and the file does not offer user opt-in or explain that the skill is limited to a China-specific or Chinese-only context.

Content

No source excerpt is available for this finding.

External Script Fetching

High
Category
Supply Chain
Confidence
98% confidence
Finding

The tutorial includes a one-line command that fetches a remote script over the network and immediately executes it. If an agent, user, or downstream automation copies this pattern without verification, a compromised domain, MITM, or malicious script update could result in arbitrary code execution on the target system.

Content

Scanner excerpt · references/templates.md (reported line 203)May include surrounding context.

Step 1:安装Ollama 安装Ollama是最简单的一步...

bash
curl -fsSL https://ollama.ai/install.sh | sh

…

Chaining Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

The use of shell piping ('| sh') removes the opportunity for review and directly chains untrusted remote content into execution. In skill context, this is more dangerous because the file is a reusable template/example that may be reproduced by an agent into generated tutorials, normalizing an unsafe execution pattern.

Content

Scanner excerpt · references/templates.md (reported line 203)May include surrounding context.

Step 1:安装Ollama 安装Ollama是最简单的一步...

bash
curl -fsSL https://ollama.ai/install.sh | sh

…

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 68)May include surrounding context.

md
Requirements:
    Credentials file at path configured by `wechat_secrets_path` in config.json
    Default: ~/.wechat-article-writer/secrets.json -- {"appid": "wx...", "appsecret": "..."}
    pip install requests

KNOWN FIELD LIMITS (empirically confirmed, unverified accounts, 2026-02-28):

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 181)May include surrounding context.

md
Requirements:
    Credentials file at path configured by `wechat_secrets_path` in config.json
    Default: ~/.wechat-article-writer/secrets.json -- {"appid": "wx...", "appsecret": "..."}
    pip install requests

KNOWN FIELD LIMITS (empirically confirmed, unverified accounts, 2026-02-28):

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/publish_via_api.py (reported line 17)May include surrounding context.

python
Requirements:
    Credentials file at path configured by `wechat_secrets_path` in config.json
    Default: ~/.wechat-article-writer/secrets.json -- {"appid": "wx...", "appsecret": "..."}
    pip install requests

KNOWN FIELD LIMITS (empirically confirmed, unverified accounts, 2026-02-28):

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/publish_via_api.py (reported line 39)May include surrounding context.

python
Requirements:
    Credentials file at path configured by `wechat_secrets_path` in config.json
    Default: ~/.wechat-article-writer/secrets.json -- {"appid": "wx...", "appsecret": "..."}
    pip install requests

KNOWN FIELD LIMITS (empirically confirmed, unverified accounts, 2026-02-28):

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/publish_via_api.py (reported line 159)May include surrounding context.

python
creds = load_credentials()
    token = get_access_token(creds)
    print(f"Access token obtained.")

    cover_path = os.path.join(draft_dir, args.cover)
    print(f"Uploading cover...")

Vague Triggers

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The publish trigger '发布{draft_id}' is too generic for a high-impact action that can push content to a live WeChat account. A short, natural phrase increases the chance of accidental or context-confused invocation, which could result in unauthorized publication or account misuse.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

The manifest explicitly references a default secrets file path containing WeChat app credentials, indicating the skill is designed to access sensitive credentials from local storage. While credential use is expected for this functionality, centralizing secrets in a predictable path increases exposure if the skill, logs, previews, or related tooling mishandle file contents.

Content

Scanner excerpt · skill.yml (reported line 220)May include surrounding context.

yaml
config:
  wechat_secrets_path:
    type: path
    default: ~/.wechat-article-writer/secrets.json
    description: Path to WeChat API credentials file containing appid and appsecret

# Skill metadata

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The README advertises automated publishing and draft-box modification using account credentials and browser automation, but it does not clearly warn users that running the skill can create, alter, or overwrite content in their WeChat Official Account. In a credentialed publishing workflow, lack of explicit consent and impact disclosure increases the risk of unintended account actions, especially if the command is run against the wrong account or with stale drafts.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The README explicitly states the Writer subagent is 'Chinese-first', which imposes a language preference. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is clearly justified and documented.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The setup instructions state that setup installs dependencies, creates a persistent systemd service, opens a preview endpoint on port 8898, writes configuration, and appends to AGENTS.md, but they do not prominently warn that local system configuration and filesystem state will be modified. Users may run setup assuming it is ephemeral, when it actually establishes a persistent service and changes host state, which is a meaningful safety and operational risk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The description specifies a "Chinese-first writing" workflow, which is a language constraint presented as a default behavior without offering the user a language choice. This is a natural-language policy issue because it forces a specific language/locale absent clear opt-in or documented justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill performs publishing through either stored WeChat API credentials or browser automation against a logged-in account, but the user-facing description does not clearly warn that these paths touch sensitive authentication material and a real publisher account. In an agent setting, lack of explicit disclosure can cause users to invoke account-affecting actions without understanding the credential and session exposure involved.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The document explicitly directs the agent to use API credentials stored in ~/.wechat-article-writer/secrets.json without any warning about secure storage, access controls, rotation, or avoiding unnecessary credential reads. In an agent skill context, normalizing silent credential use increases the chance of unintended secret exposure, misuse, or propagation into logs and downstream tooling.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% 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 · references/LESSONS_LEARNED.md (reported line 169)May include surrounding context.

Symptom: Article opens in WeChat editor showing literal text like \u591a\u667a\u80fd\u4f53\u6846\u67b618\u5c0f\u65f6 instead of 多智能体框架18小时.

Root cause: requests.post(..., json=payload) internally uses json.dumps(ensure_ascii=True) (Python default). This converts all non-ASCII characters to \uXXXX. WeChat's editor renders the raw escape sequences as text, not as Unicode characters.

Solution: Encode manually with ensure_ascii=False:

python

Static analysis

No suspicious patterns detected.