Back to skill

Security audit

imageReader

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims, but its image downloader has broad network and file-write behavior that users should review before installing.

Install only if you trust the skill with the chat platforms you configure. Use least-privilege bot/app credentials, avoid passing tokens on the command line, do not feed it arbitrary URLs from untrusted users, and keep downloaded images confined to a private temporary directory that is cleaned after use.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_image.py:134
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
str: """Download image from Discord (direct URL).""" resp = requests.get(url) resp.raise_for_status() with open(output_path, "wb") as f: f.write(resp.content) return output_path ``` ```python def download_direct_url(url: str, output_path: str, headers: dict = None) -> str: """Download image from direct URL.""" resp = requests.get(url, headers=headers or {}) resp.raise_for_status() with open(output_path, "wb") as f: f.write(resp.content) return output_path ``` ```python parser.add_argument("--url", help="Direct image URL (for Discord or any direct link)") ``` ### Technical Analysis The downloader accepts an arbitrary URL and passes it directly to `requests.get`. It does not validate the URL scheme, hostname, port, resolved IP address, or redirect destination. Although direct attachment downloads are necessary for some supported platforms, accepting “any direct link” exceeds the minimum privilege required. A Discord-specific operation should normally be restricted to approved Discord CDN domains. The generic mode permits the process to access any address reachable from the host, including loopback interfaces, private networks, link-local services, and cloud metadata endpoints. The default redirect behavior of `requests` creates an additional bypass: even if a caller initially supplies a public URL, that server can redirect the request to an internal destination. ### Attack Path 1. An attacker sends or supplies a crafted image URL. 2. The Agent invokes the downloader with `--url` or the Discord platform mode. 3. The script se ...[truncated 722 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_image.py:145
Finding

Bearer Credentials Are Forwarded to Unvalidated Download URLs

Content
View full analysis
str: """Download image from WhatsApp Business API.""" # Get media URL url = f"https://graph.facebook.com/v17.0/{media_id}" resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}) resp.raise_for_status() media_url = resp.json()["url"] # Download media resp = requests.get(media_url, headers={"Authorization": f"Bearer {token}"}) resp.raise_for_status() ``` ```python def download_slack_image(file_id: str, token: str, output_path: str) -> str: """Download image from Slack.""" # Get file info url = f"https://slack.com/api/files.info" resp = requests.get(url, params={"file": file_id}, headers={"Authorization": f"Bearer {token}"}) resp.raise_for_status() download_url = resp.json()["file"]["url_private_download"] # Download file resp = requests.get(download_url, headers={"Authorization": f"Bearer {token}"}) resp.raise_for_status() ``` ### Technical Analysis The WhatsApp and Slack flows extract a URL from an API response and immediately send the platform bearer token to that destination. The script does not validate the returned URL's scheme, hostname, port, or resolved address. The URLs are expected to originate from official APIs, which lowers the likelihood under normal operation. However, the code establishes no security boundary between trusted API metadata and credential transmission. A compromised upstream response, malicious proxy, unsafe test environment, or unexpected platform response could cause a reusable token to be transmitted to an attacker-controlled server. Redirects are also followed by default. Authorizati ...[truncated 915 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_image.py:217
Finding

API Tokens Passed Through Command-Line Arguments Can Leak Locally

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_image.py:218
Finding

Caller-Controlled Output Path Permits Arbitrary File Overwrite

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_image.py:195
Finding

Unbounded and Unvalidated Downloads Allow Resource Exhaustion and Arbitrary Content Storage

