Back to skill

Security audit

guaikei-video-copywriting-extractor

Security checks for vulnerabilities and agentic risk

Overview

The skill does the advertised video-to-text workflow, but it can fetch arbitrary URLs and upload any readable local file to cloud storage without enough scoping or confirmation.

Review before installing. Use this only with videos you intentionally want processed by the vendor cloud service, run it from a low-privilege account with access limited to intended media files, avoid private/internal URLs, and treat --id last as prior-task reuse. The publisher should add confirmation prompts, media validation, private-network blocking, upload host allowlisting, size limits, and clearer disclosure of all storage destinations.

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/utils/validator.js:3
Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery and Internal Data Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/utils/validator.js:3-8`; `scripts/utils/download.js:479-492, 911-922`; `scripts/video2text/index.js:96-124` **Vulnerability Type**: Server-Side Request Forgery (SSRF) with subsequent data upload **Risk Level**: High ### Vulnerable Code ```js // scripts/utils/validator.js:3-8 function isUrl(url) { try { const parsedUrl = new URL(url); return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:"; } catch (_) { return false; } } ``` ```js // scripts/utils/download.js:479-492 if (this.__isRequireRedirect(response)) { this.__redirectCount++; if (this.__redirectCount > this.__opts.maxRedirects) { const err = new Error("Too many redirects"); this.__setState(this.__states.FAILED); this.emit("error", err); return reject(err); } const redirectedURL = /^https?:\/\//.test(response.headers.location) ? response.headers.location : new URL(response.headers.location, this.url).href; this.__isRedirected = true; this.__initProtocol(redirectedURL); this.emit("redirected", redirectedURL, this.url); return this.__start(); } ``` ```js // scripts/utils/download.js:911-922 __getReqOptions(method, url, headers = {}) { const urlParse = new URL(url); const options = { protocol: urlParse.protocol, host: urlParse.hostname, port: urlParse.port, path: urlParse.pathname + urlParse.search, method, }; if (urlParse.username || urlParse.password) { options.auth = `${urlParse.username}:${urlParse.password}`; } ``` ```js // scripts/video2text/index.js:96-106 if (validator.isUrl(file)) { const filepath = utils.downloadPath(); try { await fs.promises.mkdir(filepath, { recursive: true }); } catch (error) { utils.printError("临时下载目录创建失败: " + (error.message || String(error))); process.exit(1); } try { const downloadResult = await helper.download(file, filepath); ``` ### Technical Analysis URL validation c ...[truncated 2074 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve the destination hostname before connecting and reject all non-public IPv4 and IPv6 ranges, including: - Loopback ranges. - RFC1918 private networks. - Link-local and cloud metadata ranges. - Unique-local IPv6 addresses. - Multicast, unspecified, and reserved ranges. 2. Repeat DNS resolution and address validation for every redirect. 3. Prevent DNS rebinding by connecting only to the validated address while preserving the expected TLS server name. 4. Apply an allowlist of supported video-hosting domains where operationally possible. 5. Reduce the maximum redirect count and reject protocol downgrades from HTTPS to HTTP. 6. Disable URL user information unless explicitly required. 7. Separate downloading from uploading and require confirmation before an HTTP response from an unusual destination is sent to the cloud service. 8. Add automated tests for direct and redirected requests to loopback, private IPv4, IPv6 local, and metadata addresses. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/utils/upload.js:11
Finding
Insufficient Validation of Local Files and API-Controlled Upload Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/utils/validator.js:11-18`; `scripts/video2text/index.js:112-149`; `scripts/utils/upload.js:11-37` **Vulnerability Type**: Unrestricted local file selection and insufficient outbound upload allowlisting **Risk Level**: High ### Vulnerable Code ```js // scripts/utils/validator.js:11-18 function isFilePath(path) { try { const stats = fs.statSync(path); return stats.isFile(); } catch (_) { return false; } } ``` ```js // scripts/video2text/index.js:112-149 } else if (!validator.isFilePath(file)) { utils.printError("无效的文件路径或URL"); process.exit(1); } if (!fs.existsSync(file)) { utils.printError("文件不存在: " + file); process.exit(1); } try { const presignedUrl = await video.getPresignedUrl(tokenValue, file); if (!presignedUrl || !presignedUrl?.url || presignedUrl.url === "") { throw new Error("获取预签名URL失败,请反馈给开发者"); } utils.printInfo("上传文件到安全空间..."); await upload.uploadFileToOSS(file, presignedUrl.url, presignedUrl.headers); utils.printInfo("文件上传到安全空间成功,获取视频分析任务ID"); if (presignedUrl.url.indexOf("?") === -1) { throw new Error("预签名URL格式错误,请反馈给开发者"); } const url = presignedUrl.url.substring(0, presignedUrl.url.indexOf("?")); const task = await video.getVideoId(tokenValue, url); ``` ```js // scripts/utils/upload.js:11-37 async function uploadFileToOSS(filename, presignedUrl, headers) { const url = new URL(presignedUrl); if (url.protocol !== "https:") { throw new Error("上传URL必须是HTTPS协议"); } return new Promise((resolve, reject) => { const fileStats = fs.statSync(filename); const totalSize = fileStats.size; let uploadedSize = 0; let settled = false; const fileStream = fs.createReadStream(filename); // 复制预签名 headers,并补充 OSS PUT 必需字段 const uploadHeaders = Object.assign({}, headers); // 关键修复:明确 Content-Length,否则 Node 对流式 body 默认使用 chunked // Transfer-Encoding,阿里云 OSS 预签名 PUT 通常会拒绝而返回 400/403 uploadHeaders ...[truncated 2678 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict local inputs to explicitly approved directories or require interactive confirmation for paths outside a configured media directory. 2. Resolve paths with `realpath()` and reject symbolic links or paths escaping the approved root. 3. Validate expected extensions and MIME types, and inspect file signatures rather than trusting the filename. 4. Reject known sensitive paths and non-media content before requesting an upload URL. 5. Allowlist the exact object-storage domains and ports that may receive uploads. 6. Validate that presigned destinations use HTTPS, an approved hostname, and an expected path format. 7. Copy only a strict allowlist of required upload headers instead of forwarding all API-provided headers. 8. Avoid sending the full local path to the presign API; send only a sanitized basename or generated object name when possible. 9. Clearly disclose every network destination in the Skill documentation. 10. Run the Skill under a dedicated, least-privileged account with access only to intended media and temporary directories. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/utils/download.js:463
Finding
Unbounded Download and Upload Sizes Permit Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/utils/helper.js:29-39`; `scripts/utils/download.js:463-475, 526-570`; `scripts/utils/upload.js:17-24` **Vulnerability Type**: Uncontrolled resource consumption **Risk Level**: Medium ### Vulnerable Code ```js // scripts/utils/helper.js:29-39 const options = { retry: { maxRetries: constants.CREATE_MAX_ATTEMPTS, delay: constants.RETRY_INTERVAL, }, override: { skip: true, skipSmaller: true }, }; let progressLog = ""; const dl = new Downloader(url, path, options); ``` ```js // scripts/utils/download.js:463-475 if (!this.__isResumed) { this.__total = parseInt(response.headers["content-length"]) || null; this.__resetStats(); } if (this.__isResumed && response.statusCode === 200) { this.__isResumed = false; this.__total = parseInt(response.headers["content-length"]) || null; this.__resetStats(); } ``` ```js // scripts/utils/download.js:526-570 const _fileName = this.__getFileNameFromHeaders(response.headers); this.__filePath = this.__getFilePath(_fileName); this.__fileName = this.__filePath.split(path.sep).pop(); if (fs.existsSync(this.__filePath)) { const downloadedSize = this.__getFilesizeInBytes(this.__filePath); const totalSize = this.__total ? this.__total : 0; if ( typeof this.__opts.override === "object" && this.__opts.override.skip && (this.__opts.override.skipSmaller || downloadedSize >= totalSize) ) { this.emit("skip", { totalSize: this.__total, fileName: this.__fileName, filePath: this.__filePath, downloadedSize: downloadedSize, }); this.__setState(this.__states.SKIPPED); return resolve(true); } } this.__fileStream = fs.createWriteStream(this.__filePath, {}); // ... readable.on("data", (chunk) => this.__calculateStats(chunk.length)); this.__pipes.forEach((pipe) => { readable.pipe(pipe.stream, pipe.options); readable = pipe.stream; }); readable.pipe(this.__fileStream); ``` ```js // scripts/utils/up ...[truncated 2138 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a configurable maximum media size appropriate for the service. 2. Reject responses whose declared `Content-Length` exceeds that limit before creating the destination file. 3. Count actual streamed bytes and abort the request and file stream immediately when the limit is exceeded. 4. Enforce the same maximum for local files before requesting a presigned upload URL. 5. Add a total wall-clock deadline in addition to the socket inactivity timeout. 6. Limit transfer rate, concurrent executions, and aggregate temporary-directory usage. 7. Delete partial and completed temporary files in a `finally` block after upload or failure. 8. Use a per-run randomized temporary directory with restrictive permissions and enforce a storage quota. 9. Require an explicit opt-in or confirmation for unusually large media files to reduce unexpected processing charges. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
声明的核心能力是视频/音频内容处理与大模型文案生成,而代码实际只是一个独立的通用参数解析模块。它读取命令行参数、校验 flag、处理默认值和生成 help 文本,没有任何与视频链接、本地视频文件、音频提取、转写、总结、翻译或模型调用相关的逻辑。因此代码与描述的主要用途明显不一致,属于实质性能力错配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
声明描述的是“视频转文字和文案生成”能力,但给出的代码片段仅是底层下载工具模块 scripts/utils/download.js。它访问远程 URL,通过 http/https 请求将内容保存到本地文件系统,并提供下载状态管理。代码中没有任何与视频转写、字幕识别、语音转文字、LLM 总结、改写、翻译或内容生成相关的实现。因此该代码片段的实际行为与声明用途存在明显不匹配。虽然下载视频可能是相关系统中的辅助步骤,但就此代码片段本身而言,其主要功能是通用下载,而非声明中的核心技能。

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
声明描述的是一个完整的视频转写与文案生成技能,但提供的代码片段只是一个工具函数:读取当前项目的 package.json 并返回其中的 name 字段,同时做了简单缓存。该行为属于元数据读取,和视频处理或文本生成没有直接关系。按评估标准,这不是支持性实现细节可直接证明核心功能的代码,而是与声明主用途明显不一致,因此应判定为描述与实际代码行为不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
声明描述的是一个视频转文字与内容生成技能,但提供的代码片段只是一个工具模块,用于验证 GUAIKEI_API_TOKEN 是否满足长度和字符要求,并在失败时暂停技能并输出获取 token 的广告与客服联系方式。这与声明的用户功能没有直接对应关系。虽然 token 校验可能属于配套基础设施,但该代码片段本身并未实现或体现任何所宣称的核心行为,因此就“描述是否准确代表该代码实际作用”而言,存在明显不匹配。

