Back to skill

Security audit

Feishu PDF Uploader

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Feishu/Lark file uploader, with some implementation and scoping cautions but no evidence of hidden, deceptive, persistent, or unrelated behavior.

Install only if you intend to let the skill upload selected local files to Feishu/Lark using your configured Feishu app credentials. Use explicit file paths and folder tokens, avoid generic upload prompts for sensitive reports, and be aware large files may consume substantial memory or hang if Feishu/network calls stall.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/upload_pdf.py:65
Finding
Unbounded Whole-File Buffering Can Exhaust Memory## Vulnerability Details **File Location**: `scripts/upload_pdf.py:65-66` and `scripts/upload_pdf.py:94-125` **Vulnerability Type**: Unbounded memory allocation from a caller-selected file **Risk Level**: Medium ### Vulnerable Code ```python # Read file content with open(file_path, "rb") as f: file_data = f.read() ``` ```python # Build multipart body body = b"" # upload_id field body += f"--{boundary}\r\n".encode() body += b'Content-Disposition: form-data; name="upload_id"\r\n\r\n' body += upload_id.encode() + b'\r\n' # seq field (0 for single-part upload) body += f"--{boundary}\r\n".encode() body += b'Content-Disposition: form-data; name="seq"\r\n\r\n' body += b'0\r\n' # size field body += f"--{boundary}\r\n".encode() body += b'Content-Disposition: form-data; name="size"\r\n\r\n' body += str(file_size).encode() + b'\r\n' # file field body += f"--{boundary}\r\n".encode() body += f'Content-Disposition: form-data; name="file"; filename="{file_name}"\r\n'.encode() body += b'Content-Type: application/octet-stream\r\n\r\n' body += file_data body += b'\r\n' # End boundary body += f"--{boundary}--\r\n".encode() ``` ### Technical Analysis The script reads the entire caller-selected file into memory without enforcing a maximum size. It then constructs a second complete multipart request body using repeated concatenation of immutable `bytes` objects. These operations can temporarily require multiple file-sized allocations. This implementation does not provide the bounded-memory multipart behavior advertised by the Skill. Although the Feishu API flow uses an upload-part endpoint, the script submits the file as one in-memory part rather than streaming or uploading bounded chunks. ### Attack Path 1. An attacker or untrusted workflow supplies a path to a very large accessible file, including a large sparse file. 2. The Agent invokes `scripts/upload_pdf.py` with that path. 3. `f.re ...[truncated 712 chars]
Remediation
## Remediation Suggestions - Enforce an explicit maximum accepted file size before reading or uploading the file. - Implement Feishu multipart uploading with fixed-size chunks and the correct `block_num`. - Read one bounded chunk at a time instead of calling `f.read()` without a size. - Use a streaming multipart implementation where supported rather than assembling the complete request body manually. - Avoid repeated immutable byte-string concatenation. - Apply process-level memory limits as defense in depth. - Reject files whose size exceeds either the configured policy or Feishu API limits before obtaining an upload ID.

T09 · Insecure Skill Coding Practices

