Back to skill

Security audit

飞书语音发送器(TTS) Feishu Voice Sender

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches its Feishu voice-message purpose, but it deserves review because it can send original prompts and local audio file contents to third-party services with limited built-in scoping.

Install only if you are comfortable granting Feishu and Volcengine credentials and sending message text, original prompt context, generated audio, and explicitly provided .ogg audio files to those services. Avoid passing sensitive prompt text as user_input, restrict ASR file paths and sizes operationally, confirm recipients before sending, and prefer a pinned requests version plus request timeouts.

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

Warning
Location
src/feishu_voice.py:187
Finding
Unnecessary Disclosure of the Complete User Prompt to the TTS Provider## Vulnerability Details **File Location**: `src/feishu_voice.py:187-190` and `src/feishu_voice.py:372` **Vulnerability Type**: Excessive transmission of potentially sensitive user data **Risk Level**: Medium ### Vulnerable Code ```python # Add context_texts when context is provided if context: payload["req_params"]["additions"] = json.dumps({ "context_texts": [context] }) ``` ```python # Generate voice opus_path = generate_voice(text, emotion, user_input) ``` ### Technical Analysis `send_voice_message()` passes the complete `user_input` value to `generate_voice()` as its `context` argument. The function then adds that value to the TTS request as `context_texts`, causing it to be transmitted to `https://openspeech.bytedance.com`. This exceeds the minimum data required to synthesize the requested voice message. The code already calls `detect_emotion()` locally and derives an emotion label, but the `emotion` argument accepted by `generate_voice()` is not used in the outgoing request. Instead, the full original prompt is transmitted. Although the transmission uses HTTPS and targets the documented TTS provider, `user_input` may contain secrets, personal information, identifiers, unrelated conversation details, or instructions that are not part of the text the user requested to synthesize. The documentation describes the context as being used for emotion awareness, but it does not adequately communicate that the complete original request is shared with the external provider. ### Attack Path 1. A caller invokes `send_voice_message()` with legitimate synthesis text. 2. The caller or surrounding Agent places sensitive or unrelated information in `user_input`. 3. `send_voice_message()` passes the complete value to `generate_voice()`. 4. `generate_voice()` serializes it into `req_params.additions.context_texts`. 5. The complete prompt is transmitted to the external ByteDance TTS endpoint. 6. The ...[truncated 635 chars]
Remediation
## Remediation Suggestions - Do not send the raw `user_input` value to the TTS service by default. - Use only the locally derived emotion classification, such as `happy`, `sad`, or `neutral`, if the provider supports an equivalent constrained parameter. - If contextual text is indispensable, require explicit user consent before sharing it with the external service. - Restrict context to a short, purpose-specific value and apply sensitive-data filtering. - Document precisely which fields are transmitted, to which provider, and for what purpose. - Remove the unused `emotion` argument or implement it through a constrained provider-supported option rather than forwarding the complete prompt.

T09 · Insecure Skill Coding Practices

Warning
Location
src/feishu_voice.py:117
Finding
Unbounded Audio File Loading and Base64 Expansion Can Exhaust Memory## Vulnerability Details **File Location**: `src/feishu_voice.py:117-118` and `src/feishu_voice.py:134` **Vulnerability Type**: Unbounded resource consumption **Risk Level**: Medium ### Vulnerable Code ```python # Read audio file with open(audio_path, 'rb') as f: audio_data = f.read() ``` ```python "audio": { "data": base64.b64encode(audio_data).decode('utf-8'), "format": "ogg", "codec": "opus", "rate": 48000, "bits": 16, "channel": 1 }, ``` ### Technical Analysis `recognize_voice_file()` checks that the path ends in `.ogg`, exists, and refers to a file. It does not enforce a maximum file size before reading the entire file into memory. The file is first materialized as a `bytes` object. Base64 encoding then creates another representation approximately one-third larger than the original, and UTF-8 decoding plus JSON serialization can create additional in-memory copies. A sufficiently large file can therefore consume several times its on-disk size in process memory. Checking only the filename extension does not establish that the input is a valid or reasonably sized OGG/Opus audio file. The path may also refer to a very large file or, depending on the surrounding permissions, a symbolic link whose target has an `.ogg` path. ### Attack Path 1. An attacker or untrusted caller creates or identifies a very large file with an `.ogg` suffix. 2. The caller invokes `recognize_voice_file()` or `recognize()` with that path. 3. The Skill reads the complete file into memory with `f.read()`. 4. The Skill creates additional Base64, string, and JSON representations. 5. Process memory is exhausted or severe memory pressure stalls or terminates the Agent. 6. If serialization succeeds, the Skill may also generate an excessively large outbound ASR request. ### Impact Assessment Exploitation does not provide elevated privileges or arbitrary code execution. It can cause denial of se ...[truncated 381 chars]
Remediation
## Remediation Suggestions - Call `os.stat()` before opening the file and reject files above a conservative documented size limit. - Recheck the size while processing where file replacement or concurrent growth is relevant. - Validate the actual OGG/Opus container and media properties rather than relying only on the suffix. - Consider rejecting symbolic links or resolving the path against an approved input directory when the deployment model permits it. - Use bounded streaming transport if supported by the ASR provider. - Apply process-level memory, execution-time, and outbound-request-size limits as defense in depth. - Return a clear validation error when the file exceeds the accepted limit.

