Back to skill

Security audit

Feed To Md Tool Free

Security checks for vulnerabilities and agentic risk

Overview

This RSS-to-Markdown skill has a coherent purpose, but its examples use unrestricted URL fetching and under-scoped local file writes that users should review carefully.

Review before installing or using this skill in an agent with access to private networks or important local files. Only use trusted public RSS/Atom URLs, keep output inside a dedicated directory, avoid title-derived filenames, and require confirmation before overwriting files or running generated scripts.

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

Error
Location
references/detail.md:18
Finding

Unrestricted RSS URL Fetching Enables Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: references/detail.md:18-27; additional occurrences in references/detail.md:129-130 and SKILL.md:254-258
Vulnerability Type: Server-Side Request Forgery through an unrestricted user-supplied URL
Risk Level: High

Vulnerable Code

python
def fetch(self, url, timeout=10):
    """获取RSS源"""
    try:
        response = requests.get(url, timeout=timeout, headers=self.headers)
        if response.status_code == 200:
            return response.content
        return None
    except Exception as e:
        print(f"获取失败:{e}")
        return None

The alternative examples contain the same behavior:

python
r = requests.get(url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
root = ET.fromstring(r.content)

Technical Analysis

The Skill legitimately needs outbound network access to retrieve RSS feeds. However, it passes the supplied URL directly to requests.get without validating:

  • The URL scheme
  • The destination hostname or resolved IP address
  • Loopback, private, link-local, reserved, and cloud metadata address ranges
  • Embedded URL credentials
  • Redirect destinations
  • DNS changes between validation and connection
  • Response size or expected content type

Consequently, the network capability exceeds the minimum privilege needed to access ordinary public RSS feeds. An attacker can provide a URL targeting services accessible from the Agent host but inaccessible from the attacker's own network.

requests follows redirects by default, so validating only an initial URL in a future partial fix would remain insufficient. Every redirect destination must also be checked.

The audit did not find evidence that the Skill intentionally sends local credentials or files to an external server. The flagged network behavior is primarily an unrestricted retrieval issue rather than confirmed sensitive-data exfiltration.

Attack Path

  1. An attacker asks the Agent to convert an attacker-selected R ...[truncated 1462 chars]
Remediation
View remediation

Remediation Suggestions

  1. Accept only https URLs unless HTTP support is explicitly required.
  2. Parse the URL before making a request and reject malformed URLs, embedded credentials, unsupported ports, and non-HTTP schemes.
  3. Resolve the hostname and reject every address in loopback, private, link-local, multicast, unspecified, reserved, and documentation ranges for both IPv4 and IPv6.
  4. Explicitly block infrastructure metadata destinations, including link-local metadata addresses.
  5. Disable automatic redirects or manually follow a small number of redirects while repeating the complete destination validation for every hop.
  6. Protect against DNS rebinding by ensuring that the address used for the connection is the validated address.
  7. Prefer an explicit allowlist of trusted RSS domains when the deployment permits it.
  8. Limit response size while streaming rather than loading an unbounded response into memory.
  9. Require an RSS or XML-compatible content type and reject unexpected response formats.
  10. Apply strict connection and read timeouts, not only a single general timeout.
  11. Run the fetcher in a network sandbox that cannot access localhost, private networks, or metadata services.
  12. Avoid returning raw internal response data or detailed network errors to untrusted users.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:138
Finding

Unsafe Output Filename Handling Permits Path Traversal and File Overwrite

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:138-153; attacker-controlled feed titles are passed as filenames at SKILL.md:213-219 and SKILL.md:343-349
Vulnerability Type: Path traversal and arbitrary writable Markdown file overwrite
Risk Level: Medium

Vulnerable Code

The save operation joins an explicit filename to the output directory without validating the resulting path:

python
def save(self, content, filename=None, feed_title=None):
    """保存Markdown文件"""
    if filename is None:
        if feed_title:
            safe_title = "".join(c for c in feed_title if c.isalnum() or c in (' ', '-', '_')).strip()
            filename = f"{safe_title}_{datetime.now().strftime('%Y%m%d')}.md"
        else:
            filename = f"feed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"

    filepath = os.path.join(self.output_dir, filename)

    with open(filepath, 'w', encoding='utf-8') as f:
        f.write(content)

    print(f"已保存:{filepath}")
    return filepath

Other examples derive the explicit filename from untrusted RSS item content:

python
for item in feed['items']:
    single_feed = {'title': item['title'], 'items': [item]}
    single_md = converter.convert(single_feed)
    saver.save(single_md, filename=f"{item['title'][:50]}.md")

A similar incremental-save example appears later:

python
for item in feed['items']:
    single = {'title': item['title'], 'items': [item]}
    md = MarkdownConverter().convert(single)
    saver.save(md, filename=f"{item['title'][:50]}.md")

Technical Analysis

The function sanitizes feed_title only when it generates a filename internally. When the caller supplies filename, no equivalent validation is performed.

RSS titles are external data controlled by the feed publisher. Passing an item title directly as filename allows path separators, traversal components, absolute paths on supported platforms, and platform-specific path syntax to reach os.path.join.

os.path.join ...[truncated 1900 chars]

Remediation
View remediation

Remediation Suggestions

  1. Apply the same strict sanitization to every filename, including explicitly supplied values.
  2. Treat RSS feed titles and item titles as untrusted data.
  3. Convert generated filenames to safe basenames and reject /, \, null bytes, drive prefixes, absolute paths, and . or .. path components.
  4. Use a conservative allowlist such as letters, numbers, spaces, hyphens, and underscores.
  5. Generate filenames independently of titles, such as with a UUID, and retain the original title only inside the Markdown document.
  6. Resolve both the output directory and candidate path before writing, then verify that the candidate remains a descendant of the output directory.
  7. Reject the operation if containment cannot be proven.
  8. Use exclusive file creation where overwriting is unnecessary, or require explicit user approval before replacing an existing file.
  9. Consider using os.open with platform-appropriate anti-symlink protections to reduce symbolic-link race risks.
  10. Run file export with access restricted to a dedicated output directory.

A containment check should follow this general design:

python
from pathlib import Path

base = Path(self.output_dir).resolve()
safe_name = sanitize_to_basename(filename)
target = (base / safe_name).resolve()

if base not in target.parents:
    raise ValueError("Output path escapes the configured directory")

with target.open("x", encoding="utf-8") as f:
    f.write(content)
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger keywords are extremely broad, including generic terms like document generation, conversion, design, and machine learning, which can cause the skill to activate for unrelated user requests. In an agent environment with file and exec tools, overbroad routing increases the chance that the skill is invoked unexpectedly and performs local file operations or command-driven workflows outside the user's intent.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The capability matrix states that batch conversion is not supported in the free version, and the limitations section repeats that multiple-feed batch conversion requires the professional edition. However, the example under learning-material organization iterates over a list of feed URLs and converts each one, which is exactly batch conversion behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill documents saving Markdown and JSON files locally but does not provide a clear warning about where data will be written, whether existing files may be overwritten, or that feed content may contain sensitive or untrusted text. In the context of an agent with filesystem access, silent local writes can lead to unintended persistence, clobbering user files, or storing unreviewed external content on disk.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The FAQ explicitly says the free edition cannot batch convert multiple subscription sources and only handles one source per run. In contrast, the example code at L229-L240 loops through multiple feed URLs and converts/saves each, creating a direct contradiction between stated intent and demonstrated behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The natural-language headings, descriptions, comments, and output strings throughout the markdown file are all Chinese-only. Under the language/locale policy, forcing a specific language without user opt-in or a documented region-specific justification is a policy concern.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

At L105 the docstring says '安全获取元素文本' ('safely get element text'), which implies a safety property. The code at L106-L107 only checks for element/text presence and strips text; it does not enforce XML safety, sanitization, or broader protections, so the documentation overstates what the function does.

Content

No source excerpt is available for this finding.