Back to skill

Security audit

DashScope Web Search (Feishu)

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly purpose-aligned, but its Feishu image pipeline handles secrets, uploads content, and sends chat messages using unsafe network and token-storage practices.

Install only if you are comfortable sending search queries to DashScope and image content to Feishu. Before using the image mode, the publisher should re-enable TLS verification, restrict and validate image URLs, add response size/type checks, store Feishu tokens in a private per-user cache, and require clear confirmation before sending images to a chat.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/feishu_image.py:109
Finding
Arbitrary Image URL Fetching Enables Server-Side Request Forgery and Data Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/feishu_image.py`, lines 109-120 and 186-208 **Vulnerability Type**: Server-Side Request Forgery (SSRF) with external data upload **Risk Level**: High ### Vulnerable Code ```python def download_image(url, dest): """Download an image URL to a local file. Returns True on success.""" try: req = urllib.request.Request(url, headers={ "User-Agent": "Mozilla/5.0 (compatible; FeishuImageBot/1.0)", }) with urllib.request.urlopen(req, timeout=15, context=_ssl_ctx) as resp: with open(dest, "wb") as f: f.write(resp.read()) return True except Exception as e: print(f"[feishu_image] download failed {url}: {e}", file=sys.stderr) return False ``` The downloaded content is subsequently uploaded to Feishu: ```python for i, m in enumerate(matches): url = m.group(2) if url in url_to_key: continue if url.startswith("img_"): url_to_key[url] = url continue ext = os.path.splitext(urllib.parse.urlparse(url).path)[1] or ".jpg" dest = os.path.join(tmpdir, f"img_{i}{ext}") if download_image(url, dest): try: image_key = upload_image_to_feishu(dest, token) url_to_key[url] = image_key print(f"[feishu_image] uploaded {url[:80]}... -> {image_key}", file=sys.stderr) except Exception as e: print(f"[feishu_image] upload failed: {e}", file=sys.stderr) ``` ### Technical Analysis Markdown image URLs are extracted from model-generated search output and passed directly to `urllib.request.urlopen`. The implementation does not validate: - The URL scheme. - The destination hostname or resolved IP address. - Whether the address belongs to a loopback, private, link-local, reserved, or cloud metadata range. - Redirect targets. - The response MIME type. - The response size. - Whether the response is actually an image. Consequently, ...[truncated 1780 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only HTTPS image URLs. 2. Resolve the hostname before connecting and reject all loopback, private, link-local, multicast, unspecified, reserved, and metadata-service addresses for both IPv4 and IPv6. 3. Disable automatic redirects or validate the hostname and resolved addresses of every redirect target. 4. Prevent DNS rebinding by ensuring that validation and connection use the same resolved address. 5. Require an approved image MIME type and verify the file signature rather than trusting the extension or `Content-Type` header alone. 6. Enforce strict response-size, timeout, and image-dimension limits while streaming the response. 7. Consider an allowlist of trusted image-hosting domains returned by the search provider. 8. Do not upload a downloaded resource until image decoding and validation succeed. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/feishu_image.py:28
Finding
TLS Certificate and Hostname Verification Is Globally Disabled<![CDATA[ ## Vulnerability Details **File Location**: `scripts/feishu_image.py`, lines 28-31; insecure context used at lines 94-100, 114-117, 146-153, and 174-181 **Vulnerability Type**: Improper certificate validation **Risk Level**: High ### Vulnerable Code ```python _ssl_ctx = ssl.create_default_context() _ssl_ctx.check_hostname = False _ssl_ctx.verify_mode = ssl.CERT_NONE ``` This context is used when transmitting the Feishu application credentials: ```python req = urllib.request.Request( FEISHU_TOKEN_URL, data=payload, headers={"Content-Type": "application/json; charset=utf-8"}, method="POST", ) with urllib.request.urlopen(req, timeout=10, context=_ssl_ctx) as resp: data = json.loads(resp.read()) ``` It is also used for image downloads, Feishu uploads, and message delivery: ```python with urllib.request.urlopen(req, timeout=15, context=_ssl_ctx) as resp: with open(dest, "wb") as f: f.write(resp.read()) ``` ```python with urllib.request.urlopen(req, timeout=30, context=_ssl_ctx) as resp: data = json.loads(resp.read()) ``` ```python with urllib.request.urlopen(req, timeout=15, context=_ssl_ctx) as resp: data = json.loads(resp.read()) ``` ### Technical Analysis `ssl.create_default_context()` initially enables secure certificate-chain and hostname validation. The following assignments explicitly disable both protections: ```python _ssl_ctx.check_hostname = False _ssl_ctx.verify_mode = ssl.CERT_NONE ``` All network operations in `feishu_image.py` reuse this insecure context. HTTPS encryption without peer authentication does not establish that the remote endpoint is Feishu or the intended image host. The token request is especially sensitive because its JSON body contains both the Feishu application ID and application secret. Subsequent requests expose bearer tokens and uploaded content to the same interception risk. ### Attack Path 1. An attacker obtains a network interception position, such as control over ...[truncated 1122 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the insecure overrides entirely: ```python _ssl_ctx = ssl.create_default_context() ``` 2. Keep both certificate-chain and hostname verification enabled for every request. 3. Do not provide an environment variable or command-line option that silently disables TLS verification. 4. Use the operating system's trusted CA bundle or an explicitly managed CA bundle where enterprise interception is required. 5. Consider certificate pinning only if the Feishu endpoint's certificate lifecycle can be managed safely. 6. Rotate the Feishu app secret and invalidate active tenant tokens if the vulnerable implementation has operated on an untrusted network. 7. Add tests confirming that expired, self-signed, hostname-mismatched, and untrusted certificates are rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/feishu_image.py:76
Finding
Feishu Tenant Access Token Is Cached in an Unsafe Predictable Temporary File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/feishu_image.py`, lines 33 and 76-106 **Vulnerability Type**: Insecure temporary file and sensitive-token storage **Risk Level**: High ### Vulnerable Code ```python TOKEN_CACHE_PATH = "/tmp/feishu_token.json" ``` ```python def get_tenant_access_token(app_id, app_secret): """Get tenant_access_token, using a file cache to avoid repeated requests.""" if os.path.exists(TOKEN_CACHE_PATH): try: with open(TOKEN_CACHE_PATH, "r") as f: cache = json.load(f) if cache.get("expire_at", 0) > time.time() + 60: return cache["token"] except (json.JSONDecodeError, KeyError): pass payload = json.dumps({ "app_id": app_id, "app_secret": app_secret, }).encode("utf-8") req = urllib.request.Request( FEISHU_TOKEN_URL, data=payload, headers={"Content-Type": "application/json; charset=utf-8"}, method="POST", ) with urllib.request.urlopen(req, timeout=10, context=_ssl_ctx) as resp: data = json.loads(resp.read()) if data.get("code") != 0: raise RuntimeError(f"Failed to get token: {data}") token = data["tenant_access_token"] expire = data.get("expire", 7200) with open(TOKEN_CACHE_PATH, "w") as f: json.dump({"token": token, "expire_at": time.time() + expire}, f) return token ``` ### Technical Analysis The tenant access token is stored at the fixed path `/tmp/feishu_token.json`. The implementation does not: - Securely create the file with exclusive creation semantics. - Explicitly set permissions to `0600`. - Verify file ownership. - Reject symbolic links or unexpected file types. - Atomically replace the cache. - Separate caches by operating-system user or Feishu app ID. - Bind the cached token to the current app ID. The process first checks the path and later opens it, creating a time-of-check/time-of-use condition. ...[truncated 1852 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the cache in a private, user-owned runtime or cache directory, such as `$XDG_RUNTIME_DIR` or `$XDG_CACHE_HOME`. 2. Create the directory with mode `0700` and verify its ownership. 3. Create cache files with mode `0600` using secure flags such as `O_CREAT`, `O_EXCL`, and `O_NOFOLLOW`. 4. Verify with `lstat` that the path is a regular file owned by the expected user before reading it. 5. Write to a securely created temporary file, flush it, and use an atomic rename for updates. 6. Include a non-secret hash or identifier of the app ID in the cache and reject tokens belonging to another application. 7. Use an operating-system credential store or an in-memory cache where feasible. 8. Coordinate concurrent processes with a secure lock. 9. Avoid retaining the token longer than necessary and remove expired cache records. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:59
Finding
Unpinned Third-Party Dependency Installation Creates Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `README.md`, lines 59-63 **Vulnerability Type**: Unpinned dependency installation **Risk Level**: Medium ### Vulnerable Code ```markdown ### 4. Install Python dependency ```bash pip install openai ``` ``` ### Technical Analysis The installation instructions request the latest available `openai` package and its transitive dependencies without: - A reviewed version constraint. - A lock file. - Package hashes. - A controlled package index. - Reproducible dependency resolution. The package is imported at runtime by `scripts/web_search.py`: ```python from openai import OpenAI ``` Therefore, the effective code installed and executed can change after the Skill itself has been reviewed. This is not evidence that the current `openai` package is malicious; the risk is that future releases, transitive dependencies, or the configured package source could be compromised or incompatible. ### Attack Path 1. A user follows the documented `pip install openai` command. 2. `pip` resolves the newest package and transitive dependency versions available from its configured index. 3. A compromised release, compromised package index, or maliciously altered dependency is selected. 4. Installation hooks or imported runtime code execute under the privileges of the user installing or invoking the Skill. 5. The dependency can access the same environment as the Skill, potentially including API credentials. The path requires compromise or malicious modification of a dependency source; the audited repository itself does not contain a dependency-confusion package name or a known malicious dependency. ### Impact Assessment A compromised dependency could execute with the privileges of the installing or runtime account. It may access: - `DASHSCOPE_API_KEY`. - Feishu credentials available in the environment or configuration. - Files readable by the process account. - Network resources reachable by the host. If installation is per ...[truncated 208 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a reviewed dependency manifest with exact versions. 2. Generate and commit a lock file covering all transitive dependencies. 3. Require cryptographic hashes, for example through a hash-locked requirements file. 4. Install with: ```bash python3 -m pip install --require-hashes -r requirements.txt ``` 5. Document the trusted package index and avoid unintended fallback indexes. 6. Run dependency vulnerability and provenance checks in continuous integration. 7. Review and update pinned versions on a controlled schedule. 8. Install dependencies in a dedicated virtual environment under a non-privileged account. 9. Declare the Python package dependency in the Skill metadata so deployment checks match actual runtime requirements. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
Findings (18)

