Back to skill

Security audit

Article Fetcher(文章抓取+Notion/Obsidian知识库存档)

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but it should be reviewed because untrusted image URLs can be fetched from the runtime and uploaded to OSS, and LLM traffic is not forced to HTTPS.

Install only if you control the inputs and the configured cloud accounts. Avoid processing untrusted HTML/MHTML until image URL validation is added, use a least-privilege OSS bucket, configure LLM_BASE_URL with HTTPS only, and prefer the pinned requirements.txt installation path.

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
processors/image_processor.py:68
Finding

Unvalidated Image URLs Enable Server-Side Request Forgery and Response Exfiltration to OSS

Content
View full analysis
List[str]: images: List[str] = [] seen = set() for img in container.find_all('img'): ds = (img.get('data-src') or '').strip() src = (img.get('src') or '').strip() real = ds or src if not real or real.startswith('data:image'): continue real = real.split('?')[0] img['src'] = real if 'data-src' in img.attrs: del img['data-src'] if real in seen: continue seen.add(real) images.append(real) logger.debug(f"Extracted {len(images)} images") return images ``` ```python # main.py:37-45 image_urls = article_data.get('images', []) if image_urls: logger.info(f"Found {len(image_urls)} images; starting upload") url_mapping = image_processor.upload_images( image_urls, platform, article_id, article_url=url ) ``` ```python # processors/image_processor.py:42-44 response = self._download_image(img_url, platform_referer) result = self.bucket.put_object(oss_path, response.content) ``` ```python # processors/image_processor.py:68-80 @staticmethod def _download_image(img_url: str, fallback_referer: str): try: resp = requests.get(img_url, timeout=30) if resp.status_code != 403: resp.raise_for_status() return resp except requests.HTTPError: pass if fallback_referer: resp = requests.get( img_url, headers={'Referer': fallback_referer}, timeout=30 ) resp.raise_for_status() return resp raise RuntimeError(f"I ...[truncated 2539 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
utils/tag_extractor.py:77
Finding

LLM API Key and Article Content May Be Transmitted over Plaintext HTTP

Content
View full analysis
bool: """Whether LLM tag extraction is available""" return bool(self.llm_api_key and self.llm_base_url and self.llm_model) ``` ```python # utils/tag_extractor.py:77-96 endpoint = f"{config.llm_base_url.rstrip('/')}/chat/completions" headers = { 'Authorization': f'Bearer {config.llm_api_key}', 'Content-Type': 'application/json', } body = { 'model': config.llm_model, 'messages': [ {'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': user_prompt}, ], 'stream': False, } for attempt in range(3): try: timeout = [60, 90, 120][attempt] response = requests.post( endpoint, headers=headers, json=body, timeout=timeout ) ``` ### Technical Analysis The LLM integration treats any non-empty `LLM_BASE_URL` as valid. No validation requires HTTPS before the bearer token and article content are transmitted. If the base URL uses `http://`, the following information traverses the network without transport encryption: - `LLM_API_KEY` in the `Authorization` header - Configured model name - Article title - Extracted article text included in the prompt The implementation also relies on the default redirect behavior of `requests` without explicitly restricting redirect schemes or destinations. Although the LLM transmission is documented as optional, configuring the integration should not permit plaintext transmission of secrets. ### Attack Path 1. ...[truncated 1116 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:6
Finding

Skill Metadata Installer Does Not Enforce Audited Dependency Versions

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (35)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

该代码块的主要功能是把已经准备好的 article_data 写入 Notion 数据库,并将 HTML 正文转换为 Notion block 结构。它依赖 notion_api_key 和 notion_article_database_id,明确访问的是 Notion 资源。相比声明描述,这只是整个宣称能力中的一个子功能,而且还是“可选 Notion”部分;描述中的主要能力——多平台抓取、OSS 上传、LLM 提词、Obsidian 本地归档——在此代码中均没有体现。因此描述与该代码块实际行为存在明显不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

该代码块是一个“离线输入解析器”,作用是把 HTML 文本或 .mhtml 文件转成 article_data,包含标题、作者、发布日期、正文 HTML 和图片 URL 列表。它确实与文章处理管线相关,但只覆盖前期的离线解析环节,且实现上明显偏微信页面结构,并带有少量通用回退选择器。声明描述的是一个端到端技能:抓取多个平台文章、上传 OSS 图片、用 LLM 提取关键词、归档到 Obsidian/Notion。当前代码块没有网络抓取逻辑、没有 OSS 调用、没有 LLM 调用、没有本地知识库或 Notion 写入逻辑,因此与声明存在实质性能力不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
88% confidence
Finding

声明描述的是一个覆盖多个平台的完整知识归档流水线,而这段代码只包含微信抓取器的单一子功能。其实际能力局限于访问微信公众号文章页面、注入 cookies、必要时使用 Playwright 抓取 HTML,并解析文章元数据与图片 URL。代码中没有任何 OSS 上传、LLM 调用、Obsidian/Notion 写入逻辑,也没有涉及小红书、豆瓣、知乎抓取。因此就该代码块本身而言,描述未准确代表其实际行为,存在明显范围不一致。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

该代码片段的实际功能仅限于“小红书文章抓取”。它通过抓取页面 HTML、解析 window.INITIAL_STATE 或降级解析 DOM,提取文章元数据和图片 URL。声明描述的是一个更完整的多平台采集与归档系统,还包括 OSS 上传、LLM 提取关键词、Obsidian/Notion 存档等能力;这些能力在当前代码中均未出现。虽然代码行为属于声明中“抓取小红书”的一个子集,但就‘描述是否准确代表该代码块实际做什么’而言,声明明显比实际代码宽泛得多,且多个核心能力缺失,因此应判定为不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

