Back to skill

Security audit

Super Rss Agent

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent RSS reader, but its URL-fetching safety checks are weak enough that users should review it before installing.

Install only if you are comfortable with an RSS tool making outbound requests from the agent host and storing subscription/article history locally. Avoid importing untrusted OPML files or adding attacker-controlled feeds in sensitive networks, disable auto_purge if you want manual retention control, and treat fetched article text as untrusted content when asking the agent to summarize it.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scanner.py:174
Finding
SSRF Validation Fails Open When DNS Resolution Fails or Times Out<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scanner.py:174-213` **Vulnerability Type**: Server-Side Request Forgery validation bypass **Risk Level**: High ### Vulnerable Code ```python def _validate_url_safe(url): """验证 URL 是否指向内网/私有网络。 如果不安全则抛出 SSRFError。""" parsed = urlparse(url) # 仅允许 http 和 https 协议 if parsed.scheme not in ('http', 'https'): raise SSRFError(f"Blocked scheme: {parsed.scheme} (only http/https allowed)") hostname = parsed.hostname if not hostname: raise SSRFError(f"No hostname in URL: {url}") # 将主机名解析为 IP 并检查是否在屏蔽范围内 # 使用线程池限制 DNS 解析时间,防止卡死 try: with ThreadPoolExecutor(max_workers=1) as dns_executor: future = dns_executor.submit( socket.getaddrinfo, hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM ) addr_infos = future.result(timeout=DNS_RESOLVE_TIMEOUT) except (socket.gaierror, TimeoutError): # DNS 无法解析或超时 — 放行,让 HTTP 请求自然失败 return for family, _, _, _, sockaddr in addr_infos: ip_str = sockaddr[0] try: ip = ipaddress.ip_address(ip_str) except ValueError: continue for blocked in _BLOCKED_IP_RANGES: if ip in blocked: raise SSRFError( f"Blocked: {hostname} resolves to {ip} (private/internal network)" ) ``` ### Technical Analysis The SSRF validator explicitly returns successfully when DNS resolution fails or exceeds its timeout. This is a fail-open security decision: the absence of a successful security check is treated as authorization to continue. After the validator returns, the HTTP client performs its own DNS lookup while establishing the connection. Consequently, a hostname that could not be verified by the preliminary lookup may still be resolved and contacted by `requests`. The HTTP request can therefore proceed without any verified assurance that its ...[truncated 1679 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed for every DNS failure or timeout: ```python except (socket.gaierror, TimeoutError) as exc: raise SSRFError(f"Unable to safely resolve {hostname}") from exc ``` 2. Resolve the hostname once and retain the validated addresses for the actual connection. Do not permit the HTTP library to independently resolve the hostname again. 3. Reject a hostname if any returned address is private, loopback, link-local, multicast, unspecified, reserved, or otherwise non-global. Prefer `ip.is_global` over a manually maintained partial denylist. 4. Connect only to a validated IP while preserving the original hostname for the HTTP `Host` header and HTTPS SNI/certificate verification. 5. Validate every redirect destination using the same fail-closed and address-pinning process. 6. Verify the actual connected peer address before accepting response data. 7. Disable environment-derived proxy settings unless explicitly required and secured, because a proxy can invalidate assumptions made by local DNS checks. 8. Add tests covering DNS timeouts, DNS errors, mixed public/private answers, redirect destinations, IPv4 and IPv6 addresses, and cloud metadata endpoints. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scanner.py:190
Finding
DNS Rebinding and Resolution TOCTOU Bypass in SSRF Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scanner.py:190-213, 228-241, 250-263` **Vulnerability Type**: DNS rebinding and time-of-check/time-of-use SSRF bypass **Risk Level**: High ### Vulnerable Code ```python try: with ThreadPoolExecutor(max_workers=1) as dns_executor: future = dns_executor.submit( socket.getaddrinfo, hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM ) addr_infos = future.result(timeout=DNS_RESOLVE_TIMEOUT) except (socket.gaierror, TimeoutError): # DNS 无法解析或超时 — 放行,让 HTTP 请求自然失败 return for family, _, _, _, sockaddr in addr_infos: ip_str = sockaddr[0] try: ip = ipaddress.ip_address(ip_str) except ValueError: continue for blocked in _BLOCKED_IP_RANGES: if ip in blocked: raise SSRFError( f"Blocked: {hostname} resolves to {ip} (private/internal network)" ) ``` The separately resolved HTTP connection is made as follows: ```python _validate_url_safe(url) remaining = deadline - time.monotonic() if remaining <= 0: raise TimeoutError(f"Request timeout before connecting to {url}") resp = requests.get( url, timeout=min(timeout, remaining), headers=REQUEST_HEADERS, allow_redirects=False, stream=True, ) ``` Redirects repeat the same check-then-resolve pattern: ```python redirect_url = urljoin(url, redirect_url) _validate_url_safe(redirect_url) url = redirect_url resp = requests.get( url, timeout=min(timeout, remaining), headers=REQUEST_HEADERS, allow_redirects=False, stream=True, ) ``` ### Technical Analysis The IP addresses approved by `_validate_url_safe()` are not bound to the subsequent network connection. Validation uses `socket.getaddrinfo()`, but `requests.get()` receives the original hostname and performs another DNS resolution when connecting. This creates a time-of-check/time-of-use gap. An attacker controlling authoritative DNS can provide a public address during ...[truncated 1869 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Eliminate the separate validation and connection resolutions. 2. Implement a transport that connects directly to an IP returned by the trusted validation lookup. 3. Preserve the original hostname for TLS SNI and certificate validation; do not disable certificate checks to implement IP pinning. 4. Reject the entire hostname if its DNS response contains any non-global address, even when other answers are public. 5. Check the connected socket's peer address before processing the response. 6. Apply the same pinned-resolution policy independently to every redirect destination. 7. Reject non-global IPv4 and IPv6 addresses using comprehensive address classification rather than only the existing range list. 8. Consider disallowing redirects to different origins unless cross-origin redirection is necessary. 9. Add automated rebinding tests in which the validation lookup returns a public address and the connection lookup attempts to return an internal address. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/scanner.py:358
Finding
Untrusted Feed Content Is Exposed to the Agent Without Prompt-Injection Boundaries<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scanner.py:358-379, 400-439`; `scripts/super_rss_agent.py:351-375, 581-597, 1149-1166`; `SKILL.md:257-263` **Vulnerability Type**: Indirect prompt injection through attacker-controlled RSS content **Risk Level**: Medium ### Vulnerable Code RSS titles and descriptions are accepted as remote, attacker-controlled text: ```python def _parse_rss_items(channel, limit=None, full_content=False, since=None): """解析 RSS 2.0 channel 条目。""" items = [] for item in channel.findall('item'): title = item.findtext('title', 'No Title') link = item.findtext('link', '') pub_date_str = item.findtext('pubDate', '') desc = item.findtext('description', '') pub_date = None if pub_date_str: try: pub_date = _normalize_date(parsedate_to_datetime(pub_date_str)) except Exception: pass if since and pub_date and pub_date < since: continue content = None if full_content: elem = item.find(f'{CONTENT_NS}encoded') if elem is not None and elem.text: content = strip_html(elem.text) summary = strip_html(desc) if desc else '' items.append({ "title": title, "url": link, "link": link, "published_date": pub_date, "date_str": pub_date_str, "summary": summary[:300] + "..." if len(summary) > 300 else summary, "content": content, }) ``` Atom content is handled similarly: ```python for entry in root.findall(f'{ATOM_NS}entry'): title = entry.findtext(f'{ATOM_NS}title', 'No Title') link = '' link_nodes = entry.findall(f'{ATOM_NS}link') for ln in link_nodes: rel = ln.get('rel', 'alternate') if rel == 'alternate' and ln.get('href'): link = ln.get('href') break if not link and link_nodes: ...[truncated 4513 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add an explicit security rule to `SKILL.md` stating that all feed metadata, article text, scraped HTML, and linked page content are untrusted data. 2. Require the Agent never to follow instructions found inside fetched content, even when they claim to be system messages, administrator directions, tool requests, or continuation instructions. 3. Delimit remote content clearly, for example: ```text BEGIN UNTRUSTED FEED CONTENT ... END UNTRUSTED FEED CONTENT ``` 4. Precede summarization with a fixed instruction to extract facts only and ignore commands, requests, policies, or tool instructions appearing inside the content. 5. Separate retrieval from privileged actions. Do not permit article content to determine which local tools, files, credentials, or URLs the Agent accesses. 6. Require explicit user confirmation before any action derived from remote article text. 7. Apply strict length limits to titles, summaries, and full content before printing or storing them. 8. Record provenance for each content field so the Agent can distinguish remote text from trusted Skill instructions. 9. Test the workflow with adversarial feed entries containing prompt-injection patterns and verify that the Agent only summarizes them as quoted data. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (16)

Missing User Warnings

High
Confidence
95% confidence
Finding
The purge_articles method executes a DELETE against stored articles and commits the change, making it an irreversible destructive operation. Although the docstring explains the method, this code file provides no user-facing warning, prompt, or other disclosure for the data deletion.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill documents commands that perform network access and write persistent local state, but the manifest shown in SKILL.md does not declare any explicit tool scope, permissions, or allowed-tools boundary. That creates a trust and review gap: an agent may invoke broader capabilities than a user expects, especially because the skill also references auxiliary fetching tools and automation.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This markdown file consistently mandates a single language for descriptions, examples, and operational guidance, but it does not indicate that the skill is region-specific or that users may opt into another language. That creates a natural-language policy concern because the skill effectively forces one locale without user choice.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill prominently supports automatic article retrieval, optional external fetching tools, and persistent SQLite storage, yet it does not give a clear upfront privacy and side-effect warning. Users may not realize that using the skill causes outbound requests to third-party sites and stores browsing/subscription history locally, which can expose sensitive interests or internal URLs if used with private feeds.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The request headers force `Accept-Language: en-US,en;q=0.5`, which imposes a specific language/locale preference regardless of the user's own settings or content locale. This is a natural-language policy concern because the file otherwise contains no user choice, opt-in, or region-specific justification for overriding locale.

Tainted flow: 'url' from requests.get (line 259, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
redirect_url = urljoin(url, redirect_url)
        _validate_url_safe(redirect_url)
        url = redirect_url
        resp = requests.get(
            url, timeout=min(timeout, remaining), headers=REQUEST_HEADERS,
            allow_redirects=False, stream=True,
        )
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The module-level description and many user-oriented docstrings/comments are written exclusively in Chinese, which indicates a language-specific presentation without any visible mechanism for user language selection. Under the stated policy, forcing a specific language without opt-in can be a locale/language policy violation.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The remove_blog method deletes blog records, and due to the foreign key cascade, associated articles may also be deleted. While there is an internal docstring describing the behavior, there is no user-facing confirmation, prompt, or visible disclosure in this file for this destructive action.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The mark_all_read method can update all unread articles globally or by category/blog in a single call, which is a potentially high-impact state-changing operation. This file contains no visible prompt, log, or user disclosure warning that such a bulk update is occurring.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This Python skill presents its top-level description, usage text, and extensive command/help output in Chinese, and there is no visible option for the user to select another language or locale. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
该技能说明从开头即完全以中文呈现,未见提供多语言选项、用户语言偏好机制,或声明这是仅面向特定中文用户群的区域性技能。按规则,强制单一语言且无用户选择可构成语言/locale 策略问题。

Missing User Warnings

Low
Confidence
88% confidence
Finding
The scan workflow combines automatic network fetching, background/silent execution, and automatic deletion of stored content after scans, but the command documentation does not present these side effects together where users invoke scan. This can lead to unintended network activity and unexpected data loss or audit gaps, especially when run via cron or silent mode.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.20.0
defusedxml>=0.7.0
beautifulsoup4>=4.9.0
Confidence
95% confidence
Finding
The dependency is specified with only a lower bound, so builds may resolve to different versions over time. This weakens supply-chain reproducibility and can accidentally introduce vulnerable or breaking releases, especially for a network-facing RSS tool that fetches remote content.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding
The manifest does not pin requests, and that package has multiple known advisories across versions, so there is no assurance that deployments avoid affected releases. Because this skill retrieves remote URLs and may handle redirects, credentials, and attacker-controlled endpoints, an unsafe requests version could materially increase exposure.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.20.0
defusedxml>=0.7.0
beautifulsoup4>=4.9.0
Confidence
95% confidence
Finding
The dependency is not pinned to a specific version, which makes installations non-reproducible and increases supply-chain risk. Although defusedxml is a defensive library, leaving it unpinned can still result in unexpected version changes or regressions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.20.0
defusedxml>=0.7.0
beautifulsoup4>=4.9.0
Confidence
95% confidence
Finding
Using an unpinned beautifulsoup4 version allows future installs to pull different releases, reducing build determinism and increasing supply-chain exposure. In a scraper/RSS reader that parses untrusted HTML, unexpected parser behavior changes can affect security posture or stability.

Static analysis

No suspicious patterns detected.