Back to skill

Security audit

pollinations-sketch-note

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its image-card purpose, but it needs review because it requires an unused API key and sends topics/prompts to third-party services with limited disclosure.

Review before installing. Use this only with non-sensitive topics, use revocable low-quota API keys, and be aware that themes/prompts leave the local environment. The publisher should remove the unused Tavily key requirement, document the actual providers and data sent, narrow the triggers, and harden temporary-file handling.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/generate.py:29
Finding
Mandatory Access to an Unused Tavily API Credential<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py`, lines 29-45 **Vulnerability Type**: Unnecessary access to a sensitive credential **Risk Level**: Medium ### Vulnerable Code ```python # 配置 - 从环境变量读取 POLLINATIONS_API_KEY = os.environ.get("POLLINATIONS_API_KEY") TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY") # 验证 API Key 是否存在 if not POLLINATIONS_API_KEY: print("❌ 错误:未找到 POLLINATIONS_API_KEY 环境变量") print("请配置:export POLLINATIONS_API_KEY=\"your-api-key\"") print("或在 ~/.zshrc 中添加后运行:source ~/.zshrc") sys.exit(1) if not TAVILY_API_KEY: print("❌ 错误:未找到 TAVILY_API_KEY 环境变量") print("请配置:export TAVILY_API_KEY=\"your-api-key\"") print("或在 ~/.zshrc 中添加后运行:source ~/.zshrc") sys.exit(1) ``` The actual search implementation uses the Jina Reader service rather than Tavily: ```python baidu_url = f"https://baike.baidu.com/item/{urllib.parse.quote(theme)}" jina_url = f"https://r.jina.ai/{baidu_url}" ``` ### Technical Analysis The program reads `TAVILY_API_KEY` and refuses to operate unless the credential is available. However, no code in the project sends a request to Tavily or otherwise uses this value. Requiring an unrelated secret violates least-privilege principles. It unnecessarily expands the sensitive environment data available to the process and contradicts the documented architecture, which states that Tavily performs the search. The reviewed code does not transmit or print the Tavily key, so direct exfiltration was not identified. The security issue is the unnecessary credential-access requirement itself. ### Attack Path 1. A user follows the installation instructions and places a valid Tavily key in the environment. 2. The Skill process starts and reads that key into process memory. 3. The process does not use Tavily; searches are instead sent through `r.jina.ai`. 4. Any future compromise, unsafe diagnostic addition, imported-code flaw, or process-memory disclosure would expose a credential that t ...[truncated 361 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `TAVILY_API_KEY` from `scripts/generate.py` unless the application actually uses Tavily. 2. Remove the corresponding requirement from `SKILL.md`, `README.md`, `INTRO.md`, `skill.yaml`, and `package.json`. 3. If Tavily integration is implemented later, load the credential only immediately before the request that requires it. 4. Use a narrowly scoped and revocable API key with the lowest available quota and permissions. 5. Document the actual search provider, currently `r.jina.ai`, so users can make an informed decision about transmitting search topics. 6. Add tests that verify the program starts without credentials for services it does not call. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:247
Finding
Pollinations API Key Exposed in a URL Query Parameter<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py`, lines 247-256 **Vulnerability Type**: Sensitive credential in a request URL **Risk Level**: Medium ### Vulnerable Code ```python encoded_prompt = urllib.parse.quote(prompt) url = f"https://gen.pollinations.ai/image/{encoded_prompt}" params = { "width": 804, "height": 440, "model": "flux", "key": POLLINATIONS_API_KEY } # print(f"🎨 正在生成背景图(风格:{style[:50]}...)") # 静默模式 response = requests.get(url, params=params, timeout=60) ``` ### Technical Analysis The `requests` library serializes the `params` dictionary into the URL query string. Consequently, the resulting request URL contains the Pollinations credential in a form similar to: ```text https://gen.pollinations.ai/image/...?...&key=<POLLINATIONS_API_KEY> ``` TLS protects the URL while it travels across the network, but it does not prevent the receiving service, reverse proxies, monitoring systems, browser-independent HTTP diagnostics, or application logs from recording the complete URL. Query strings are routinely retained in access and observability logs. The code does not explicitly log the final URL, but placing a long-lived credential in the URL increases its exposure surface beyond the request authentication mechanism. ### Attack Path 1. A user invokes image generation with a valid Pollinations key. 2. `requests.get()` places the key in the request query string. 3. The complete request URL is processed by the remote service and may pass through reverse proxies or monitoring infrastructure. 4. A person or system with access to retained URL logs can recover the API key. 5. The recovered key can be reused against Pollinations until it expires or is revoked. ### Impact Assessment An exposed key could permit unauthorized Pollinations API usage, consume the victim's quota, incur charges where applicable, or access functionality granted to that credential. The exact scope depends on the permissions and billing ...[truncated 211 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. If supported by Pollinations, send the credential in an authorization header instead of the query string: ```python headers = { "Authorization": f"Bearer {POLLINATIONS_API_KEY}", } response = requests.get( url, params={"width": 804, "height": 440, "model": "flux"}, headers=headers, timeout=60, ) ``` 2. If the provider only supports query-string authentication, use short-lived, narrowly scoped, and easily revocable credentials. 3. Configure application, proxy, and monitoring systems to redact the `key` parameter. 4. Never include the prepared request URL in exceptions, telemetry, or debug output. 5. Rotate any credential that may previously have appeared in retained request logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:457
Finding
Predictable Temporary Image File Permits Symlink-Based Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py`, lines 257-260 and 457-461 **Vulnerability Type**: Predictable and non-atomic temporary-file creation **Risk Level**: Medium ### Vulnerable Code The background generator opens the supplied path with ordinary truncating write semantics: ```python if response.status_code == 200: with open(output_path, 'wb') as f: f.write(response.content) # print(f"✅ 背景图已保存:{output_path}") # 静默模式 return True ``` The caller constructs that path predictably from the final output filename: ```python # 生成背景图 bg_path = output_path.parent / f"bg_{output_path.name}" if not generate_background(args.theme, style, bg_path): print(f"ERROR: Failed to generate background. Style: {style[:100]}...") sys.exit(1) ``` The same path is later removed without verifying that it still identifies the file created by this process: ```python # 清理背景图 bg_path.unlink() ``` ### Technical Analysis The temporary background filename is deterministically created by prefixing the user-selected output filename with `bg_`. It is not created with exclusive semantics, and the program does not reject symbolic links. On systems where another local user or process can write to the output directory, an attacker can predict the temporary path and create a symbolic link at that location. Python's ordinary `open(path, "wb")` follows the link and truncates the linked target before writing the downloaded image. There is also a time-of-check/time-of-use window before `unlink()`: another process could replace the temporary pathname after image processing. Although `unlink()` removes a symlink rather than its target on normal platforms, it could remove an attacker-selected directory entry that the Skill process has permission to delete. ### Attack Path 1. The victim chooses or reveals an output such as `/shared/card.png`. 2. The attacker predicts the temporary path `/shared/bg_card.png`. 3. Before generation begins ...[truncated 907 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create temporary files with Python's `tempfile` module in a private directory: ```python import tempfile output_path.parent.mkdir(parents=True, exist_ok=True) with tempfile.NamedTemporaryFile( mode="wb", suffix=".png", prefix=".sketch-bg-", dir=output_path.parent, delete=False, ) as temporary_file: bg_path = Path(temporary_file.name) ``` 2. Keep the generated unpredictable filename rather than closing it and reopening a predictable path where possible. 3. Ensure the temporary directory is not writable by untrusted users. 4. If low-level file creation is required, use `os.open()` with `O_CREAT | O_EXCL | O_NOFOLLOW` where supported. 5. Put cleanup in a `finally` block and verify that the path still refers to the originally created regular file before deleting it. 6. Save the final image to a separate temporary file and atomically move it into place with `os.replace()` after successful processing. 7. Reject output directories that are unsafe, unexpectedly shared, or contain attacker-controlled path components. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Tainted flow: 'params' from os.environ.get (line 253, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
}

    # print(f"🎨 正在生成背景图(风格:{style[:50]}...)")  # 静默模式
    response = requests.get(url, params=params, timeout=60)

    if response.status_code == 200:
        with open(output_path, 'wb') as f:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The introduction states that the skill uses Tavily Search and Pollinations AI, but it does not disclose that user prompts, topics, or generated content may be transmitted to third-party services. This creates a privacy and consent risk because users may provide sensitive educational, business, or personal topics without realizing they leave the local OpenClaw environment.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The description says the skill automatically searches Wikipedia and Baidu Baike, but it does not clearly warn users that their topic queries will be sent to external third-party services. This creates a privacy and data-handling risk, especially if users provide sensitive, proprietary, or personal topics assuming the request stays local to the assistant.

