Back to skill

Security audit

Video To Text

Security checks for vulnerabilities and agentic risk

Overview

This transcription skill does what it claims, but it has serious unsafe execution and network-fetch behavior that should be reviewed before installation.

Review before installing. Do not use this skill with private recordings, signed URLs, internal service URLs, or confidential meeting audio unless the command-injection issue is fixed and you are comfortable sending the media to third-party transcription providers. A safer version should remove shell-string execution, restrict URL targets, enforce download limits during streaming, clean temporary files on failure, and add explicit external-upload disclosure and consent.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
tool.js:26
Finding
Arbitrary Command Execution Through Shell Command Injection<![CDATA[ ## Vulnerability Details **File Location**: `tool.js:26-44` **Vulnerability Type**: Shell command injection **Risk Level**: Critical ### Vulnerable Code ```js try { // 构建命令 const scriptPath = path.join(__dirname, 'index.js'); const args = [ 'node', scriptPath, '--url', url, '--language', language, '--format', output_format ]; // 执行转写 const output = execSync(args.join(' '), { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, // 10MB timeout: 300000 // 5分钟超时 }); ``` ### Technical Analysis The `url`, `language`, and `output_format` values originate from tool parameters. They are placed into an argument array, joined into a single string, and passed to `execSync`. When `execSync` receives a string, Node.js executes it through a system shell. Because the parameter values are neither escaped nor validated, shell metacharacters such as command separators, substitutions, pipes, and redirections are interpreted by the shell instead of being passed exclusively to `index.js`. The metadata restricts `output_format` through an enum, but runtime enforcement is not performed by this function. More importantly, `url` and `language` remain directly injectable. ### Attack Path 1. An attacker or untrusted user invokes `video_to_text`. 2. The attacker supplies a tool parameter containing shell syntax, such as a URL followed by a shell command separator and an attacker-selected command. 3. `video_to_text` adds the malicious value to `args`. 4. `args.join(' ')` constructs a shell command string containing the injected syntax. 5. `execSync` invokes the system shell. 6. The shell interprets and executes the injected command with the permissions of the Agent process. No successful media download or transcription is required for exploitation because the injected command is interpreted before `index.js` validates or processes the URL. ### Impact Assessment Successful exploitation provides arbitrary operating-system comm ...[truncated 603 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace shell-string execution with an API that passes arguments directly and does not invoke a shell: ```js const { execFileSync } = require('child_process'); const output = execFileSync( process.execPath, [scriptPath, '--url', url, '--language', language, '--format', output_format], { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 300000, shell: false } ); ``` - Alternatively, use `spawn` or `spawnSync` with an argument array and `shell: false`. - Validate `language` against an explicit allowlist such as `zh`, `en`, and `ja`. - Validate `output_format` at runtime against `text` and `srt`; do not rely solely on metadata validation. - Parse `url` with the standard `URL` class and reject malformed values. - Do not attempt to mitigate this issue only by manually quoting values. Eliminating the shell is the reliable fix. - Run the Skill under a restricted service account with minimal filesystem, network, and credential access as defense in depth. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.js:27
Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery and External Relaying of Responses<![CDATA[ ## Vulnerability Details **File Location**: `index.js:27-58`, with external upload at `index.js:74-127` **Vulnerability Type**: Server-side request forgery and unintended data relay **Risk Level**: High ### Vulnerable Code ```js async function downloadFile(url) { return new Promise((resolve, reject) => { const protocol = url.startsWith('https') ? https : http; console.log(`📥 正在下载文件: ${url}`); protocol.get(url, (response) => { // 检查 Content-Length const contentLength = parseInt(response.headers['content-length'] || '0', 10); if (contentLength > CONFIG.maxFileSize) { reject(new Error(`文件太大: ${(contentLength / 1024 / 1024).toFixed(2)}MB,最大支持 ${CONFIG.maxFileSize / 1024 / 1024}MB`)); return; } const tmpDir = os.tmpdir(); const ext = path.extname(url.split('?')[0]) || '.tmp'; const tempFile = path.join(tmpDir, `video-to-text-${Date.now()}${ext}`); const fileStream = fs.createWriteStream(tempFile); let downloaded = 0; response.on('data', (chunk) => { downloaded += chunk.length; if (contentLength) { const percent = ((downloaded / contentLength) * 100).toFixed(1); process.stdout.write(`\r📥 下载进度: ${percent}%`); } }); response.pipe(fileStream); fileStream.on('finish', () => { console.log(`\n✅ 文件已保存到: ${tempFile}`); resolve(tempFile); }); ``` The resulting file is read in full and submitted to the configured MyShell endpoint: ```js async function transcribeWithMyShell(filePath, language = 'zh') { const boundary = '----FormBoundary' + Date.now(); const filename = path.basename(filePath); const fileContent = fs.readFileSync(filePath); // ... const body = Buffer.concat([header, fileContent, languagePart, modelPart, footer]); return new Promise((resolve, reject) => { const url = new URL(CONFIG.primaryApi); const options = { hostname: url. ...[truncated 2569 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer a strict allowlist of approved public media hosts. - Parse input with `new URL(url)` and accept only explicitly supported protocols, preferably HTTPS. - Resolve the hostname before connecting and reject every address in loopback, private, link-local, multicast, unspecified, and reserved ranges for both IPv4 and IPv6. - Pin the validated resolved address for the connection or otherwise prevent DNS rebinding between validation and use. - If redirects are added, validate every redirect destination using the same controls. - Reject URLs containing embedded credentials. - Verify successful HTTP status codes before processing the response. - Enforce an allowlist of expected media MIME types and inspect file signatures rather than relying only on extensions. - Consider requiring the user to upload a file through a controlled attachment mechanism instead of permitting arbitrary server-side URLs. - Obtain explicit user consent before sending media to a third-party transcription provider, particularly when media may contain confidential content. - Apply outbound network controls so the Skill process cannot reach metadata services or internal administrative networks. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:32
Finding
Download Size Limit Bypass and Sensitive Temporary File Retention<![CDATA[ ## Vulnerability Details **File Location**: `index.js:32-58` and `index.js:168-186` **Vulnerability Type**: Unbounded download and unsafe temporary-file lifecycle **Risk Level**: Medium ### Vulnerable Code The download limit relies only on the remote server's optional `Content-Length` header: ```js protocol.get(url, (response) => { // 检查 Content-Length const contentLength = parseInt(response.headers['content-length'] || '0', 10); if (contentLength > CONFIG.maxFileSize) { reject(new Error(`文件太大: ${(contentLength / 1024 / 1024).toFixed(2)}MB,最大支持 ${CONFIG.maxFileSize / 1024 / 1024}MB`)); return; } const tmpDir = os.tmpdir(); const ext = path.extname(url.split('?')[0]) || '.tmp'; const tempFile = path.join(tmpDir, `video-to-text-${Date.now()}${ext}`); const fileStream = fs.createWriteStream(tempFile); let downloaded = 0; response.on('data', (chunk) => { downloaded += chunk.length; if (contentLength) { const percent = ((downloaded / contentLength) * 100).toFixed(1); process.stdout.write(`\r📥 下载进度: ${percent}%`); } }); response.pipe(fileStream); fileStream.on('finish', () => { console.log(`\n✅ 文件已保存到: ${tempFile}`); resolve(tempFile); }); fileStream.on('error', (err) => { fs.unlink(tempFile, () => {}); reject(err); }); ``` Cleanup is performed only after successful transcription: ```js try { console.log('🎬 开始视频转文字处理...'); console.log(`📎 输入文件: ${url}`); console.log(`🌐 语言: ${language}`); console.log(`📝 输出格式: ${outputFormat}`); // 下载文件 const filePath = await downloadFile(url); // 转写 console.log('🔄 正在识别语音...'); const text = await transcribeWithMyShell(filePath, language); // 清理临时文件 fs.unlink(filePath, () => {}); // 输出结果 ``` ### Technical Analysis The stated 25 MB limit is checked only when the remote server supplies a trustworthy `Content-Length` header. A server can omit the header, use chunked transfer encoding, or report an ...[truncated 1958 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Enforce the size limit against actual bytes received: ```js response.on('data', (chunk) => { downloaded += chunk.length; if (downloaded > CONFIG.maxFileSize) { response.destroy(new Error('Downloaded file exceeds the maximum size')); fileStream.destroy(); fs.unlink(tempFile, () => {}); } }); ``` - Treat `Content-Length` only as an early rejection optimization, not as the authoritative limit. - Set connection, response, idle, and total-operation timeouts. - Use `stream.pipeline` so source and destination errors are propagated consistently. - Create a unique private directory with `fs.mkdtemp`, use restrictive file permissions, and remove the directory recursively after completion. - Put cleanup in a `finally` block so files are removed on success, API failure, parsing failure, or other exceptions. - Avoid `fs.readFileSync` for potentially large content. Stream the file into the outbound multipart request or use a multipart library with bounded streaming. - Ensure partially written files are deleted when the response aborts or the configured size limit is exceeded. - Consider periodic cleanup of stale files from prior interrupted executions as defense in depth. ]]>
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (32)

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The code builds a shell command by concatenating user-controlled values (`url`, `language`, and `output_format`) into a single string and passes it to `execSync`. This creates a command injection risk, allowing crafted input to break shell argument boundaries and execute arbitrary commands with the privileges of the process.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill explicitly routes user-provided media to third-party transcription services but does not warn users that their URLs and underlying audio/video content may be transmitted off-platform. This creates a privacy and consent risk, especially if users submit sensitive recordings, private links, or copyrighted material under the assumption processing is local or first-party only.

External Transmission

Medium
Category
Data Exfiltration
Content
此技能使用免费的 Whisper API 服务进行语音识别,无需 API Key,直接调用即可使用。

支持的免费 API 端点:
- https://api.myshell.ai/v1/audio/transcriptions (MyShell Whisper)
- https://api.openai.com/v1/audio/transcriptions (OpenAI,需要Key)

如果主要API不可用,会自动尝试备用方案。
Confidence
94% confidence
Finding
This skill declares transmission of user media to an external MyShell API for transcription. External transmission is risky here because uploaded media may contain sensitive speech, personal data, confidential business information, or authenticated file URLs, and the skill does not describe consent, minimization, or privacy safeguards.

External Transmission

Medium
Category
Data Exfiltration
Content
支持的免费 API 端点:
- https://api.myshell.ai/v1/audio/transcriptions (MyShell Whisper)
- https://api.openai.com/v1/audio/transcriptions (OpenAI,需要Key)

如果主要API不可用,会自动尝试备用方案。
Confidence
93% confidence
Finding
The listed OpenAI transcription endpoint represents another third-party destination for user media, increasing data-sharing surface area and introducing possible credential, billing, and privacy concerns. Because the skill suggests automatic fallback, users may not know which external provider receives their content at runtime.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger list includes a very broad phrase, "免费转文字" (free transcription), which can match ordinary user requests that are not clearly intended to invoke this specific skill. Overly broad activation increases the chance of accidental invocation, causing users to send media URLs to an external service without clear intent or awareness.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The tool description says it uses a free Whisper API and requires no API key, but it does not clearly warn that user-supplied media URLs and potentially their contents will be transmitted to an external transcription service. This creates a privacy and consent risk, especially if users provide private recordings, internal meeting audio, or signed URLs that grant temporary access to sensitive files.

External Transmission

Medium
Category
Data Exfiltration
Content
// 配置
const CONFIG = {
  // 首选免费 API (MyShell)
  primaryApi: 'https://api.myshell.ai/v1/audio/transcriptions',
  // 备用 API (OpenAI,需要Key时可配置)
  backupApi: 'https://api.openai.com/v1/audio/transcriptions',
  maxFileSize: 25 * 1024 * 1024, // 25MB
Confidence
93% confidence
Finding
The skill is explicitly designed to send user-supplied audio/video content to an external API endpoint, which is a real external data transmission path. In this context, the danger is heightened because the tool downloads arbitrary remote media and forwards it to a third party, potentially exposing sensitive content without strong user awareness or administrative control.

External Transmission

Medium
Category
Data Exfiltration
Content
// 首选免费 API (MyShell)
  primaryApi: 'https://api.myshell.ai/v1/audio/transcriptions',
  // 备用 API (OpenAI,需要Key时可配置)
  backupApi: 'https://api.openai.com/v1/audio/transcriptions',
  maxFileSize: 25 * 1024 * 1024, // 25MB
};
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The code reads the downloaded media file and uploads its full contents to a third-party transcription service without any explicit consent prompt, privacy notice, or host allowlisting. Because audio/video may contain sensitive personal, biometric, or confidential business information, silent transmission to an external service creates a real privacy and data-handling risk.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The transcription function defaults the language parameter to 'zh', and the tool description and CLI usage are written to assume Chinese by default. This can constitute a language/locale policy violation because the skill imposes a specific language choice unless the user explicitly overrides it.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The main entrypoint initializes the language setting to 'zh' before parsing any user input, meaning Chinese is selected even when the user does not request it. This is a natural-language policy concern because the tool forces a locale choice by default rather than obtaining user opt-in.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = ['ffmpeg', '-i', video_path, '-vn', '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', output_path, '-y']
    try:
        subprocess.run(cmd, check=True)
        print(f"✅ 音频已提取: {output_path}")
        return output_path
    except subprocess.CalledProcessError:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
print(f"✅ 音频已提取: {output_path}")
        return output_path
    except subprocess.CalledProcessError:
        print("❌ ffmpeg 未安装,请先安装: sudo apt install ffmpeg")
        return None

def transcribe_with_local(video_path, model='base', language='zh'):
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Natural-Language Policy Violations

Medium
Confidence
85% confidence
Finding
The local transcription function defaults to language='zh', and the CLI also defaults --lang to zh, which means the skill assumes a specific language unless the user overrides it. This is a natural-language policy concern because the tool forces a locale choice rather than prompting or auto-detecting by default.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
When the AssemblyAI mode is used, the script uploads local audio content to a third-party service without any explicit consent prompt, privacy warning, or data-handling notice. In a transcription tool, media commonly contains sensitive conversations or personal information, so silent external transfer can cause confidentiality and compliance issues.

External Transmission

Medium
Category
Data Exfiltration
Content
# 上传音频
    with open(audio_path, 'rb') as f:
        response = requests.post(
            'https://api.assemblyai.com/v2/upload',
            headers={'authorization': api_key},
            files={'file': f}
Confidence
90% confidence
Finding
This code transmits the contents of a local audio file to an external third-party endpoint. In the context of a video-to-text skill, uploaded media may contain confidential or regulated data, so outbound transfer is security-relevant and should not happen without clear user awareness and policy controls.

External Transmission

Medium
Category
Data Exfiltration
Content
# 上传音频
    with open(audio_path, 'rb') as f:
        response = requests.post(
            'https://api.assemblyai.com/v2/upload',
            headers={'authorization': api_key},
            files={'file': f}
        )
Confidence
60% 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
# 上传音频
    with open(audio_path, 'rb') as f:
        response = requests.post(
            'https://api.assemblyai.com/v2/upload',
            headers={'authorization': api_key},
            files={'file': f}
        )
Confidence
60% 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
# 上传音频
    with open(audio_path, 'rb') as f:
        response = requests.post(
            'https://api.assemblyai.com/v2/upload',
            headers={'authorization': api_key},
            files={'file': f}
        )
Confidence
60% 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
audio_url = response.json()['upload_url']
    
    # 转写
    response = requests.post(
        'https://api.assemblyai.com/v2/transcript',
        headers={'authorization': api_key},
        json={'audio_url': audio_url, 'language_code': 'zh'}
Confidence
70% 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
audio_url = response.json()['upload_url']
    
    # 转写
    response = requests.post(
        'https://api.assemblyai.com/v2/transcript',
        headers={'authorization': api_key},
        json={'audio_url': audio_url, 'language_code': 'zh'}
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The remote transcription branch always sends language_code 'zh' to the API, regardless of user input. This forces a specific language/locale in a way that is not optional or justified in the file.

Tainted flow: 'transcript_id' from requests.post (line 63, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
# 等待结果
    import time
    while True:
        response = requests.get(
            f'https://api.assemblyai.com/v2/transcript/{transcript_id}',
            headers={'authorization': api_key}
        )
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.

Intent-Code Divergence

Medium
Confidence
76% confidence
Finding
The module comment and function docblock present the code as directly converting video/audio to text. In practice, the function's core behavior is to construct and execute a shell command string calling node on another script, which is a materially different operational behavior than the documentation suggests.

Missing User Warnings

Medium
Confidence
77% confidence
Finding
The skill accepts a video/audio file URL and passes it to another script for transcription, implying retrieval or transmission of remote content. The file contains no explicit warning to the user that a provided URL will be fetched/processed, beyond the parameter name and brief function description.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tool.js:40