Content
View full analysis
str: """Download image from direct URL.""" resp = requests.get(url, headers=headers or {}) resp.raise_for_status() with open(output_path, "wb") as f: f.write(resp.content) return output_path ``` Equivalent `requests.get` followed by `f.write(resp.content)` operations occur throughout the Feishu, DingTalk, WeChat, Telegram, Discord, WhatsApp, Slack, and LINE download functions. ### Technical Analysis No request specifies connection or read timeouts. Responses are loaded completely into memory through `resp.content` before being written. There is no maximum response size, streaming limit, MIME-type check, file-signature validation, or image-decoding verification. Consequently, an endpoint can return an extremely large response, respond indefinitely, or provide arbitrary non-image data. The file extension defaults to `.jpg`, but this does not establish that the content is an image. This behavior exceeds the minimum needs of image retrieval because supported platform size limits are documented and could be enforced. ### Attack Path 1. An attacker supplies a controlled URL or causes a supported media endpoint to return an oversized response. 2. The downloader waits without an explicit timeout. 3. `requests` buffers the entire body in process memory. 4. Memory or disk resources are exhausted, or the Agent remains blocked. 5. Alternatively, arbitrary non-image bytes are stored and passed to downstream tooling. ### Impact Assessment Successful exploitation can cause denial of service through memory exhaustion, disk exhaustion, or blocked workers. Stored non-image content may ...[truncated 184 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (50)

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/download_image.py (reported line 26)May include surrounding context.

python
wnload_image.py --platform telegram --file-id <id> --token <bot_token> --output <path>
    python download_image.py --url <direct_url> --output <path>
"""

import argparse
import os
import sys
import requests
import tempfile
import json
from pathlib import Path


def download_feishu_image(message_id: str, image_key: str, output_path: str) -> str:
    """Download image from Feishu."""
    app_id = os.environ.get("FEISHU_APP_ID")
    app_secret = os.environ.get("FEISHU_APP_SECRET")
    
    if not app_id or not app_secret:
        raise ValueError("FEISHU_APP_ID and FEISHU_APP_SECRET must be set")
    
    # Get tenant token
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": app_id, "app_secret": app_secret}
    )
    resp.raise_for_status()
    token = resp.json()["tenant_access_token"]
    
    # Download image
    url = f"https://open.feishu.cn/open-apis/im/v1/messages/{message_id}/resources/{image_key}"

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/download_image.py (reported line 33)May include surrounding context.

python
raise ValueError("FEISHU_APP_ID and FEISHU_APP_SECRET must be set")
    
    # Get tenant token
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": app_id, "app_secret": app_secret}
    )

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/download_image.py (reported line 64)May include surrounding context.

python
raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}
    )

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/download_image.py (reported line 94)May include surrounding context.

python
raise ValueError("WECHAT_CORP_ID and WECHAT_CORP_SECRET must be set")
    
    # Get access token
    resp = requests.get(
        f"https://qyapi.weixin.qq.com/cgi-bin/gettoken",
        params={"corpid": corp_id, "corpsecret": corp_secret}
    )

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 42)May include surrounding context.

md
| Platform | Channel | Image Source | Download Method |
|----------|---------|--------------|-----------------|
| Feishu (飞书) | feishu | Message image_key | API + Tenant Token |
| DingTalk (钉钉) | dingtalk | Message downloadCode | API + Access Token |
| WeChat (微信) | wechat | Media file_id | API + Access Token |
| Discord | discord | Attachment URL | Direct download |
| Telegram | telegram | file_id | Bot API |

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 43)May include surrounding context.

md
| Platform | Channel | Image Source | Download Method |
|----------|---------|--------------|-----------------|
| Feishu (飞书) | feishu | Message image_key | API + Tenant Token |
| DingTalk (钉钉) | dingtalk | Message downloadCode | API + Access Token |
| WeChat (微信) | wechat | Media file_id | API + Access Token |
| Discord | discord | Attachment URL | Direct download |
| Telegram | telegram | file_id | Bot API |

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 113)May include surrounding context.

md
if not app_key or not app_secret:
        raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 119)May include surrounding context.

md
if not app_key or not app_secret:
        raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 125)May include surrounding context.

md
if not app_key or not app_secret:
        raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 179)May include surrounding context.

md
if not app_key or not app_secret:
        raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 194)May include surrounding context.

md
if not app_key or not app_secret:
        raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/download_image.py (reported line 63)May include surrounding context.

python
if not app_key or not app_secret:
        raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/download_image.py (reported line 93)May include surrounding context.

python
if not app_key or not app_secret:
        raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The trigger phrases include very generic commands like "analyze image" and "read image from chat," which can cause the skill to activate in situations broader than users may expect. Because the skill can access chat messages, attachments, local file paths, and external platform APIs, accidental invocation can lead to unintended processing of sensitive images or message context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill explicitly accepts local file paths and direct downloads, but the user-facing description does not prominently warn that local files may be accessed and copied to temporary storage. This can mislead users about what data sources the skill touches and increase the risk of exposing sensitive local images or files through over-broad path input.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This reference explains how to authenticate, retrieve, and download user message images from multiple chat platforms, but it omits basic privacy, consent, retention, provenance, and content-validation warnings. In a skill that may be used to process user-supplied chat images, that omission can encourage unsafe handling of sensitive user content and blind trust in remote attachments, increasing the risk of privacy violations, accidental overcollection, and unsafe downstream processing.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code reads platform app IDs and secrets from environment variables and uses them to authenticate outbound API requests, but the only surrounding text is a minimal function docstring. There is no visible disclosure in the CLI help or runtime output that credentials from the environment will be consumed and transmitted to external services.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/download_image.py (reported line 33)May include surrounding context.

python
raise ValueError("FEISHU_APP_ID and FEISHU_APP_SECRET must be set")
    
    # Get tenant token
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": app_id, "app_secret": app_secret}
    )

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/download_image.py (reported line 64)May include surrounding context.

python
raise ValueError("FEISHU_APP_ID and FEISHU_APP_SECRET must be set")
    
    # Get tenant token
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": app_id, "app_secret": app_secret}
    )

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/download_image.py (reported line 33)May include surrounding context.

python
raise ValueError("FEISHU_APP_ID and FEISHU_APP_SECRET must be set")
    
    # Get tenant token
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": app_id, "app_secret": app_secret}
    )

Tainted flow: 'url' from requests.get (line 117, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/download_image.py (reported line 42)May include surrounding context.

python
# Download image
    url = f"https://open.feishu.cn/open-apis/im/v1/messages/{message_id}/resources/{image_key}"
    resp = requests.get(
        url,
        headers={"Authorization": f"Bearer {token}"},
        params={"type": "file"}

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/download_image.py (reported line 64)May include surrounding context.

python
raise ValueError("DINGTALK_APP_KEY and DINGTALK_APP_SECRET must be set")
    
    # Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}
    )

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 181)May include surrounding context.

md
# Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}
    )
    resp.raise_for_status()

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 185)May include surrounding context.

md
# Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}
    )
    resp.raise_for_status()

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/platform-api.md (reported line 48)May include surrounding context.

md
# Get access token
    resp = requests.post(
        "https://api.dingtalk.com/v1.0/oauth2/accessToken",
        json={"appKey": app_key, "appSecret": app_secret}
    )
    resp.raise_for_status()

Static analysis

No suspicious patterns detected.