Back to skill

Security audit

Web Publisher Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its publishing purpose, but users should review it carefully because sensitive login/configuration links are relayed from the server without strong local origin validation.

Install only if you trust the tools.siping.me service with article content, uploaded local files, publishing authority, and WeChat configuration. When the agent shows login or AppSecret setup links, verify they are HTTPS links on the expected tools.siping.me domain before opening them, and prefer draft mode unless you explicitly intend to publish.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.js:122
Finding
Remote-Provided Browser Links Are Not Restricted to Trusted HTTPS Origins<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.js:122-131`, with unsafe values used at `scripts/run.js:1154, 1192, 1203, 1506-1539, 1564-1579` **Vulnerability Type**: Insufficient validation of security-sensitive browser links **Risk Level**: Medium ### Vulnerable Code ```js function pickLink(data) { if (!data || typeof data !== 'object') return ''; const candidates = [ data.url, data.link, data.configureUrl, data.configUrl, data.verifyUrl ]; return candidates.find((value) => typeof value === 'string' && /^https?:\/\//.test(value)) || ''; } ``` The WeChat configuration handler passes the remotely supplied value directly to the user and embeds it in an instruction intended for the Agent: ```js const link = pickLink(data); if (!link) { console.error(JSON.stringify({ success: false, error: '服务器没有返回可打开的 AppID/AppSecret 配置短链', responseKeys: data && typeof data === 'object' ? Object.keys(data) : [] }, null, 2)); process.exit(1); } process.stderr.write('\n请在浏览器中打开以下链接填写 AppID / AppSecret:\n'); process.stderr.write(` ${link}\n`); console.log(JSON.stringify({ success: true, url: link, serverIps, instruction: `[AI必读] 两件事都必须做完,缺一不可:(1)把下面这个完整 URL 原文粘贴给用户,不要改写、不要只输出 Markdown 超链接文字、不要用"点击此处"替代。用户需要在浏览器中打开这个 URL 填写 AppID/AppSecret。URL:${link}${ipBlock}` }, null, 2)); ``` The login flow also consumes and prints `verifyUrl` without applying `pickLink()` or equivalent origin validation: ```js const { deviceCode, userCode, verifyUrl, expiresInSec } = initResp.data; const expiresAt = Date.now() + expiresInSec * 1000; flushStderr('\n请在浏览器中打开以下链接,确认绑定到你的账号:\n'); flushStderr(` ${verifyUrl}\n`); flushStdout(JSON.stringify({ success: true, pendingCheckpoint: true, verifyUrl, userCode, expiresInSec, expiresAt, pendingPath: LOGIN_PENDING_PATH, credentialsPath: CREDENTIALS_PATH, instruction: '[AI必读] 请把 verifyUrl 完整 URL 原文交给用户,并附上 userCode。用户在浏览器点击确认后,调用 `web-publisher login-status` 完成登录 ...[truncated 2409 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create one validator for every browser-facing URL returned by the service: ```js function trustedBrowserLink(value, allowedPaths) { if (typeof value !== 'string') return ''; let url; try { url = new URL(value); } catch { return ''; } if ( url.protocol !== 'https:' || url.origin !== 'https://tools.siping.me' || url.username || url.password || url.hash ) { return ''; } if (!allowedPaths.some((prefix) => url.pathname.startsWith(prefix))) { return ''; } return url.href; } ``` 2. Apply it separately with narrowly scoped paths: - Login: `/skill/bind` - WeChat configuration: the exact expected WeChat configuration route - Wrapper configuration: the exact expected wrapper route 3. Reject HTTP links rather than relying on users or browsers to upgrade them. 4. Validate `verifyUrl` before writing the pending checkpoint or displaying any login instructions. 5. Do not emit “relay verbatim” instructions unless the URL has passed local origin and path validation. 6. Add regression tests covering: - HTTP downgrade links. - Foreign origins. - Look-alike domains. - Embedded credentials. - protocol-relative URLs. - unexpected paths. - malformed and encoded URLs. ]]>

T08 · Insecure Dependencies

Note
Location
config.json:190
Finding
Optional Fallback Executes an Unaudited npm Dependency Without a Project-Supplied Lockfile<![CDATA[ ## Vulnerability Details **File Location**: `config.json:190-197`; related installation guidance appears in `README.md:24`, `SKILL.md:446`, and `SKILL.md:731` **Vulnerability Type**: Third-party dependency and supply-chain exposure **Risk Level**: Low ### Vulnerable Configuration ```json "recommendedSkills": [ { "name": "news-to-markdown", "type": "npm-cli", "install": "npm install --ignore-scripts --save-exact news-to-markdown@3.3.1", "purpose": "本地兜底:服务端 pipeline 因云端 IP 被反爬站点挡掉时(小红书、部分知乎专栏、登录墙、海外站点),改用本地 IP 抓取 URL → Markdown", "required": false } ] ``` The documentation subsequently recommends invoking the locally installed executable: ```text npm install --ignore-scripts --save-exact news-to-markdown@3.3.1 ... use that directory's node_modules/.bin/news-to-markdown ``` ### Technical Analysis The dependency is optional, pinned to an exact direct-package version, and installed with npm lifecycle scripts disabled. These are meaningful safeguards and prevent this issue from affecting the core Skill automatically. However, the project does not supply a reviewed lockfile or integrity-pinned artifact for this fallback. Exact pinning of the top-level package does not necessarily freeze or verify its transitive dependency graph. Disabling lifecycle scripts prevents install-time scripts but does not make runtime JavaScript safe: invoking the package’s CLI executes its package code and imported dependencies with the current user’s permissions. The documentation itself states that the third-party tool is outside this Skill’s audit scope and that version pinning does not constitute a security audit. Therefore, users who enable the fallback cross from the audited, dependency-free CLI into an independently supplied execution path. ### Attack Path 1. The normal remote conversion pipeline fails for a protected or anti-bot website. 2. The user or Agent follows the documented optional fallback instructions. 3. npm retrieves `news-to-m ...[truncated 1297 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Keep the fallback explicitly optional and require user confirmation before installation or first execution. 2. Publish a reviewed installation directory containing: - `package.json` - `package-lock.json` - Exact package versions - npm integrity hashes 3. Install reproducibly with: ```bash npm ci --ignore-scripts ``` 4. Audit the direct package and complete transitive dependency graph before recommending it as a trusted fallback. 5. Re-audit and regenerate the lockfile for every approved update. 6. Consider vendoring a reviewed implementation or distributing a signed, checksum-verified artifact. 7. Run the fallback with constrained filesystem and network access where the host platform supports sandboxing. 8. Avoid exposing unrelated environment variables or credential directories to the fallback process. 9. Clearly distinguish the security boundary in user-facing output: the fallback is third-party local code and is not covered by the core Skill audit. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (5)

Intent-Code Divergence

Medium
Confidence
84% confidence
Finding
The skill contains contradictory guidance about whether local Markdown can be passed to draft/publish. In a security-sensitive agent workflow, this ambiguity can cause the agent to route local files unexpectedly to the remote publishing service, potentially exposing sensitive local content or wasting credits through failed/repeated calls.

Credential Access

High
Category
Privilege Escalation
Content
"args": [
        "login-status"
      ],
      "description": "完成登录的第二步:读取 pending checkpoint -> 一次性 POST /skill/device/poll -> bound 时把凭证写到 credentials.json 并删除 checkpoint,然后 GET /skill/whoami 拿账号信息和微信公众号配置状态。状态:logged-in(账号有效 **且** 公众号 AppID/AppSecret 已配置,可以发布) / logged-in-no-wechat(账号绑了但还没配置公众号,**AI 必须接着提示用户跑 `wechat config`**,draft / publish 在这个状态下会失败) / awaiting-browser-confirm(用户还没点确认) / expired-pending / invalid-credentials(apiKey 失效,请 login --force) / polling-failed / persist-failed / not-logged-in / logged-in-unverified。Exit code 0 表示 logged-in / logged-in-no-wechat / awaiting-browser-confirm,其他状态 exit 1。"
    },
    "logout": {
      "script": "./scripts/run.js",
Confidence
96% confidence
Finding
This command explicitly retrieves credentials from a remote service and persists them to `credentials.json`. Any skill that stores reusable API credentials locally introduces a sensitive secret-handling surface: compromise of the local file, overbroad file access, or accidental disclosure could enable unauthorized publishing actions under the user's account.

Credential Access

High
Category
Privilege Escalation
Content
"type": "config-only",
    "autoInstall": true,
    "riskLevel": "low",
    "riskReason": "此 Skill 仅通过 HTTP API 调用远程服务,不安装任何第三方包,不调用任何外部二进制,不使用 child_process / 不 spawn 任何子进程。凭证存储在 ~/.web-publisher/credentials.json (mode 0600);登录过程的临时 device-code 存在 ~/.web-publisher/login-pending.json (mode 0600)。draft / publish / convert 接受本地文件路径时,CLI 只读 basename + 文件字节并 multipart 上传到服务端,不带绝对路径。",
    "requiresApproval": false
  },
  "dependencies": {
Confidence
95% confidence
Finding
The manifest downplays risk as `low` while acknowledging local storage of credentials and upload of user-supplied files to a remote service. This mismatch can reduce user scrutiny and lead to execution without appropriate review, increasing the chance of credential misuse or unintended data transfer.

Credential Access

High
Category
Privilege Escalation
Content
"permissions": {
    "filesystem": {
      "read": [
        "~/.web-publisher/credentials.json",
        "~/.web-publisher/login-pending.json",
        "<user-supplied input file passed to draft|publish|convert>",
        "<user-supplied --out path written by convert>"
Confidence
97% confidence
Finding
Declaring read access to `~/.web-publisher/credentials.json` and pending login state gives the skill access to persistent authentication material. Even if intended for normal operation, this is a real sensitive-permissions exposure because a compromised or misused skill invocation could read reusable secrets and act as the user.

Credential Access

High
Category
Privilege Escalation
Content
"<user-supplied --out path written by convert>"
      ],
      "write": [
        "~/.web-publisher/credentials.json",
        "~/.web-publisher/login-pending.json",
        "<user-supplied --out path passed to convert>"
      ]
Confidence
97% confidence
Finding
Write access to `~/.web-publisher/credentials.json` allows the skill to create or overwrite authentication state. If abused, this could implant attacker-controlled credentials, corrupt login state, or manipulate which remote account future publish operations use.

Static analysis

No suspicious patterns detected.