T09 · Insecure Skill Coding Practices
- Location
scripts/generate.py:59- Finding
Unrestricted Fetching of API-Controlled Image URLs Enables SSRF and Resource Exhaustion
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This image-generation skill is mostly purpose-aligned, but it automatically sends generated content to external services and relays remote image URLs without enough safeguards or user control.
Review before installing. Use this only for non-sensitive prompts and images unless you are comfortable sending them to the configured AIGC service and Feishu. Prefer a pinned dependency set, restrict AIGC_BASE_URL to a trusted endpoint, add URL and size validation for returned images, and require explicit confirmation before Feishu or OSS uploads.
scripts/generate.py:59Unrestricted Fetching of API-Controlled Image URLs Enables SSRF and Resource Exhaustion
README.md:21Third-Party Dependency Is Installed Without Version or Integrity Pinning
The skill uses capabilities that can access environment variables and make networked requests, but it declares no explicit tool scope or permissions boundary. That creates an authorization and review gap: operators cannot easily see or restrict what the skill may access, and a compromised or modified implementation could exfiltrate secrets such as AIGC_API_KEY or send data to unintended endpoints.
The trigger phrases are broad enough to match many normal requests for drawing or content creation, which can cause the skill to auto-run in contexts where the user did not intend to invoke this specific workflow. In this skill, that matters because invocation can lead to code execution, third-party image generation, and downstream upload behavior, increasing the chance of unintended data handling or side effects.
The skill instructs automatic upload of generated images to Feishu without an explicit user warning or consent step about transmitting content to an external service. This is dangerous because prompts and generated images may contain sensitive or proprietary information, and automatic transfer reduces the chance the user can review or stop disclosure before it happens.
The script persists generated images locally and explicitly prepares them for downstream document-sharing workflows, which is broader than the stated purpose of simple image generation. In an agent environment this increases exposure because content is retained on disk and staged for later transmission, potentially without the user's awareness.
The file-level description is written as an instruction in Chinese and presents the skill's behavior in that locale without offering any language choice. The code also uses Chinese user-facing messages throughout, indicating a fixed language policy rather than user-selected localization.
The skill has an undeclared secondary capability: it uploads generated or fetched images to an OSS/file service, which exceeds the narrow user expectation of image generation. In agent contexts, hidden outbound data movement is dangerous because generated content may contain sensitive prompts or user-requested material and gets transmitted to an additional third party without clear disclosure or consent.
The skill fetches arbitrary image_url content from a remote source and immediately re-uploads it to another external service. This creates a blind network-to-network relay path that can be abused for SSRF-style access to internal resources, exfiltration of fetched content, or transfer of unexpected data to a third party if the upstream service returns attacker-controlled URLs.
filename = os.path.basename(urlparse(image_url).path) or f"aigc_{int(time.time()*1000)}.png"
files = {'file': (filename, img_resp.content, 'image/png')}
headers = {'X-API-Key': CONFIG['api_key']}
resp = requests.post(
f"{CONFIG['base_url']}/ai/file/upload",
files=files, headers=headers, timeout=30
)
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
if result.get("code") == 200:
return result.get("data", {}).get("url", image_url)
except Exception as e:
print(f" ⚠️ OSS 上传异常: {e}", file=__import__('sys').stderr)
return image_url
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
def generate(prompt, negative_prompt="", aspect_ratio=1, batch_size=1):
"""提交生成任务,返回 task_id"""
if not _check_rate_limit(CONFIG['client_id']):
print("❌ 频率超限: 60秒内最多 20 次", file=__import__('sys').stderr)
return None
request_id = str(uuid.uuid4())
The skill transmits the user's prompt and generation parameters to an external API endpoint. External transmission is expected for a hosted image-generation service, but it is still a real data-flow risk because prompts may contain sensitive or proprietary information and the default base URL points to a third-party domain controlled outside the local agent environment.
}
try:
resp = requests.post(
f"{CONFIG['base_url']}/ai/image/generate",
json=payload,
headers={"X-API-Key": CONFIG['api_key']},
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
result = resp.json()
if result.get("code") != 200:
print(f"❌ 生成失败: {result.get('message', 'Unknown error')}", file=__import__('sys').stderr)
return None
task_id = (
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
result.get("data", {}).get("promptId")
)
if not task_id:
print("❌ 未获取到任务ID", file=__import__('sys').stderr)
return None
print(f"✅ 任务已提交: {task_id}", flush=True)
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
print(f"✅ 任务已提交: {task_id}", flush=True)
return task_id
except Exception as e:
print(f"❌ 请求失败: {e}", file=__import__('sys').stderr)
return None
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
start = time.time()
while time.time() - start < max_wait:
try:
resp = requests.get(
f"{CONFIG['base_url']}/ai/task/query",
params={"taskId": task_id},
headers={"X-API-Key": CONFIG['api_key']},
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
return local_paths
elif status < 0:
print(f"❌ 生成失败: {data.get('message', status_desc)}", file=__import__('sys').stderr)
return None
elif status == 1 and progress >= 1.0 and not result_urls:
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
time.sleep(2)
except Exception as e:
print(f"⚠️ 查询异常: {e}", file=__import__('sys').stderr)
time.sleep(2)
print(f"⏱️ 超时:任务未在 {max_wait} 秒内完成", file=__import__('sys').stderr)
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
print(f"⚠️ 查询异常: {e}", file=__import__('sys').stderr)
time.sleep(2)
print(f"⏱️ 超时:任务未在 {max_wait} 秒内完成", file=__import__('sys').stderr)
return None
The natural-language content in this skill documentation is entirely in Chinese, including the description and section headings. This can amount to a language policy violation when users are not given a choice of language or informed that the skill is intended only for Chinese-speaking users.