代码行为与整体声明用途存在明显不一致。声明描述的是一个端到端知识归档工具,包含多平台抓取、图片上传到 OSS、LLM 关键词提取,以及同步到 Obsidian/Notion。当前代码块实际上只是一个知乎抓取模块,负责获取知乎页面 HTML 并解析出内容与图片链接,没有执行 OSS 上传、LLM 调用、知识库写入,也没有体现其他平台抓取逻辑。因此从“该技能声明是否准确代表此代码块实际行为”角度看,应判定为不匹配。

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · config.py (reported line 7)May include surrounding context.

python
import os
from dotenv import load_dotenv

# .env 加载策略:$AGENT_HOME > $HERMES_HOME > 当前目录
_agent_home = os.getenv('AGENT_HOME', os.getenv('HERMES_HOME', ''))
if _agent_home:
    load_dotenv(os.path.join(_agent_home, '.env'))

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · config.py (reported line 10)May include surrounding context.

python
# .env 加载策略:$AGENT_HOME > $HERMES_HOME > 当前目录
_agent_home = os.getenv('AGENT_HOME', os.getenv('HERMES_HOME', ''))
if _agent_home:
    load_dotenv(os.path.join(_agent_home, '.env'))
else:
    load_dotenv()

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The primary skill description and operational guidance are written in Chinese, and the document does not indicate multilingual support or provide an English alternative. Under the policy criteria, this is a natural-language locale/language constraint presented without user choice or explicit justification.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The description forces a specific language/locale in the skill's user-facing documentation. Under the policy, language constraints should either be optional or clearly justified; this file does not offer a language choice or note that the skill is intended only for Chinese-speaking users.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code file accesses a sensitive credential via config.notion_api_key and uses it to send article metadata and content to Notion. While there is an error log and docstrings describing archiving behavior, there is no explicit user-facing warning, confirmation, or visible disclosure about credential use or external data transmission in this file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The calls to self.notion.pages.create and self.notion.blocks.children.append send user-supplied article data to an external SaaS service. The code lacks a print/log/confirmation at the point of transmission, so users may not be clearly warned that their content and referenced image URLs are being uploaded to Notion.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The module docstring and class/method descriptions are written entirely in Chinese and present the skill as a Chinese-language archiver, with no indication that users may choose another language or locale. Under the stated policy, forcing a specific language without opt-in is a natural-language policy violation unless the locale restriction is clearly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This file contains user-facing natural-language content in a single language, such as the module docstring and later validation message, with no indication that Chinese is optional or required for a region-specific skill. That can violate language/locale policy because it forces one locale without user opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The raised ValueError includes a Chinese-only message that users will see when required environment variables are missing. Because no language selection or documented locale restriction is present, this is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The fetcher loads cookies from a local file, applies matching cookies to outbound request headers, and then sends them in an HTTP request. Although there are debug logs for developers, there is no user-facing prompt, warning comment, or disclosure that authentication/session cookies may be transmitted to remote sites.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This Python file contains natural-language strings such as the module/class docstrings and the error message entirely in Chinese. Under the policy, forcing a specific language without user opt-in or a clearly documented locale justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code loads and injects cookies from self.cookies_list into a Playwright browser context, which is a sensitive credential-handling operation. Although there is debug logging about the number of cookies injected, there is no user-facing warning, confirmation, or explanatory comment disclosing that stored authentication cookies will be used for browser-based access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code downloads remote images with requests and then uploads their contents to Alibaba OSS, which transmits user-supplied or system-handled data over the network. Although there is internal logging for success and failure, there is no user-facing prompt, warning comment, or docstring disclosure that image URLs and content will be fetched from third parties and stored in OSS.

Content

No source excerpt is available for this finding.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Confidence
55% confidence
Finding

Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.

Content

Scanner excerpt · processors/image_processor.py (reported line 49)May include surrounding context.

python
# 下载:优先空 Referer,403 时降级回退平台 Referer
                response = self._download_image(img_url, platform_referer)

                result = self.bucket.put_object(oss_path, response.content)

                if result.status == 200:
                    endpoint = config.aliyun_oss_endpoint.lstrip('https://').lstrip('http://')

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The code hard-codes Accept-Language: zh-CN,zh;q=0.9,en;q=0.8, which imposes a specific locale preference on all outgoing requests. This is a natural-language/locale policy concern because users are not given an opt-in or configuration choice, and the file does not document a region-specific justification for forcing Chinese as the default locale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The module-level natural-language description is written only in Chinese, which imposes a specific language choice without any visible opt-in or alternative. Under the policy, language constraints should either offer user choice or be clearly documented as justified and region-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code embeds user-facing natural-language behavior entirely in Chinese, including the module description and the LLM system/user prompts. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified, which is not present here.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
91% confidence
Finding

The module sends article text and optionally the title to a user-configured external LLM endpoint, which creates a real data exfiltration path. Even though the feature is optional and truncated to 12,000 characters, the code transmits potentially sensitive article content and bearer credentials to whatever base URL is configured, with no allowlist, trust validation, redaction, or explicit per-request consent.

Content

Scanner excerpt · utils/tag_extractor.py (reported line 96)May include surrounding context.

python
for attempt in range(3):
        try:
            timeout = [60, 90, 120][attempt]
            response = requests.post(endpoint, headers=headers, json=body, timeout=timeout)

            if response.status_code == 200:
                return response.json()['choices'][0]['message']['content']

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

This markdown file contains user-facing natural-language content exclusively in Chinese across its substantive entries. Under the policy rule for language/locale, a forced language without user opt-in can be a policy violation when no alternative language choice or justification is provided in the file.

Content

No source excerpt is available for this finding.