Warning
Location
uploader.py:61
Finding
Unused Whole-File Read Enables Memory Exhaustion## Vulnerability Details **File Location**: `uploader.py:61-62` **Vulnerability Type**: Unbounded and unnecessary file allocation **Risk Level**: Medium ### Vulnerable Code ```python # 2. 上传文件内容 with open(file_path, 'rb') as f: file_content = f.read() ``` ### Technical Analysis `upload_pdf()` reads the entire caller-selected file into memory without a size restriction. The resulting `file_content` value is never used: the implementation proceeds directly to `upload_finish` and never calls Feishu's `upload_part` endpoint. Consequently, this allocation is unnecessary for the implemented network flow and creates an avoidable denial-of-service condition. The incomplete upload sequence also means this implementation is unlikely to upload the file successfully. ### Attack Path 1. An attacker or untrusted task provides a path to a very large file that the Agent can already access. 2. The Agent invokes `FeishuPDFUploader.upload_pdf()`. 3. The method obtains upload metadata and executes an unrestricted `f.read()`. 4. Memory is exhausted or the process is terminated before it reaches `upload_finish`. Exploitation requires control or influence over the selected input path and the ability to trigger this method. ### Impact Assessment The issue affects availability rather than authorization. No additional privileges are obtained, but the upload process can be terminated and colocated Agent tasks may experience resource pressure. The affected scope depends on the memory limits and isolation applied to the process.
Remediation
## Remediation Suggestions - Remove the unused whole-file read. - Complete the required `upload_part` operation before calling `upload_finish`. - Upload through bounded chunks or a streaming file object. - Validate the file size against an explicit policy and the remote API limit. - Calculate `block_num` from the actual chunking strategy instead of always setting it to one. - Add tests that verify both successful content transmission and bounded memory consumption.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/upload_pdf.py:27
Finding
Feishu HTTP Requests Have No Timeouts## Vulnerability Details **File Location**: `scripts/upload_pdf.py:27`, `scripts/upload_pdf.py:76`, `scripts/upload_pdf.py:131`, and `scripts/upload_pdf.py:143` **Vulnerability Type**: Indefinitely blocking network operations **Risk Level**: Medium ### Vulnerable Code ```python resp = requests.post(url, headers=headers, json=data) ``` ```python resp = requests.post(prepare_url, headers=headers, json=prepare_data) ``` ```python resp = requests.post(upload_url, headers=upload_headers, data=body) ``` ```python resp = requests.post(finish_url, headers=headers, json=finish_data) ``` ### Technical Analysis None of the token, prepare, upload, or finish requests specifies a connection or read timeout. Python Requests does not impose a default timeout, so a connection or response that stalls can block the Agent indefinitely. The destinations are hard-coded official Feishu HTTPS endpoints, and the audit found no unrelated exfiltration destination. Nevertheless, network failure, a malfunctioning proxy, a stalled remote service, or an attacker with relevant network influence can prevent these calls from returning. ### Attack Path 1. The Agent starts an upload and reaches one of the four HTTP requests. 2. A network intermediary, configured proxy, or remote connection accepts the request but stops making progress. 3. Because no timeout is configured, the Requests call continues waiting. 4. The Agent worker remains occupied and the task cannot complete without external cancellation or process termination. A deliberate attack generally requires control over a relevant network path or proxy. The same impact can occur through ordinary network or service failure. ### Impact Assessment No additional system privileges or Feishu permissions are obtained. The impact is denial of service against the current Agent worker and potentially reduced service capacity if multiple stalled uploads consume multiple workers. The to ...[truncated 77 chars]
Remediation
## Remediation Suggestions - Set explicit connection and read timeouts on every request, for example `timeout=(5, 60)`. - Catch `requests.Timeout` separately and return a controlled failure. - Use bounded retries with exponential backoff only where retry semantics are safe. - Do not retry a potentially completed upload part without confirming Feishu's idempotency requirements. - Apply an overall operation deadline in addition to per-request timeouts. - Ensure response parsing and error handling also cover non-JSON and unsuccessful HTTP responses.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The documented behavior claims full file upload support, including multipart/chunked upload, but the implementation reportedly does not actually send file bytes and only performs prepare/finalize-style API calls. This mismatch is security-relevant because users may trust the skill with sensitive files or operational workflows under false assumptions, causing data handling errors, broken audit expectations, or accidental disclosure of metadata/credentials without accomplishing the stated task.

Credential Access

High
Category
Privilege Escalation
Content
def get_tenant_token(app_id: str, app_secret: str) -> str:
    """Get tenant access token from Feishu."""
    url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    headers = {"Content-Type": "application/json"}
    data = {"app_id": app_id, "app_secret": app_secret}
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def get_tenant_token(app_id: str, app_secret: str) -> str:
    """Get tenant access token from Feishu."""
    url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    headers = {"Content-Type": "application/json"}
    data = {"app_id": app_id, "app_secret": app_secret}
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises behavior that requires reading local files, using credentials from configuration or environment, and making outbound network requests, but it declares no explicit tool scope or permission boundaries. In an agent environment, this weakens least-privilege controls and can allow the skill to access sensitive local data or secrets more broadly than intended when invoked.

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

Medium
Category
Data Flow
Content
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    headers = {"Content-Type": "application/json"}
    data = {"app_id": app_id, "app_secret": app_secret}
    resp = requests.post(url, headers=headers, json=data)
    result = resp.json()
    
    if result.get("code") != 0:
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.

