Back to skill

Security audit

News Brief - 新闻简报

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Chinese news scraper, but it needs review because it can fetch arbitrary web URLs and its private-network protections are incomplete or configurable off.

Install only if you are comfortable with a skill that fetches web content from URLs supplied to it. Keep NEWS_CN_BLOCK_PRIVATE enabled, use NEWS_CN_ALLOW_HOSTS to restrict destinations to trusted news domains, avoid arbitrary URLs from untrusted prompts, and consider pinning beautifulsoup4 before use.

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

T09 · Insecure Skill Coding Practices

Error
Location
fetch.py:196
Finding

SSRF Protection Can Be Bypassed Through Redirects and DNS Rebinding

Content
View full analysis
None: """ URL 访问安全检查: - 仅 http/https - 可选白名单 NEWS_CN_ALLOW_HOSTS - 默认拦截本机/私网/链路本地等地址(NEWS_CN_BLOCK_PRIVATE=0 可关闭) """ p = urlparse(url) if p.scheme not in ("http", "https"): raise ValueError("only http(s) URLs allowed") host = (p.hostname or "").strip().lower() if not host: raise ValueError("url host is empty") if not _host_allowed_by_env(host): raise ValueError("host not allowed by NEWS_CN_ALLOW_HOSTS: %s" % host) if not _env_on("NEWS_CN_BLOCK_PRIVATE", "1"): return if host == "localhost" or host.endswith(".localhost"): raise ValueError("blocked private/local host: %s" % host) ips = _resolve_host_ips(host) if not ips: # 解析失败时保持安全默认,避免未知主机绕过 raise ValueError("dns resolve failed for host: %s" % host) bad = [ip for ip in ips if _ip_is_privateish(ip)] if bad: raise ValueError("blocked private/local address for host %s: %s" % (host, ",".join(bad))) def _fetch_url(url: str, timeout: float, max_bytes: int = 3_500_000) -> bytes: _validate_remote_url(url) ua = os.environ.get( "NEWS_CN_UA", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", ) req = urllib.request.Request( url, headers={ "User-Agent": ua, "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", # 避免 urllib 不解压 gzip 导致乱解析 "Accept-Encoding": "identity", }, ) ctx = ssl.create_default_context() with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp: ...[truncated 2609 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
fetch.py:211
Finding

Private-Network Request Protection Can Be Completely Disabled

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
SKILL.md:20
Finding

BeautifulSoup Dependency Is Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Tainted flow: 'req' from os.environ.get (line 230, 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 · fetch.py (reported line 241)May include surrounding context.

python
},
    )
    ctx = ssl.create_default_context()
    with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
        data = resp.read(max_bytes + 1)
    if len(data) > max_bytes:
        raise ValueError("response too large (max_bytes=%s)" % max_bytes)

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding

Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill documents network access, environment-variable controls, and local script execution, but it does not declare any explicit tool scope or permissions boundaries. This creates an authorization gap where an agent may invoke broader capabilities than intended, increasing the risk of unexpected network access, SSRF-like fetching of attacker-supplied URLs, or use of sensitive environment configuration without clear policy enforcement.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The description includes broad trigger phrases like '类似中文新闻聚合时,使用本技能', which can cause over-broad activation for loosely related requests. In this skill, that is more concerning because activation leads to network fetching of user-influenced URLs and optional agent-assisted web access, so an ambiguous trigger can expand exposure to untrusted remote content and unintended data retrieval.

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 · fetch.py (reported line 605)May include surrounding context.

python
all_items, errors = _cmd_fetch_pages(req)
        used_mode = "pages"
    else:
        plist = req.get("pages")
        flist = req.get("feeds")
        has_pages = isinstance(plist, list) and len(plist) > 0
        has_feeds = isinstance(flist, list) and len(flist) > 0

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 · fetch.py (reported line 607)May include surrounding context.

python
all_items, errors = _cmd_fetch_pages(req)
        used_mode = "pages"
    else:
        plist = req.get("pages")
        flist = req.get("feeds")
        has_pages = isinstance(plist, list) and len(plist) > 0
        has_feeds = isinstance(flist, list) and len(flist) > 0

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The HTTP request hard-codes the Accept-Language header to prefer zh-CN/zh, which imposes a specific language/locale policy on remote interactions. This is a natural-language policy concern because the skill does not offer the user a locale choice or document this as a required regional constraint.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.