T09 · Insecure Skill Coding Practices
- Location
scripts/news_digest_v2/fetcher.py:734- 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 appears to be a real Chinese news digest tool, but its broad web fetching, disabled TLS checks, and optional LLM credential/content transmission need review before installation.
Install only if you are comfortable with the skill scraping external websites, keeping a local SQLite database of full article content, and writing digest files locally. Do not enable the LLM stage with valuable credentials unless the endpoint is HTTPS and trusted, and avoid running it on sensitive internal networks until URL allowlisting, redirect/IP validation, TLS verification, retention controls, and the missing package files are fixed.
scripts/news_digest_v2/fetcher.py:734Unrestricted URL Fetching Enables Server-Side Request Forgery
scripts/news_digest_v2/fetcher.py:304TLS Certificate Verification Is Disabled for News Fetching
scripts/news_digest_v2/stage2_5_llm_summary.py:27Configurable LLM Endpoint Can Expose API Credentials over Plaintext Transport
The code sends article content and titles to an external endpoint derived from NEWS_DIGEST_LLM_BASE_URL, which is taken directly from environment variables without any allowlisting or validation. In this skill, the payload can include scraped news content and source metadata, so a misconfigured or attacker-controlled base URL can cause silent exfiltration of collected data and API credentials to an unintended service.
)
try:
with urllib.request.urlopen(req, timeout=300) as resp:
result = json.loads(resp.read().decode('utf-8'))
return result.get('choices', [{}])[0].get('message', {}).get('content', '')
except Exception as e:
The declared description presents a full end-to-end news digest capability: scraping Chinese news sources, processing content, and generating daily summaries with citations/links. The supplied code, however, is a narrow internal processing stage labeled '阶段 2', explicitly handling only deduplication and keyword tagging via database functions (mark_duplicates, update_keywords_for_new). It prints job progress and counts, and even notes that summary output is moved to stage 3. There is no scraping, summarization, source attribution, or link output in this chunk. While deduplication and keyword tagging are consistent with 'processing,' the overall declared purpose materially overstates what this code actually does, so this chunk does not accurately represent the claimed skill behavior on its own.
Referenced artifact was not completely inspected
- `fetcher.py` 新增 `decode_response()` 函数,对已知 GBK 编码来源(人民日报海外版)强制使用 GBK 解码,从根源修复 Cyrillic 乱码
Referenced artifact was not completely inspected
- `stage2_5_llm_summary.py` 新增乱码标题检测,发现乱码时提示 LLM 从正文生成准确标题
The skill documents and invokes capabilities that read environment variables, access local files, write output files, and perform network scraping, but it does not declare any explicit tool scope or permissions boundary. This is dangerous because users and hosting platforms cannot easily evaluate or constrain what the skill is allowed to do, increasing the risk of unintended data access, file writes, or outbound requests during execution.
The manifest description explicitly scopes the skill to Chinese news sources and includes Chinese trigger phrases, but does not indicate that users can choose another language or locale. Under the stated policy, forcing a specific language or locale without opt-in is a natural-language policy concern unless the constraint is clearly documented and justified as region-specific.
The skill states that it produces output files, including a timestamped file on the user's desktop, but does not present this as a clear pre-execution warning in the description or quick-start flow. Unexpected writes outside the workspace can overwrite user data, leak sensitive summaries into synced folders, or violate user expectations about where agent-generated artifacts are stored.
The skill performs external website scraping and optionally sends article content to an LLM service when API settings are configured, but this data flow is not clearly surfaced as a user warning. This is dangerous because users may not realize the skill initiates outbound connections to third-party sites and may transmit scraped content to an external model provider, creating privacy, compliance, and network-risk concerns.
The module docstring explicitly states the skill performs web scraping and saves collected news to a database, which goes beyond the declared behavior of generating a news digest. This kind of capability mismatch is a real security concern because hidden or under-disclosed persistence can enable unauthorized data retention, broaden the attack surface, and violate least-privilege expectations for a summarization skill.
The file’s module docstring and operational messages are entirely in Chinese, which effectively fixes the skill’s language for maintainers and operators. Under the policy, forcing a specific language without opt-in or a documented regional justification is a natural-language policy violation.
This code file contains natural-language documentation exclusively in Chinese, beginning with the module docstring, and the pattern continues throughout comments and messages. Under the stated policy, forcing a specific language without user opt-in or a documented justification is a natural-language policy violation.
The manifest describes scraping, processing, and generating daily news digests, but this file creates and maintains a local SQLite database schema and persists article/keyword state. Persistent storage may be an implementation choice, but the manifest does not mention database creation or maintaining long-lived local state, so the code does more than the user-facing description claims.
Beyond scraping and summarization, the module actively rewrites article records by marking duplicates and storing similarity scores. This is a substantive record-management behavior that is not surfaced in the manifest, which only promises digest generation from Chinese news sources.
The module docstring and embedded natural-language comments/instructions are entirely in Chinese, indicating an implicit fixed-language experience. There is no visible opt-in, language selection, or justification that this skill is intended only for a Chinese-language or region-specific context.
This code explicitly disables TLS certificate verification for gov.cn requests, which permits man-in-the-middle interception or content tampering by a network attacker. For a scraper that ingests and republishes news content, this can poison downstream summaries, store falsified articles, or cause the system to trust attacker-controlled responses.
Setting verify_ssl = False establishes an insecure default for requests to a specific domain and normalizes bypassing TLS protections in production code. In a content aggregation pipeline, that means attacker-modified pages can be accepted as authentic, affecting stored articles, summaries, and any downstream decisions based on them.
current_headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
current_headers['Accept-Encoding'] = 'gzip, deflate'
# gov.cn 可能存在 SSL 策略,尝试不验证
verify_ssl = False
else:
verify_ssl = True
The SSL error fallback retries requests with verify=False, converting certificate failures into silent insecure connections. This removes the main protection against impersonated servers and lets an attacker inject malicious or misleading content into the news ingestion and summarization pipeline.
except requests.exceptions.SSLError:
# SSL 错误时尝试不验证证书
try:
response = requests.get(url, headers=HEADERS, timeout=timeout, verify=False)
response.raise_for_status()
return decode_response(response, url)
except Exception as e2:
This code fetches article content from external sites and, when LLM summarization is enabled, sends the title and full content to another component for summarization. While there is internal file logging, there is no user-facing warning, prompt, or comment disclosing that externally sourced content may be transmitted onward for LLM processing.
The fetcher stores full scraped article content in the database, not just the digest output described by the skill metadata. This expands data retention and creates an unnecessary persistence surface: if the database is later exposed or reused, the skill has accumulated more third-party content and potentially sensitive metadata than users would reasonably expect.
The module docstring says this is a "格式化输出模块" and the earlier functions perform text cleanup and output generation, but save_output also persists data to disk. That is a behavioral expansion from formatting into filesystem output, which is not reflected in this file's stated intent/documentation.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
desktop = os.path.join(os.environ.get('USERPROFILE', ''), 'Desktop')
today_str = datetime.now().strftime('%Y%m%d_%H%M%S')
out_file = os.path.join(desktop, f"新闻摘要_{today_str}.txt")
with open(out_file, 'w', encoding='utf-8') as f:
f.write(output_text)
saved_to.append(out_file)
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
desktop = os.path.join(os.environ.get('USERPROFILE', ''), 'Desktop')
today_str = datetime.now().strftime('%Y%m%d_%H%M%S')
out_file = os.path.join(desktop, f"新闻摘要_{today_str}.txt")
with open(out_file, 'w', encoding='utf-8') as f:
f.write(output_text)
saved_to.append(out_file)
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
desktop = os.path.join(os.environ.get('USERPROFILE', ''), 'Desktop')
today_str = datetime.now().strftime('%Y%m%d_%H%M%S')
out_file = os.path.join(desktop, f"新闻摘要_{today_str}.txt")
with open(out_file, 'w', encoding='utf-8') as f:
f.write(output_text)
saved_to.append(out_file)
The natural-language strings throughout the file are entirely in Chinese, including status messages and error output, and the script provides no user opt-in or alternative locale. Under the policy, forcing a specific language without user choice is a natural-language policy violation unless the locale constraint is explicitly justified.
The module description is written only in Chinese ("过滤规则配置 - 集中管理所有过滤规则"), and the surrounding comments/docstrings throughout the file are likewise Chinese-only. This imposes a fixed language choice in user-facing natural-language content without offering any language or locale option or documenting a justified regional constraint.