Vague Triggers

Medium
Confidence
95% confidence
Finding
The skill defines broad natural-language conversion rules that can trigger on generic user requests involving video links, local file paths, summaries, rewrites, or analysis. Without explicit activation boundaries, a host agent may invoke this skill on ambiguous everyday language and send user-provided files/URLs and prompts to a third-party cloud service, creating unintended data exposure and incorrect tool execution.

Whitespace Padding

Medium
Category
Prompt Injection
Content
4. 同时传入文件路径与任务ID,优先执行 `--id`,忽略 `--file`
5. 无自定义 prompt 时,默认完整转录视频全部文字

| 用户自然语言指令                                         | 自动生成命令                                                                                                |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| 视频提取 https://example.com/video.mp4 中的文字          | `node scripts/video2text/index.js --file "https://example.com/video.mp4"`                                   |
| 把本地 /path/to/your/video.mp4 改成小红书风格的文案      | `node scripts/video2text/index.js --file "/path/to/your/video.mp4" --prompt "改写成小红书风格的文案"`       |
Confidence
70% confidence
Finding
Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The example trigger phrases are open-ended and overlap with normal conversation, but the README does not specify when the skill should stay inactive. In this skill’s context, mistaken activation is more dangerous because it may cause local video paths, public links, or previously processed task IDs to be sent for cloud processing, with privacy, cost, and consent implications.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger description is very broad and overlaps with common requests like summarization, transcription, rewriting, translation, and content extraction. In an agent ecosystem, that can cause the skill to be invoked on unrelated user content, increasing the chance of sending sensitive local files or URLs to a third-party cloud service without sufficiently specific user intent.