Tainted flow: 'req' from os.environ.get (line 169, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json; charset=utf-8"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=10, context=_ssl_ctx) as resp:
        data = json.loads(resp.read())

    if data.get("code") != 0:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 169, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request(url, headers={
            "User-Agent": "Mozilla/5.0 (compatible; FeishuImageBot/1.0)",
        })
        with urllib.request.urlopen(req, timeout=15, context=_ssl_ctx) as resp:
            with open(dest, "wb") as f:
                f.write(resp.read())
        return True
Confidence
99% confidence
Finding
The script downloads arbitrary input-derived image URLs with TLS certificate verification and hostname checking explicitly disabled. This enables SSRF to internal or sensitive endpoints and makes interception or spoofing of HTTPS downloads feasible, especially because the skill processes untrusted Markdown from stdin.

Tainted flow: 'req' from os.environ.get (line 169, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=30, context=_ssl_ctx) as resp:
        data = json.loads(resp.read())

    if data.get("code") != 0:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 169, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15, context=_ssl_ctx) as resp:
        data = json.loads(resp.read())

    if data.get("code") != 0:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Self-Modification

High
Category
Rogue Agent
Content
pip install openai
```

### 5. Update SKILL.md paths

Replace `{{SKILL_DIR}}` with the actual install path:
Confidence
85% confidence
Finding
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The documented purpose is simple web search, but the detected behavior includes downloading arbitrary image URLs, sending content to Feishu chats, reading credentials from environment or local config, and caching tokens in /tmp. This mismatch is dangerous because users and reviewers may authorize the skill for low-risk retrieval while it actually performs credential handling and outbound content delivery with materially higher security and privacy impact.

Vague Triggers

High
Confidence
97% confidence
Finding
The activation guidance is extremely broad and tells the agent to invoke the skill proactively for many common factual requests. In combination with network access and chat-delivery features, this can cause over-invocation, unnecessary data transmission, and use of external services without a clear user opt-in or need-to-know boundary.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The script can send outbound Feishu chat messages, a materially broader capability than passive web search. In an agent-skill context, this increases the blast radius from information retrieval to external action, enabling unreviewed data exfiltration, spam, or unintended communications if the skill is invoked or chained improperly.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README states that search-result images are automatically downloaded, uploaded to Feishu, and sent as messages, but it does not clearly warn that user queries, fetched content, and image URLs/media may be transmitted to multiple third-party services. In a chat-agent context, this can cause unintended disclosure of potentially sensitive prompts or externally retrieved content, especially when the skill activates proactively.

Session Persistence

Medium
Category
Rogue Agent
Content
### 3. Configure Feishu Credentials

Create a Feishu app at [Feishu Open Platform](https://open.feishu.cn/app/) and get your App ID and App Secret.

**Required permissions** (add in Feishu Developer Console):
- `im:message:send_as_bot` — Send messages as bot
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill declares no explicit tool scope or permission boundaries even though its documented behavior implies access to environment variables, local files, network resources, and message delivery workflows. In an agent setting, missing scope restrictions increases the chance the skill is invoked with broader capabilities than users expect, especially because it also encourages proactive use.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The instructions describe a pipeline that automatically sends images to a Feishu chat, but they do not require an explicit warning or confirmation that user queries and fetched image content will be transmitted to external services and posted into a chat. This creates privacy and integrity risk, especially if the agent chooses image mode based on ambiguous phrasing or uses the wrong chat identifier.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script fetches user/input-derived image URLs and then uploads the resulting image contents to Feishu without any explicit notice or consent boundary. In this skill context, that means externally sourced and potentially sensitive content may be transmitted to third parties automatically, which raises privacy, compliance, and data-handling risks.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest describes a skill for searching the web for real-time information and returning results optimized for Feishu, including visual references. This file does substantially more than search or format results: it loads Feishu app credentials, uploads downloaded images to Feishu, and can send messages directly to Feishu chats, which are separate operational capabilities not implied by merely performing web search.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
Reading fallback credentials from local openclaw.json files expands the credential surface beyond documented environment variables and may silently consume secrets from the host environment. In a skill ecosystem, this is dangerous because it creates unexpected privilege inheritance and can couple the skill to unrelated local deployments or secrets.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This script sends the user's raw query to the external DashScope API for every search, but it provides no user-facing notice or confirmation at execution time that the prompt content will leave the local environment. In a skill explicitly designed to be invoked proactively for arbitrary user requests, this increases the chance that sensitive user data, internal context, or confidential terms are transmitted to a third-party service without informed consent.

Intent-Code Divergence

Low
Confidence
78% confidence
Finding
The docstring states 'Load credentials from env vars, with fallback to openclaw.json,' which partially describes behavior, but the broader module documentation above presents credentials as being read from environment variables without disclosing file-based secret loading. This creates a documentation-level mismatch around how credentials are actually sourced.

Missing User Warnings

Low
Confidence
81% confidence
Finding
The script reads sensitive credentials from environment variables and also searches local config files for Feishu secrets. Although this is functionally necessary, there is no explicit warning in the user-facing interface that credentials will be sourced from the environment or local files, which falls under sensitive credential access for code files.

Static analysis

No suspicious patterns detected.