Back to skill

Security audit

Feishu Video Message

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it can fetch arbitrary URLs and forward the result to Feishu using local credentials without strong destination or size limits.

Install only if you trust agents to send media through your Feishu app credentials. Use it with explicit current-chat receive IDs and trusted public HTTPS video URLs; avoid attacker-supplied URLs until the skill enforces URL allowlisting, private-network blocking, redirect validation, and the documented 30 MB size limit.

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

Error
Location
scripts/feishu_send_video.py:139
Finding

Arbitrary URL Fetching Enables SSRF and Potential Forwarding of Internal Resources

Content
View full analysis
Path: parsed = urlparse(url) suffix = Path(parsed.path).suffix.lower() if suffix not in VIDEO_EXTENSIONS: suffix = ".mp4" fd, tmp_name = tempfile.mkstemp(prefix="_ovvideo_", suffix=suffix) os.close(fd) tmp_path = Path(tmp_name) with requests.get(url, stream=True, timeout=300) as resp: resp.raise_for_status() with tmp_path.open("wb") as f: for chunk in resp.iter_content(chunk_size=1 << 20): if chunk: f.write(chunk) if tmp_path.stat().st_size == 0: tmp_path.unlink(missing_ok=True) raise RuntimeError(f"Downloaded empty file from: {url}") return tmp_path ``` The downloaded response is later uploaded to Feishu and sent to the selected recipient: ```python if args.url: downloaded_path = download_video(args.url) file_path = downloaded_path else: file_path = Path(args.file) ``` ```python file_key = upload_video(token, file_path) result = send_video_message( token, receive_id, receive_id_type, file_key, image_key ) ``` ### Technical Analysis The caller can provide an arbitrary value to `--url`. The implementation parses the path only to derive a filename extension and then passes the URL directly to `requests.get`. It does not validate: - The URL scheme. - Embedded URL credentials. - The destination hostname. - DNS-resolved IP addresses. - Loopback, private, link-local, multicast, or reserved network ranges. - Redirect destinations. The `requests` library follows redirects by default. Therefore, even an initially public URL can redirect the Agent to an internal address. Assigning an `.mp4` suffi ...[truncated 1934 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/feishu_send_video.py:149
Finding

Unbounded Remote Download Can Exhaust Disk and Network Resources

Content
View full analysis
Remediation
View remediation
MAX_VIDEO_BYTES: raise ValueError("Remote video exceeds the 30 MB limit") f.write(chunk) ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/feishu_send_video.py:159
Finding

Predictable Cover Path and Incomplete Failure Cleanup Expose Temporary Files

Content
View full analysis
Optional[Path]: cover_path = Path(tempfile.gettempdir()) / f"_cover_{os.getpid()}.jpg" try: subprocess.run( [ "ffmpeg", "-y", "-ss", "00:00:01", "-i", str(video_path), "-vframes", "1", "-q:v", "2", str(cover_path), ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60, ) if cover_path.exists() and cover_path.stat().st_size > 0: return cover_path except Exception as exc: print(f"Warning: extract cover failed, fallback to no cover: {exc}") return None ``` The remote temporary file is created securely, but an exception during the download can occur before the path is returned: ```python fd, tmp_name = tempfile.mkstemp(prefix="_ovvideo_", suffix=suffix) os.close(fd) tmp_path = Path(tmp_name) with requests.get(url, stream=True, timeout=300) as resp: resp.raise_for_status() with tmp_path.open("wb") as f: for chunk in resp.iter_content(chunk_size=1 << 20): if chunk: f.write(chunk) ``` The caller's cleanup block is entered only after `download_video` returns successfully: ```python downloaded_path: Optional[Path] = None if args.url: downloaded_path = download_video(args.url) file_path = downloaded_path ``` ### Technical Analysis The cover path is predictable because process identifiers are observable or guessable in many multi-user environments. The path ...[truncated 2492 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill explicitly performs network access, local file handling, and shell execution via python3/ffmpeg/ffprobe, but the manifest does not declare any tool scope such as permissions or allowed-tools. This creates an authorization and review gap: an agent or operator cannot easily constrain or audit the skill’s real capabilities, increasing the risk of unintended file access, arbitrary URL fetching, and command execution in a sensitive environment.

Content

No source excerpt is available for this finding.

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/feishu_send_video.py (reported line 113)May include surrounding context.

python
def get_tenant_access_token(app_id: str, app_secret: str) -> str:
    resp = requests.post(
        FEISHU_TOKEN_URL,
        json={"app_id": app_id, "app_secret": app_secret},
        timeout=15,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/feishu_send_video.py (reported line 150)May include surrounding context.

python
"""用 ffmpeg 抽取第 1 秒首帧作为封面,失败返回 None。"""
    cover_path = Path(tempfile.gettempdir()) / f"_cover_{os.getpid()}.jpg"
    try:
        subprocess.run(
            [
                "ffmpeg", "-y",
                "-ss", "00:00:01",

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/feishu_send_video.py (reported line 174)May include surrounding context.

python
def probe_duration_ms(video_path: Path) -> Optional[int]:
    """用 ffprobe 探测视频时长,返回毫秒;失败返回 None。"""
    try:
        out = subprocess.run(
            [
                "ffprobe", "-v", "error",
                "-show_entries", "format=duration",

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

The skill sends arbitrary video content to a user-supplied receive_id with only minimal normalization and prefix-based type inference, creating a real risk of data being transmitted to the wrong Feishu recipient. In this skill context, the agent is explicitly designed to forward remote video URLs into chat, so a bad or attacker-influenced receive_id can cause unintended cross-chat disclosure of potentially sensitive media.

Content

Scanner excerpt · scripts/feishu_send_video.py (reported line 259)May include surrounding context.

python
"msg_type": "media",
        "content": json.dumps(media_content),
    }
    resp = requests.post(
        FEISHU_SEND_MSG_URL,
        headers=headers,
        params=params,

Static analysis

No suspicious patterns detected.