Whitespace Padding

Medium
Category
Prompt Injection
Content
## 6. 🧠 自然语言 → 命令 映射(AI 执行参考)

| 用户自然语言指令                                     | 生成的命令                                                                                                  |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| 提取 https://example.com/video.mp4 里的文字          | `node scripts/video2text/index.js --file "https://example.com/video.mp4"`                                   |
| 总结这个视频的核心观点 https://example.com/video.mp4 | `node scripts/video2text/index.js --file "https://example.com/video.mp4" --prompt "总结这个视频的核心观点"` |
Confidence
70% confidence
Finding
Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Natural-Language Policy Violations

Medium
Confidence
86% confidence
Finding
This JavaScript file contains natural-language comments, parameter descriptions, and operation labels exclusively in Chinese, including strings likely surfaced to users such as error messages and request labels. Under the policy, forcing a specific language without user opt-in or documented locale justification is a language-policy violation.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This JavaScript code initiates HTTP/HTTPS requests and persists remote content to the local filesystem via createWriteStream, and it can also delete partially downloaded files on stop or failure. While the behavior is core to a downloader, this file contains no user-facing prompt, print/log disclosure, or inline warning text explaining these data-transfer and file-modification effects.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Multiple error strings are hard-coded in Chinese, including operational and credential-related messages, with no indication that the user can choose a language or locale. This can violate language/locale policy when a skill forces one language without user opt-in.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The skill emits all user-facing status and warning messages in Chinese string literals, including the invalid-token warning and promotional guidance. There is no indication that the skill is China-specific or that users can opt into a preferred language, which creates a natural-language locale policy issue.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script uploads a user-supplied local file or a downloaded remote video to a third-party service to obtain transcription/analysis, but the code does not present an explicit consent or privacy warning at the point of transmission. This creates a real data-exposure risk because users may supply sensitive or private videos and reasonably not realize the content is being transferred off-device.