T09 · Insecure Skill Coding Practices

Note
Location
src/feishu_voice.py:307
Finding
Feishu Network Requests Lack Timeouts## Vulnerability Details **File Location**: `src/feishu_voice.py:307` and `src/feishu_voice.py:396` **Vulnerability Type**: Unbounded network wait and availability risk **Risk Level**: Low ### Vulnerable Code ```python with open(audio_path, 'rb') as f: files = {'file': ('voice.opus', f, 'audio/opus')} data = {'file_type': 'opus', 'file_name': 'voice.opus'} response = requests.post(url, headers=headers, files=files, data=data) ``` ```python response = requests.post(url, headers=headers, json=payload) ``` ### Technical Analysis The Feishu audio upload and message-send operations call `requests.post()` without a `timeout` argument. Unlike other requests in the same file, these operations can wait indefinitely if a connection is established but the server or an intermediary stops making progress. A missing timeout creates an availability weakness even when the destination is a legitimate HTTPS endpoint. DNS problems, network interception, service degradation, or a stalled response can occupy the executing worker without a defined upper bound. ### Attack Path 1. The Skill begins uploading audio or sending a message to Feishu. 2. Feishu or a network intermediary accepts the connection but delays or never completes the response. 3. Because no connect or read timeout is configured, the request remains blocked. 4. The Skill invocation cannot complete, and an associated Agent worker may remain unavailable. 5. Repeated stalled invocations can consume multiple workers and amplify the denial-of-service effect. ### Impact Assessment This weakness does not grant access to credentials, files, or elevated privileges. Its impact is limited to availability: the current invocation and potentially the shared Agent worker can remain blocked. The broader scope depends on the host's concurrency model. In a single-worker deployment, one stalled request may prevent all subsequent work; in a multi-worker deployment, ...[truncated 46 chars]
Remediation
## Remediation Suggestions - Add explicit connect and read timeouts to both Feishu requests, for example `timeout=(5, 30)`. - Catch `requests.Timeout` and return a controlled, non-sensitive error. - Use only a small number of bounded retries for transient failures. - Apply exponential backoff with jitter and avoid retrying non-idempotent message submissions unless duplicate-send behavior is addressed. - Enforce an overall deadline for the complete send workflow.

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Dependency Version Is Not Reproducibly Pinned## Vulnerability Details **File Location**: `requirements.txt:1` **Vulnerability Type**: Unbounded dependency version selection **Risk Level**: Low ### Vulnerable Code ```text requests>=2.28.0 ``` ### Technical Analysis The dependency declaration accepts any installed or future version of `requests` at or above version 2.28.0. This makes installation results dependent on the package repository state at installation time and prevents the reviewed source tree from identifying the exact code that will execute. The package name is legitimate, and the audit found no evidence of dependency confusion, typosquatting, or a currently malicious package. The risk arises from the absence of reproducible version constraints and integrity verification rather than from a confirmed malicious dependency. ### Attack Path 1. The Skill is installed at a later time or in a different environment. 2. Dependency resolution selects a newer release than the one used during review. 3. The selected release contains an incompatible change, a newly introduced vulnerability, or, in a supply-chain compromise scenario, malicious code. 4. The package is imported by `src/feishu_voice.py`. 5. The dependency executes with the same filesystem, environment, and network privileges as the Skill process. ### Impact Assessment No direct privilege escalation is present in the project code. If a resolved dependency were compromised, it would inherit the Skill process's privileges, including potential access to environment variables containing TTS, ASR, and Feishu credentials. The current finding establishes non-reproducibility and future supply-chain exposure; it does not establish that the presently available `requests` package is malicious.
Remediation
## Remediation Suggestions - Pin `requests` to a reviewed version or a narrowly controlled compatible range. - Generate and commit a lock file for reproducible deployments. - Require package hashes during installation, such as through a hash-locked requirements file. - Use a trusted package index and disable unintended fallback indexes. - Automate dependency vulnerability monitoring and update pinned versions through a reviewed process. - Record and verify transitive dependency versions in addition to the direct dependency.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (22)

