T09 · Insecure Skill Coding Practices
- Location
scripts/save-to-notion.py:43- Finding
Unrestricted User-Controlled 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 has a coherent Notion-saving purpose, but it can automatically fetch user-supplied URLs and create persistent Notion records with insufficient scoping and safeguards.
Review this before installing if your agent may handle private links, internal URLs, or sensitive repositories. Use it only with a tightly scoped Notion integration token and require explicit confirmation before fetching a URL or creating a Notion page. Avoid using the curl template with untrusted page text unless JSON is generated by a safe serializer.
scripts/save-to-notion.py:43Unrestricted User-Controlled URL Fetching Enables Server-Side Request Forgery
SKILL.md:80Documented Dynamic Curl Pattern Can Enable Shell Command Injection
The script performs an outbound fetch to a user-supplied URL in extract_cover_image() with no allowlist or private-address validation. This creates an SSRF primitive that can be abused to probe internal services, access cloud metadata endpoints, or force the host running the skill to make arbitrary network requests.
url,
headers={'User-Agent': 'Mozilla/5.0 (compatible; NotionTool/1.0)'}
)
with urllib.request.urlopen(req, timeout=10) as response:
html = response.read().decode('utf-8', errors='ignore')
# Parse meta tags
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method='POST'
)
with urllib.request.urlopen(req) as response:
result = json.loads(response.read().decode('utf-8'))
return {
'success': True,
The skill description says to use this skill whenever the user provides ANY tool URL and mentions generic save/add/bookmark phrasing. That activation scope is overly broad and can cause the agent to invoke the skill for routine bookmarking or unrelated save requests, leading to unintended web fetching and creation of persistent Notion records without sufficiently specific user intent.
The skill does not clearly warn users that it will fetch the provided URL, extract metadata/content, and send structured data to Notion where it becomes a persistent record. That lack of disclosure undermines informed consent and can expose sensitive URLs, private repo metadata, or internal tool references to an external service.
The skill body is written to operate in Chinese and provides Chinese-only user-facing response templates such as the success and error messages. There is no indication that language is selectable or that the locale restriction is required for a region-specific purpose, which can violate language/locale policy expectations.
The instruction to immediately use the skill for broad categories of links and generic trigger words removes ambiguity checks and pushes the agent toward automatic execution. In context, that means external fetching and third-party transmission may occur even when the user did not clearly request storage in Notion, increasing privacy and integrity risk.
The direct reference to the Notion API endpoint indicates outbound data transfer to an external service. In the skill context this is not inherently malicious, but it increases risk because the skill also instructs automatic fetching and packaging of remote page metadata, which can leak sensitive information if users provide private or internal links.
使用以下 API 调用(一次性创建页面,同时设置封面和正文图片):
curl -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2021-05-13" \
-H "Content-Type: application/json" \
The direct reference to the Notion API endpoint indicates outbound data transfer to an external service. In the skill context this is not inherently malicious, but it increases risk because the skill also instructs automatic fetching and packaging of remote page metadata, which can leak sensitive information if users provide private or internal links.
使用以下 API 调用(一次性创建页面,同时设置封面和正文图片):
curl -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2021-05-13" \
-H "Content-Type: application/json" \
This duplicate endpoint reference reinforces that the workflow depends on authenticated outbound transmission to Notion. The surrounding skill design makes this more dangerous than a generic API example because activation is broad and automatic, so external transfer may happen with insufficient user awareness or confirmation.
curl -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2021-05-13" \
-H "Content-Type: application/json" \
This duplicate endpoint reference reinforces that the workflow depends on authenticated outbound transmission to Notion. The surrounding skill design makes this more dangerous than a generic API example because activation is broad and automatic, so external transfer may happen with insufficient user awareness or confirmation.
curl -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2021-05-13" \
-H "Content-Type: application/json" \
This JSON eval file contains multiple generic activation-style phrases such as '保存这个工具', '把这个添加到我的工具箱', and '帮我保存这个链接到工具箱' that resemble ordinary conversational requests rather than narrowly scoped triggers. Because manifest/JSON files are in scope for SQP-1, these broad phrasings can indicate ambiguous invocation conditions without explicit constraints or negative examples.
The usage example and allowed type values require Chinese labels such as "网站" and "工具包", and the CLI choices later enforce that locale-specific vocabulary. This is a natural-language policy concern because the skill forces a specific language without opt-in or documented justification.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
NOTION_API_KEY = os.environ.get('NOTION_API_KEY', '')
DATABASE_ID = '6f7bb9cc-ac01-41c5-9856-fb1568d197ae'
NOTION_API_URL = 'https://api.notion.com/v1/pages'
# Valid types (exactly 5 options)
VALID_TYPES = {'软件', '网站', '浏览器插件', 'Prompt', '工具包'}
VALID_TYPES is restricted to Chinese strings only, which means users must interact in that locale even though the script otherwise uses English identifiers and help text. This is a policy violation under the language/locale rule because no alternative locale or explicit user choice is provided.
This code fetches the user-supplied URL and later sends tool metadata to Notion, which transmits user-provided data and remote resource references over the network. While the module docstring states the script saves to Notion, there is no explicit warning, confirmation, or user-facing notice about external network access or data transmission when the script runs.
All prompts and expected outputs in this file are written in Chinese, which may imply the skill is evaluated or expected to operate in a fixed language. For policy compliance, language constraints should be explicitly optional or justified rather than implicitly enforced across all examples.
The script reads a credential from the environment and uses it for authenticated API calls, but provides no warning in its help text or runtime output that it depends on and will use this sensitive token. For safety auditing, credential use should be disclosed so operators understand the authentication context.
No suspicious patterns detected.