Tainted flow: 'finish_data' from requests.post (line 141, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
finish_url = "https://open.feishu.cn/open-apis/drive/v1/files/upload_finish"
    finish_data = {"upload_id": upload_id, "block_num": 1}
    
    resp = requests.post(finish_url, headers=headers, json=finish_data)
    result = resp.json()
    
    if result.get("code") != 0:
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.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The skill silently pulls Feishu credentials from environment variables and ~/.openclaw/openclaw.json, expanding credential access beyond the explicit CLI inputs a user might expect. In an agent-skill context, this is more dangerous because it can use ambient local secrets without clear disclosure, enabling unintended authenticated actions against the user's Feishu tenant.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code reads ~/.openclaw/openclaw.json to obtain Feishu app credentials without prominent user-facing disclosure. In a skill ecosystem, silent access to local secret stores is risky because users may believe they are only uploading a file, not granting the skill access to preexisting account credentials.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger phrase "上传报告" is broad and can match many ordinary user requests that are not specifically about uploading files to Feishu. In an agent environment, overly broad activation can cause the skill to run in unintended contexts, increasing the chance of unexpected file handling or data transfer to an external service.

Vague Triggers

Medium
Confidence
87% confidence
Finding
The example phrase "保存报告到云上" broadens the apparent activation scope beyond the declared Feishu-specific upload behavior. This can teach routing systems or users that generic cloud-save requests should invoke this skill, potentially causing unintended uploads of sensitive documents to Feishu.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This code accesses sensitive credentials via FEISHU_APP_ID and FEISHU_APP_SECRET, then sends them in an HTTP request to obtain a tenant token. Although this is part of authentication, the file provides no user-facing warning, confirmation, or explanatory comment/docstring about using environment-stored secrets and sending them off-host.

External Transmission

Medium
Category
Data Exfiltration
Content
"app_secret": self.app_secret
        }
        try:
            resp = requests.post(url, headers=headers, json=data, timeout=10)
            if resp.status_code == 200:
                self.token = resp.json().get("tenant_access_token")
                return self.token
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"app_secret": self.app_secret
        }
        try:
            resp = requests.post(url, headers=headers, json=data, timeout=10)
            if resp.status_code == 200:
                self.token = resp.json().get("tenant_access_token")
                return self.token
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The skill claims to perform multipart/chunked upload, but it never calls the upload_part step and instead reads the file locally then immediately calls upload_finish. This mismatch is dangerous because users may trust it for reliable large-file transfer or assume data was uploaded when it was not, causing silent failure, data loss, or incorrect workflow automation around sensitive documents.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script uploads local file contents to an external cloud service without any explicit user-facing warning, confirmation, or data-sensitivity notice. In an agent-skill context this is more dangerous because users may invoke it as a convenience tool without realizing it transmits potentially sensitive local documents off-host to a third party.

External Transmission

Medium
Category
Data Exfiltration
Content
}
        
        try:
            resp = requests.post(prepare_url, headers=headers, json=prepare_data, timeout=10)
            if resp.status_code != 200:
                return {"error": f"获取上传凭证失败: {resp.text}"}
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}
        
        try:
            resp = requests.post(prepare_url, headers=headers, json=prepare_data, timeout=10)
            if resp.status_code != 200:
                return {"error": f"获取上传凭证失败: {resp.text}"}
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"block_num": 1
            }
            
            resp = requests.post(finish_url, headers=headers, json=finish_data, timeout=10)
            if resp.status_code == 200:
                result = resp.json().get("data", {})
                return {
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"block_num": 1
            }
            
            resp = requests.post(finish_url, headers=headers, json=finish_data, timeout=10)
            if resp.status_code == 200:
                result = resp.json().get("data", {})
                return {
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Tainted flow: 'finish_data' from requests.post (line 72, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
"block_num": 1
            }
            
            resp = requests.post(finish_url, headers=headers, json=finish_data, timeout=10)
            if resp.status_code == 200:
                result = resp.json().get("data", {})
                return {
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.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
The manifest content, aliases, triggers, and examples are entirely in Chinese, with no indication that other languages are supported or that Chinese is an intentional, documented locale restriction. This can violate language/locale policy if users are not offered a choice or informed of the limitation.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
Natural-language text exposed to users, including the module description, CLI description, and help strings, is exclusively in Chinese. This imposes a language choice on users without opt-in or alternative locale support, which matches the language-policy violation criteria.

Description-Behavior Mismatch

Low
Confidence
91% confidence
Finding
Both the manifest and module text emphasize uploading PDF files, but upload_pdf only checks that the path exists and derives the filename; it does not verify extension or content type. This means the code's behavior is broader than the stated PDF-focused purpose.

Intent-Code Divergence

Low
Confidence
90% confidence
Finding
The function-level documentation specifically states it uploads PDF files, creating an intent expectation for PDF-only handling. The actual code does not check extension, MIME type, or file signature, so the documented intent contradicts the broader behavior.

Static analysis

No suspicious patterns detected.