Session Persistence

Medium
Category
Rogue Agent
Content
- ✍️ Auto signature and timestamp

**Who it's for:**
- Students: Create learning note cards
- Educators: Quick teaching materials
- Content creators: Social media knowledge sharing
- Corporate training: Standardized training materials
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The invocation phrases shown to users are very broad generic requests for image generation, such as asking to generate a picture about a topic. In an agent environment, broad triggers can cause this skill to activate when a user intended a different image workflow, leading to unintended external searches and generation behavior.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly advertises automatic search against external services, but the description does not warn users that their query/theme content will be transmitted to third-party providers such as Wikipedia, Baidu Baike, and likely API-backed summarization services. This creates a real privacy and transparency issue because users may provide sensitive topics or internal terms without realizing the data leaves the local environment.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The manifest advertises automatic search and summarization but does not disclose that user topics may be transmitted to third-party APIs via the configured POLLINATIONS_API_KEY and TAVILY_API_KEY integrations. This is dangerous because users may provide sensitive subjects, assuming local processing, while the skill silently sends that data to external providers with their own retention and logging practices.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger phrases are broad enough to match generic image-generation requests, which can cause the skill to activate outside its intended sketch-note use case. This creates a prompt-routing and consent problem: users may unintentionally send topics to this skill and its linked external services when they expected a different tool or no third-party processing at all.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script transmits user-provided themes and fetched content to external services (Jina Reader/Pollinations) without making that data-sharing behavior explicit at the point of use. In a skill/agent context, users may assume local-only processing, so undisclosed outbound transmission can leak sensitive prompts or private topics entered as themes.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill documentation does not clearly warn users that their topic input may be transmitted to external services for both search and image generation. This creates a privacy and consent risk, especially if users provide sensitive, personal, or proprietary topics assuming the operation is local or self-contained.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger phrases are very broad and overlap with generic image-generation requests, which can cause the skill to activate in situations where the user did not specifically intend to use this tool. Because the skill sends prompts to external APIs and may perform web search, accidental invocation can expose user topics to third parties and produce unexpected actions.

