T09 · Insecure Skill Coding Practices
- Location
scripts/unified_fetch.py:562- Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill does what it claims, but it can fetch arbitrary URLs, send URLs to third-party extractors, and persist fetched content locally without enough scoping or confirmation.
Review before installing. Use it only for public article URLs, avoid private/internal links, prefer --dry-run for testing, and be aware that Tavily or Firecrawl may receive submitted URLs during fallback. Configure the vault path intentionally and treat saved raw copies as persistent local records.
scripts/unified_fetch.py:562Unrestricted URL Fetching Enables Server-Side Request Forgery
scripts/unified_fetch.py:519Predictable Shared Temporary File Permits Symlink and Content-Substitution Attacks
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
normalized_url = normalize_wechat_url(url) if 'mp.weixin.qq.com' in url else url
if platform_config:
normalized_url = strip_tracking_params(normalized_url, platform_config)
response = req.post(
'https://api.tavily.com/search',
json={
'api_key': api_key,
The skill performs sensitive operations including shell execution, network access, environment-variable use, and file writes, but it declares no explicit tool scope or permissions boundary. That creates an over-privileged execution model where an invoking agent may use more capabilities than the user expects, increasing the chance of unintended filesystem modification, data exposure, or risky command execution.
The skill description does not clearly warn that fetched content will be written into the user's Obsidian vault and that raw copies are retained. Users may unknowingly persist copyrighted, sensitive, or unwanted material in multiple locations, making accidental data retention and local privacy exposure more likely.
The trigger phrases are broad enough that ordinary discussion about saving or fetching articles could invoke the skill unintentionally. Because the skill can fetch remote content and write into the user's Obsidian vault, accidental activation can lead to unwanted network requests, content ingestion, and filesystem changes without clear user intent.
The skill instructs use of external services such as Tavily and Firecrawl but does not disclose that article URLs and possibly page content may be sent to third parties. This can expose sensitive links, research interests, or restricted content to external processors without informed consent, especially when automatic fallback silently switches to those services.
The manifest description is entirely in Chinese, which imposes a specific language/locale on users without any stated opt-in or region-specific justification. Under the policy, language constraints should either be optional for the user or clearly documented as necessary for a region-specific tool.
User-supplied URLs are sent to the third-party Tavily service with no explicit privacy notice or opt-in. This can expose private, internal, or sensitive URLs and associated access patterns to an external provider, which is particularly relevant for an agent skill that may process arbitrary user inputs.
This code makes an outbound request to Tavily and includes the user-supplied URL in the request body, creating external data transmission. In this skill's context, that is security-relevant because arbitrary article URLs may be private, internal, or sensitive, and users are not clearly prompted before that disclosure occurs.
if platform_config:
normalized_url = strip_tracking_params(normalized_url, platform_config)
response = req.post(
'https://api.tavily.com/search',
json={
'api_key': api_key,
'query': normalized_url,
The Firecrawl integration forwards a user-provided URL to an external CLI/tool that may in turn transmit it to a third-party service, but the script gives no explicit disclosure or consent checkpoint. This creates privacy and data-handling risk, especially if users provide confidential or internal article URLs.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
normalized_url = strip_tracking_params(normalized_url, platform_config)
output_file = f'/tmp/firecrawl_{int(time.time())}.md'
cmd = ['firecrawl', 'scrape', normalized_url, '--only-main-content', '-o', output_file]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, timeout=60)
if result.returncode != 0:
return None, f"Firecrawl 失败: {result.stderr[:200]}"
The script writes fetched remote content directly into a fixed local Obsidian vault path without an explicit confirmation or safety gate. In an agent context, this can cause unintended local persistence of sensitive, malicious, or unwanted content, and may overwrite user expectations about where data is stored.
The manifest description and the rest of the document consistently prescribe Chinese-language interaction and output conventions, but do not state that this is an optional locale choice or a region-specific requirement. Under the policy, forcing a specific language without user opt-in can be a natural-language policy issue.
Natural-language strings in the module docstring and CLI interface are presented in Chinese, and the script does not offer an alternate language or locale selection. This can be a language-policy issue where the skill effectively forces one language without giving the user a choice.
No suspicious patterns detected.