Tainted flow: 'payload' from os.getenv (line 282, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}
    }
    
    response = requests.post(url, headers=headers, json=payload, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"ASR request failed: {response.status_code} | {response.text[:500]}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'payload' from os.getenv (line 282, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}
    }
    
    response = requests.post(url, headers=headers, json=payload, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"ASR request failed: {response.status_code} | {response.text[:500]}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'payload' from os.getenv (line 282, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
"context_texts": [context]
        })
    
    response = requests.post(url, headers=headers, json=payload, stream=True, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"TTS request failed: {response.status_code}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'headers' from os.getenv (line 384, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
})
            }
            
            response = requests.post(url, headers=headers, json=payload)
            
            if response.status_code != 200:
                raise RuntimeError(f"Send failed: {response.status_code}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
def get_feishu_token(config: Dict[str, str]) -> str:
    """获取飞书 access token(带缓存)"""
    import requests
    import time
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_feishu_token(config: Dict[str, str]) -> str:
    """获取飞书 access token(带缓存)"""
    import requests
    import time
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
94% confidence
Finding
The skill declares access to sensitive environment variables and clearly relies on network access plus an external binary (ffmpeg), but it does not declare an explicit permission or allowed-tools scope in the skill manifest. That creates a governance gap: reviewers and runtime policy engines cannot easily constrain or audit what the skill is permitted to do, which increases the chance of overbroad execution or unexpected capability use.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
**注意:** 需要系统安装 ffmpeg
- macOS: `brew install ffmpeg`
- Ubuntu: `sudo apt-get install ffmpeg`

## 项目结构
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The skill can read any local path ending in .ogg and exfiltrate its contents to a third-party ASR service, even though the skill's primary purpose is voice sending rather than unrestricted local file access. In an agent environment, this broad file-read capability can be abused to access sensitive local audio artifacts or other renamed files if an attacker can influence the path argument.

External Transmission

Medium
Category
Data Exfiltration
Content
}
    }
    
    response = requests.post(url, headers=headers, json=payload, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"ASR request failed: {response.status_code} | {response.text[:500]}")
Confidence
87% confidence
Finding
This function transmits the contents of a local audio file to an external ASR provider. In the context of an agent skill, external transmission is security-relevant because users or upstream prompts may not expect local file content to be sent off-platform, and the skill accepts arbitrary local .ogg paths.

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

Medium
Category
Data Flow
Content
}
    }
    
    response = requests.post(url, headers=headers, json=payload, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"ASR request failed: {response.status_code} | {response.text[:500]}")
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: 'headers' from requests.post (line 320, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
}
    }
    
    response = requests.post(url, headers=headers, json=payload, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"ASR request failed: {response.status_code} | {response.text[:500]}")
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

Medium
Confidence
88% confidence
Finding
The TTS request fixes the speaker to `zh_male_m191_uranus_bigtts`, which enforces a Chinese voice/locale in behavior. The file does not provide any user opt-in, language-selection parameter, or documented region-specific justification, so this is a natural-language locale policy concern.

External Transmission

Medium
Category
Data Exfiltration
Content
"context_texts": [context]
        })
    
    response = requests.post(url, headers=headers, json=payload, stream=True, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"TTS request failed: {response.status_code}")
Confidence
80% confidence
Finding
The TTS function sends user-provided text and optional context to a third-party speech API. Because context may contain prior conversation text or sensitive user content, this creates a real data egress path outside the host platform.

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

Medium
Category
Data Flow
Content
"context_texts": [context]
        })
    
    response = requests.post(url, headers=headers, json=payload, stream=True, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"TTS request failed: {response.status_code}")
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
opus_path,
            '-y'
        ]
        subprocess.run(cmd, check=True, capture_output=True)
    finally:
        # 清理临时 mp3 文件
        if os.path.exists(mp3_path):
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

External Transmission

Medium
Category
Data Exfiltration
Content
'app_secret': config['FEISHU_APP_SECRET']
    }
    
    response = requests.post(url, headers=headers, json=payload, timeout=30)
    
    if response.status_code != 200:
        raise RuntimeError(f"Failed to get Feishu token: {response.status_code}")
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: 'headers' from requests.post (line 320, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
with open(audio_path, 'rb') as f:
        files = {'file': ('voice.opus', f, 'audio/opus')}
        data = {'file_type': 'opus', 'file_name': 'voice.opus'}
        response = requests.post(url, headers=headers, files=files, data=data)
    
    if response.status_code != 200:
        raise RuntimeError(f"Upload failed: {response.status_code}")
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.

External Transmission

Medium
Category
Data Exfiltration
Content
})
            }
            
            response = requests.post(url, headers=headers, json=payload)
            
            if response.status_code != 200:
                raise RuntimeError(f"Send failed: {response.status_code}")
Confidence
80% confidence
Finding
This call sends an audio message to Feishu, which is the core purpose of the skill but still a real external transmission channel. In an agent setting, any capability that can send content to an external recipient can be abused for data exfiltration if invocation and recipient selection are not tightly governed.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
The skill description and usage instructions are presented in a bilingual but fixed Chinese/English format, with primary operational examples and warnings centered on Chinese phrasing. There is no explicit statement that users may choose their preferred language or locale for interaction, which can conflict with a language-choice policy if user opt-in is required.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
Confidence
94% confidence
Finding
The dependency is specified as `requests>=2.28.0`, which allows any future version and does not guarantee a reproducible or reviewed install. This can cause builds to pick up vulnerable or breaking releases unexpectedly, and because `requests` has had security advisories, leaving the version unpinned increases supply-chain and patch-management risk.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding
The manifest does not pin the `requests` version, so it is impossible to verify whether the installed package includes fixes for known advisories affecting some `requests` releases. In a skill that may make network requests to Feishu/Lark or related services, an unverified HTTP client version can expose the environment to known library flaws depending on what gets installed.

Static analysis

No suspicious patterns detected.