Natural-Language Policy Violations

Low
Confidence
75% confidence
Finding
The natural-language description labels the skill as a Chinese AI tool, which can imply a locale or language constraint. Although the file later lists support for Chinese and English, this early phrasing does not clearly present language choice or explain why a Chinese-specific framing is required.

Natural-Language Policy Violations

Low
Confidence
74% confidence
Finding
The description is given in both English and Chinese, and the example usage uses a Chinese placeholder, which suggests language-specific behavior or expectations. The file does not state whether users may choose their preferred language/output locale or whether the skill is intentionally region-specific.

Natural-Language Policy Violations

Low
Confidence
70% confidence
Finding
Keywords, tags, and trigger examples are exclusively Chinese-centric, including labels such as "chinese" and "中文 AI", but the manifest does not explicitly state whether other languages are supported or whether Chinese is an intentional regional constraint. This can create an implicit language policy restriction without user opt-in or clear justification.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The skill description, CLI help text, and generated fallback text are all written in Chinese, and the text filtering/compression logic is tailored to Chinese output, with no indication that users may choose another language. This constitutes a locale/language policy concern because the skill effectively forces one language without documented opt-in or justification.

Intent-Code Divergence

Low
Confidence
92% confidence
Finding
The docstring says the function fetches Wikipedia content, which implies a specific source and behavior. In reality, the implemented primary behavior is to access Baidu Baike through r.jina.ai, with Wikipedia only as a secondary fallback, so the documentation actively misstates what the function does.

Static analysis

No suspicious patterns detected.