Natural-Language Policy Violations

Low
Confidence
75% confidence
Finding
L061 要求用户配置名为 `GUAIKEI_API_TOKEN` 的环境变量,属于强制特定语言/品牌前缀的命名约定,但文档未说明是否有可替代的本地化或通用配置方式。按给定规则,强制单一语言/locale 且无用户选择时可构成自然语言层面的组织政策问题。

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
This utility's docstrings, error messages, and generated help output are written entirely in Chinese, including user-visible strings such as parameter errors and usage text. Because the file provides no option for language selection and no documented justification for a Chinese-only locale, it creates a natural-language policy concern under the language/locale rule.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
This code contains natural-language comments and user-visible log/error strings entirely in Chinese, including operational notices and cleanup failure messages. The file does not indicate that the skill is region-specific or that users can opt into this locale, which may violate language/locale policy for general-purpose skills.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
This code contains multiple user-facing messages in Chinese, including progress, retry, resume, and error output, with no indication that the user can choose a language or locale. That can violate language/locale policy when the skill is used in broader contexts where users have not opted into Chinese output.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This code sends arbitrary request data and an authentication token over HTTPS via `postJson`, but there is no confirmation prompt, user-facing disclosure, or warning in the file about transmitting that information. Because network transmission of user or system data is safety-relevant, the lack of any visible disclosure qualifies under the code-file warning criterion.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The user-facing banner is hardcoded in Chinese as '视频文案智能提取助手', which imposes a specific language/locale in the interface. There is no indication in this file that users can opt into another language or that the locale restriction is required for a region-specific purpose.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The CLI help text, flag descriptions, and examples are presented only in Chinese, which imposes a specific language on users without visible opt-in or alternative locale support. This is a natural-language policy concern because the skill does not appear to offer language selection or document a justified locale restriction.

Static analysis

No suspicious patterns detected.