Back to skill

Security audit

Reading Widget Clawhub

Security checks for vulnerabilities and agentic risk

Overview

This reading widget appears purpose-built rather than malicious, but it mixes a local background service, agent settings access, and weak localhost controls in ways users should review before installing.

Review this before installing. Prefer providing WEREAD_API_KEY explicitly through a controlled environment or dedicated secret store, avoid putting an api_key field in config.json, and do not enable the LaunchAgent unless you want a long-running background process. The local helper should ideally be hardened to serve only widget.html and require a per-install token for /set-goal.

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

T09 · Insecure Skill Coding Practices

Warning
Location
server.py:36
Finding

Runtime Directory Exposure Can Disclose Configuration and API Credentials

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
server.py:41
Finding

Unauthenticated Localhost Endpoint Is Vulnerable to Cross-Site Request Forgery

Content
View full analysis
``` ### Technical Analysis The `/set-goal` endpoint changes persistent state without requiring authentication, a per-install token, or a CSRF token. It also does not validate the `Origin`, `Referer`, or `Host` headers. The client deliberately sends JSON data with a `Content-Type` of `text/plain`. This is a CORS-safelisted content type, allowing a ...[truncated 1917 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • 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 (30)

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
90% confidence
Finding

The skill instructs users to place an API key in a broad agent settings file under ~/.claw/settings.json or ~/.claude/settings.json, and elsewhere states that server.py/update.py will read from that file automatically. Accessing shared agent configuration expands the blast radius: any other skill, plugin, or tool with config-directory access may read or misuse the credential, turning this widget into a consumer of high-value shared secrets.

Content

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

https://cdn.weread.qq.com/skills/weread-skills.zip

text

解压后看 `SKILL.md` 的「鉴权」一节,按指引申请你自己的 `wrk-xxxxxxxx` key。拿到后写进 `~/.claw/settings.json`(Claude Code 用户为 `~/.claude/settings.json`;**别**写进 shell rc,免得泄露到其它进程):

```json
{

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
97% confidence
Finding

The skill instructs the agent to read from ~/.claw/settings.json or ~/.claude/settings.json, which are agent configuration directories that may contain secrets beyond the intended API key. Accessing high-value config stores is dangerous because a compromised or overly broad skill can exfiltrate or misuse unrelated credentials and settings.

Content

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

md
### Step 1 — 确认前置依赖

1. 检查 `WEREAD_API_KEY` 是否已设置:
   - 先看 `~/.claw/settings.json`(Claude Code 用户为 `~/.claude/settings.json`)的 `env` 字段
   - 再看当前 shell 环境变量
2. **如果没有 API key**:
   - 告诉用户 key 格式是 `wrk-xxxxxxxx`,绑定用户身份(vid)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- 让用户参考微信读书官方 skill 包获取:`https://cdn.weread.qq.com/skills/weread-skills.zip`,解压后看 `SKILL.md` 的"鉴权"章节

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
88% confidence
Finding

This finding is the same sensitive behavior at the same line: the script references ~/.claude/settings.json as part of a grep-based secret presence check. Even without printing file contents, a third-party installer touching agent secret/config stores violates least privilege and creates unnecessary trust in code that should not inspect broader agent state.

Content

Scanner excerpt · install.sh (reported line 18)May include surrounding context.

sh
chmod +x "$DEST/open-widget.sh"
[ -f "$DEST/config.json" ] || cp "$SRC/config.default.json" "$DEST/config.json"

if [ -z "$WEREAD_API_KEY" ] && ! grep -qs WEREAD_API_KEY "$HOME/.claw/settings.json" "$HOME/.claude/settings.json"; then
  echo ""
  echo "⚠️  WEREAD_API_KEY not set."
  echo "   Apply for one via WeRead Agent Gateway (see SKILL.md)."

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
90% confidence
Finding

This finding is the same sensitive behavior at the same line: the script references ~/.claude/settings.json as part of a grep-based secret presence check. Even without printing file contents, a third-party installer touching agent secret/config stores violates least privilege and creates unnecessary trust in code that should not inspect broader agent state.

Content

Scanner excerpt · install.sh (reported line 18)May include surrounding context.

sh
chmod +x "$DEST/open-widget.sh"
[ -f "$DEST/config.json" ] || cp "$SRC/config.default.json" "$DEST/config.json"

if [ -z "$WEREAD_API_KEY" ] && ! grep -qs WEREAD_API_KEY "$HOME/.claw/settings.json" "$HOME/.claude/settings.json"; then
  echo ""
  echo "⚠️  WEREAD_API_KEY not set."
  echo "   Apply for one via WeRead Agent Gateway (see SKILL.md)."

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
90% confidence
Finding

Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Content

Scanner excerpt · install.sh (reported line 22)May include surrounding context.

sh
echo ""
  echo "⚠️  WEREAD_API_KEY not set."
  echo "   Apply for one via WeRead Agent Gateway (see SKILL.md)."
  echo "   Then add to ~/.claw/settings.json (Claude Code: ~/.claude/settings.json) env, or run:"
  echo "   export WEREAD_API_KEY=wrk-xxxxxxxx"
  echo ""
  exit 0

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · server.py (reported line 31)May include surrounding context.

python
subprocess.run(
            [sys.executable, str(ROOT / "update.py")],
            cwd=str(ROOT),
            env=os.environ.copy(),
            timeout=60,
            check=False,
        )

Session Persistence

Medium
Category
Rogue Agent
Confidence
86% confidence
Finding

The README instructs users to install a LaunchAgent plist so server.py runs automatically in the background every login. Persistence mechanisms are security-sensitive because they survive restarts and can continue network access, local writes, and secret consumption without ongoing user awareness.

Content

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

想让数据一直在后台每 30 分钟刷新、不用每次手动开卡片,就把 server.py 设成开机自启:

bash
cp com.user.reading-widget.plist ~/Library/LaunchAgents/
# 把里面的 USERNAME 换成你的用户名,/usr/bin/python3 换成 `which python3` 的实际路径
launchctl load ~/Library/LaunchAgents/com.user.reading-widget.plist

Session Persistence

Medium
Category
Rogue Agent
Confidence
83% confidence
Finding

The README ties the LaunchAgent persistence mechanism to automatic reading of credentials from shared settings, which makes the persisted process more sensitive than a simple local UI helper. A background task that auto-starts and consumes secrets increases exposure if abused or if users forget it remains active.

Content

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

bash
cp com.user.reading-widget.plist ~/Library/LaunchAgents/
# 把里面的 USERNAME 换成你的用户名,/usr/bin/python3 换成 `which python3` 的实际路径
launchctl load ~/Library/LaunchAgents/com.user.reading-widget.plist

plist 里不放 API key —— server.py → update.py 会自己从 ~/.claw/settings.json 的 env 读。日志在 ~/Desktop/reading-widget/helper.log。

Session Persistence

Medium
Category
Rogue Agent
Confidence
83% confidence
Finding

The README ties the LaunchAgent persistence mechanism to automatic reading of credentials from shared settings, which makes the persisted process more sensitive than a simple local UI helper. A background task that auto-starts and consumes secrets increases exposure if abused or if users forget it remains active.

Content

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

bash
cp com.user.reading-widget.plist ~/Library/LaunchAgents/
# 把里面的 USERNAME 换成你的用户名,/usr/bin/python3 换成 `which python3` 的实际路径
launchctl load ~/Library/LaunchAgents/com.user.reading-widget.plist

plist 里不放 API key —— server.py → update.py 会自己从 ~/.claw/settings.json 的 env 读。日志在 ~/Desktop/reading-widget/helper.log。

Session Persistence

Medium
Category
Rogue Agent
Confidence
81% confidence
Finding

The README continues describing the persisted LaunchAgent and notes log locations, confirming ongoing background execution. Persistent background services are not inherently malicious, but in an agent-skill context they are security-relevant because they can maintain long-lived access to local state and credentials outside the user's immediate session.

Content

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

launchctl load ~/Library/LaunchAgents/com.user.reading-widget.plist

text

plist 里**不放** API key —— `server.py` → `update.py` 会自己从 `~/.claw/settings.json` 的 `env` 读。日志在 `~/Desktop/reading-widget/helper.log`。

### 另一种摆法:Übersicht

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill performs sensitive actions including reading agent config, writing files, invoking shell commands, accessing environment variables, and making network requests, yet it declares no explicit tool scope or permission boundaries. This increases the risk of overbroad agent execution because users and the platform cannot easily constrain what the skill is allowed to do.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are broad enough to match common requests about reading, widgets, or tracking habits, which can cause the skill to activate unexpectedly. Because the skill then performs installation, file copying, local server setup, and possible config access, accidental invocation materially expands risk.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · README.md (reported line 111)May include surrounding context.

md
- `server.py` → 本地小后台(发卡片 + 存目标 + 自动刷新)
   - `open-widget.sh` → 一键打开脚本
   - `config.default.json` → 重命名为 `config.json`
   - `com.user.reading-widget.plist` → launchd 模板(Step 4 用,先别动)
3. 跑一次 `WEREAD_API_KEY=xxx python3 ~/Desktop/reading-widget/update.py` 生成首版 `widget.html`
4. 如果脚本报错(key 无效、网络等),把错误原样给用户看,不要瞎猜原因

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · SKILL.md (reported line 38)May include surrounding context.

md
- `server.py` → 本地小后台(发卡片 + 存目标 + 自动刷新)
   - `open-widget.sh` → 一键打开脚本
   - `config.default.json` → 重命名为 `config.json`
   - `com.user.reading-widget.plist` → launchd 模板(Step 4 用,先别动)
3. 跑一次 `WEREAD_API_KEY=xxx python3 ~/Desktop/reading-widget/update.py` 生成首版 `widget.html`
4. 如果脚本报错(key 无效、网络等),把错误原样给用户看,不要瞎猜原因

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · SKILL.md (reported line 61)May include surrounding context.

md
- `server.py` → 本地小后台(发卡片 + 存目标 + 自动刷新)
   - `open-widget.sh` → 一键打开脚本
   - `config.default.json` → 重命名为 `config.json`
   - `com.user.reading-widget.plist` → launchd 模板(Step 4 用,先别动)
3. 跑一次 `WEREAD_API_KEY=xxx python3 ~/Desktop/reading-widget/update.py` 生成首版 `widget.html`
4. 如果脚本报错(key 无效、网络等),把错误原样给用户看,不要瞎猜原因

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · SKILL.md (reported line 90)May include surrounding context.

md
- `server.py` → 本地小后台(发卡片 + 存目标 + 自动刷新)
   - `open-widget.sh` → 一键打开脚本
   - `config.default.json` → 重命名为 `config.json`
   - `com.user.reading-widget.plist` → launchd 模板(Step 4 用,先别动)
3. 跑一次 `WEREAD_API_KEY=xxx python3 ~/Desktop/reading-widget/update.py` 生成首版 `widget.html`
4. 如果脚本报错(key 无效、网络等),把错误原样给用户看,不要瞎猜原因

Session Persistence

Medium
Category
Rogue Agent
Confidence
78% confidence
Finding

The skill explicitly proposes installing a persistent LaunchAgent that keeps the background service running across sessions. Although it does disclose the behavior and asks for consent, persistence is still a security-relevant capability because it creates long-lived code execution on the user's machine.

Content

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

md
> ⚠️ 这是给用户电脑加一个常驻 LaunchAgent,属于持久化系统改动。**装之前必须明确问一句用户同不同意**,不要默默 load。

1. 把 skill 目录里的 `com.user.reading-widget.plist` 复制到 `~/Library/LaunchAgents/`,并把里面的 `USERNAME` 换成真实用户名、`/usr/bin/python3` 换成 `which python3` 实际路径。
2. 加载:`launchctl load ~/Library/LaunchAgents/com.user.reading-widget.plist`

注意 plist 里**不放** `WEREAD_API_KEY`——`server.py` → `update.py` 会自己从 `~/.claw/settings.json` 的 `env` 读 key,别把 key 落进 plist。日志在 `~/Desktop/reading-widget/helper.log`。

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

The same persistence-enabling step also depends on a plist file copied into LaunchAgents, which creates an auto-start mechanism tied to user sessions. This is not inherently malicious, but it is a real persistence capability that should be treated as sensitive.

Content

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

md
> ⚠️ 这是给用户电脑加一个常驻 LaunchAgent,属于持久化系统改动。**装之前必须明确问一句用户同不同意**,不要默默 load。

1. 把 skill 目录里的 `com.user.reading-widget.plist` 复制到 `~/Library/LaunchAgents/`,并把里面的 `USERNAME` 换成真实用户名、`/usr/bin/python3` 换成 `which python3` 实际路径。
2. 加载:`launchctl load ~/Library/LaunchAgents/com.user.reading-widget.plist`

注意 plist 里**不放** `WEREAD_API_KEY`——`server.py` → `update.py` 会自己从 `~/.claw/settings.json` 的 `env` 读 key,别把 key 落进 plist。日志在 `~/Desktop/reading-widget/helper.log`。

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

The same persistence-enabling step also depends on a plist file copied into LaunchAgents, which creates an auto-start mechanism tied to user sessions. This is not inherently malicious, but it is a real persistence capability that should be treated as sensitive.

Content

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

md
> ⚠️ 这是给用户电脑加一个常驻 LaunchAgent,属于持久化系统改动。**装之前必须明确问一句用户同不同意**,不要默默 load。

1. 把 skill 目录里的 `com.user.reading-widget.plist` 复制到 `~/Library/LaunchAgents/`,并把里面的 `USERNAME` 换成真实用户名、`/usr/bin/python3` 换成 `which python3` 实际路径。
2. 加载:`launchctl load ~/Library/LaunchAgents/com.user.reading-widget.plist`

注意 plist 里**不放** `WEREAD_API_KEY`——`server.py` → `update.py` 会自己从 `~/.claw/settings.json` 的 `env` 读 key,别把 key 落进 plist。日志在 `~/Desktop/reading-widget/helper.log`。

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The invocation scenarios include ambiguous phrases like '装这个 widget' and '让我也用上' that do not clearly identify this specific skill. In context, ambiguous activation is risky because the skill can install software artifacts, start local services, and suggest persistence changes on the host.

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 · open-widget.sh (reported line 12)May include surrounding context.

sh
PY="$(command -v python3 || echo /usr/bin/python3)"

# Start the helper if it isn't already responding.
if ! curl -s -o /dev/null "http://127.0.0.1:$PORT/widget.html"; then
  "$PY" "$ROOT/server.py" >>"$ROOT/helper.log" 2>&1 &
  for i in $(seq 1 20); do
    curl -s -o /dev/null "http://127.0.0.1:$PORT/widget.html" && break

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · server.py (reported line 28)May include surrounding context.

python
def regenerate():
    """Re-run update.py in a fresh process so it picks up the latest config.json."""
    try:
        subprocess.run(
            [sys.executable, str(ROOT / "update.py")],
            cwd=str(ROOT),
            env=os.environ.copy(),

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The document declares lang="zh", and the visible UI text throughout the template is Chinese, indicating a fixed language/locale. There is no indication in this file that the user can opt into this locale or select an alternative language.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

The template performs an HTTP fetch to a localhost endpoint from inline JavaScript, transmitting user-modified data outside the page context. Even though the destination is local rather than remote, it is still external to the document and can reach an unintended service if that port is occupied by another application.

Content

Scanner excerpt · template.html (reported line 132)May include surrounding context.

html
<div class="progress-section">
      <div class="progress-row">
        <div class="progress-title">本月目标 · <input class="goal-input" id="goalInput" type="number" min="1" max="999" value="{{GOAL_HOURS}}" oninput="var g=Math.max(1,Math.min(999,parseInt(this.value)||1));var p=Math.min(100,Math.round({{MONTH_HOURS}}/g*100));document.getElementById('goalBar').style.width=p+'%';document.getElementById('goalPct').textContent=p+'%';clearTimeout(window.__goalTimer);window.__goalTimer=setTimeout(function(){fetch('http://127.0.0.1:47900/set-goal',{method:'POST',headers:{'Content-Type':'text/plain'},body:JSON.stringify({goal:g})}).catch(function(){});},600);"> 小时</div>
        <div class="progress-pct" id="goalPct">{{GOAL_PCT}}%</div>
      </div>
      <div class="bar"><div class="bar-fill" id="goalBar" style="width:{{GOAL_PCT}}%"></div></div>