Back to skill

Security audit

Morning Brief · 每日AI早报

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent AI news brief tool, but it automatically sends LLM-selected content to Telegram and broadly marks blogwatcher items read without clear user review or opt-in.

Review before installing if the configured Telegram destination is shared or public. Keep tokens in environment variables or a secret manager, confirm the API hub and ClawdChat services are acceptable third parties, and consider modifying the script to remove `blogwatcher read-all`, validate links, escape Telegram formatting, and require review before scheduled delivery.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/morning-brief.py:257
Finding

Untrusted Feed Content Can Manipulate LLM Selection and Telegram Output

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

Tainted flow: 'req' from os.environ.get (line 288, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/morning-brief.py (reported line 56)May include surrounding context.

python
method="POST"
    )
    try:
        r = json.loads(urllib.request.urlopen(req, timeout=20).read())
        if r.get("success"):
            return r.get("data", {})
        else:

Tainted flow: 'req' from os.environ.get (line 288, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/morning-brief.py (reported line 140)May include surrounding context.

python
req = urllib.request.Request(url, headers={
            "User-Agent": "Mozilla/5.0 (compatible; RSS/1.0)",
        })
        resp = urllib.request.urlopen(req, timeout=15)
        content = resp.read().decode("utf-8", errors="replace")
        root = ET.fromstring(content)

Tainted flow: 'req' from os.environ.get (line 288, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/morning-brief.py (reported line 337)May include surrounding context.

python
req = urllib.request.Request(url, headers={
            "User-Agent": "Mozilla/5.0 (compatible; RSS/1.0)",
        })
        resp = urllib.request.urlopen(req, timeout=15)
        content = resp.read().decode("utf-8", errors="replace")
        root = ET.fromstring(content)

Tainted flow: 'req' from os.environ.get (line 288, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The script sends externally sourced article titles directly to a third-party LLM service, which creates an information disclosure boundary and exposes the decision logic to prompt-injection-by-content. In this skill context, many titles come from untrusted feeds and tool gateways, so attacker-controlled text can influence selection behavior or cause undesired downstream content to be published.

Content

Scanner excerpt · scripts/morning-brief.py (reported line 299)May include surrounding context.

python
},
            method="POST"
        )
        resp = urllib.request.urlopen(req, timeout=60)
        result = json.loads(resp.read())
        text = result["content"][0]["text"].strip()

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The documented purpose does not fully match the described behavior: it references undeclared InfoQ access, performs blogwatcher read-all state-changing actions, and claims an OpenRouter snapshot is included even though it may only be silently read. This is dangerous because users may authorize the skill expecting passive content aggregation, while it also modifies external state and accesses sources not clearly disclosed.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill declares broad operational behavior involving environment variables, file access, networking, and shell execution, but does not define an explicit tool scope such as permissions or allowed-tools. This weakens least-privilege controls and makes it harder for users or the platform to constrain what the skill can access, increasing the blast radius if the skill or a downstream dependency behaves unexpectedly.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation instructs sending collected content to Telegram but does not clearly warn that aggregated data will be transmitted to a third-party messaging service. Even if the content is mostly public news, summaries, metadata, or user-curated feeds can still reveal interests, subscriptions, and operational patterns, creating avoidable data leakage risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The setup instructions tell users to place bot tokens and API keys directly in script configuration without any security guidance. This encourages insecure secret handling practices, such as hardcoding credentials into files that may be checked into version control, copied into logs, or exposed to other local users.

Content

No source excerpt is available for this finding.

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 · scripts/morning-brief.py (reported line 209)May include surrounding context.

python
"""从 blogwatcher 获取最新未读文章"""
    try:
        # 先 scan
        subprocess.run(["blogwatcher", "scan"], capture_output=True, timeout=60)
        # 获取文章(默认显示未读)
        result = subprocess.run(
            ["blogwatcher", "articles"],

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 · scripts/morning-brief.py (reported line 211)May include surrounding context.

python
# 先 scan
        subprocess.run(["blogwatcher", "scan"], capture_output=True, timeout=60)
        # 获取文章(默认显示未读)
        result = subprocess.run(
            ["blogwatcher", "articles"],
            capture_output=True, text=True, timeout=10
        )

Ssd 4

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The prompt embeds untrusted article titles from RSS feeds, Reddit, Twitter, and tool-gateway responses directly into natural-language instructions for an LLM. Because LLMs can treat hostile content as instructions, an attacker can craft a title that biases ranking, injects misleading 'reason' text, or manipulates what gets posted to Telegram, making this more dangerous in an automated publish pipeline.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Collected article titles from multiple external sources are transmitted to a third-party LLM, and selected summaries are then pushed to Telegram, with no user-facing disclosure or approval step. In this skill's news-aggregation context the content is mostly public, so impact is not critical, but there is still a real privacy/data-governance risk and a chance of relaying malicious or unwanted content to downstream channels.

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 · scripts/morning-brief.py (reported line 332)May include surrounding context.

python
"disable_web_page_preview": True,
    }).encode()
    req = urllib.request.Request(
        f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage",
        data=payload,
        headers={"Content-Type": "application/json"},
        method="POST"

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 · scripts/morning-brief.py (reported line 345)May include surrounding context.

python
def mark_all_read():
    """标记所有文章为已读"""
    try:
        subprocess.run(["blogwatcher", "read-all"], capture_output=True, timeout=10)
    except Exception:
        pass  # blogwatcher 未安装时静默跳过

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The inline comment explicitly states that calling get_openrouter_top_apps() will only update the cache, but the function actually opens an existing local file and returns parsed entries. This is an active contradiction between the comment and the code's real behavior, not just missing detail.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The natural-language instructions and operational guidance are presented only in Chinese, which effectively forces a specific language for users without opt-in. The file does not indicate that the skill is intentionally region-specific or provide an alternative language option.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file-level description and all user-directed output behavior are written as a Chinese-only morning brief, with no indication that language is configurable or chosen by the user. Under the policy rule, forcing a specific language without opt-in is a natural-language locale policy violation.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest says the skill includes an OpenRouter leaderboard snapshot as part of the briefing workflow, which implies the skill itself gathers that data. Here the function only reads a pre-populated local JSON cache and does not fetch the snapshot directly; this is a mild behavior mismatch between the described capability and the implemented operation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.