Back to skill

Security audit

Guaikei Video Transcript Extractor

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches its video transcription purpose, but it can fetch arbitrary web URLs and upload the result to a cloud service despite under-disclosing that risk.

Review before installing. Use this only for trusted public videos or local media you intend to send to GuaiKei and its storage provider. Do not provide localhost, private-network, cloud metadata, or sensitive internal URLs, and treat task IDs as sensitive. Do not rely solely on the artifact's automatic-deletion claims without independent assurance from the service provider.

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 Can Exfiltrate Internal Resources Through Automatic Cloud Upload<![CDATA[ ## Vulnerability Details **File Location**: `scripts/utils/validator.js:3-9`, `scripts/video2text/index.js:97-107`, `scripts/video2text/index.js:140-149`, and `scripts/utils/download.js:478-493` **Vulnerability Type**: Server-Side Request Forgery and unintended data disclosure **Risk Level**: High ### Vulnerable Code ```js function isUrl(url) { try { const parsedUrl = new URL(url); return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:"; } catch (_) { return false; } } ``` ```js if (validator.isUrl(file)) { const filepath = utils.downloadPath(); try { await fs.promises.mkdir(filepath, { recursive: true }); } catch (error) { utils.printError("Temporary download directory creation failed: " + (error.message || String(error))); process.exit(1); } try { const downloadResult = await helper.download(file, filepath); ``` ```js const presignedUrl = await video.getPresignedUrl(tokenValue, file); if (!presignedUrl || !presignedUrl?.url || presignedUrl.url === "") { throw new Error("Failed to obtain a presigned URL"); } utils.printInfo("Uploading file..."); await upload.uploadFileToOSS(file, presignedUrl.url, presignedUrl.headers); utils.printInfo("File upload completed"); ``` ```js 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(); } ``` ### Technical Analysis The URL validator only verifies that the supplied value uses the HTTP or HTTPS scheme. It does not reject loopba ...[truncated 2296 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve destination hostnames before connecting and reject: - IPv4 and IPv6 loopback addresses. - RFC1918 private networks. - Link-local and unique-local ranges. - Multicast and unspecified addresses. - Known cloud metadata destinations. 2. Apply the same validation after every redirect, not only to the initial URL. 3. Protect against DNS rebinding by connecting only to the validated resolved address and verifying the address again when connections are established. 4. Permit HTTPS only unless HTTP is explicitly required and approved. 5. Reject redirects that downgrade from HTTPS to HTTP. 6. Introduce an allowlist of supported media domains where operationally possible. 7. Verify response MIME types and media file signatures before upload. 8. Require explicit confirmation before uploading content fetched from an untrusted or non-public destination. 9. Restrict presigned upload destinations to documented storage hostnames or hostname suffixes, and allow only the minimum required upload headers. 10. Consider network-level egress controls that prevent the process from reaching metadata and private-network ranges. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/utils/helper.js:27
Finding
Unbounded Remote Downloads Permit Disk, Bandwidth, and Execution-Time Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/utils/helper.js:27-39` and `scripts/utils/download.js:526-549` **Vulnerability Type**: Uncontrolled resource consumption **Risk Level**: Medium ### Vulnerable Code ```js async function download(url, path) { return new Promise((resolve, reject) => { 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 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, {}); ``` ```js 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); ``` ### Technical Analysis The downloader does not define a maximum accepted `Content-Length`, a maximum number of streamed bytes, or a maximum media size. It writes the response directly to disk until the source closes the stream. A remote server can omit `Content-Length`, provide a dishonest value, or send a very large or effectively endless response. The implem ...[truncated 1518 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a conservative, configurable maximum media size. 2. Reject responses whose declared `Content-Length` exceeds that limit. 3. Independently count streamed bytes and immediately destroy the request and output stream when the limit is crossed. 4. Apply limits to both initial responses and redirected responses. 5. Establish an overall download deadline in addition to socket inactivity timeouts. 6. Limit the minimum acceptable transfer rate to mitigate endless slow responses. 7. Delete partial files on timeout, cancellation, stream error, size-limit violation, and process failure. 8. Validate the downloaded file's MIME type and magic bytes before upload. 9. Apply filesystem quotas or place temporary files on a size-limited dedicated volume. 10. Avoid retrying failures caused by size limits, invalid content, and other permanent validation errors. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/utils/utils.js:36
Finding
Reusable Task Identifier Is Persisted in a Predictable File Without Explicitly Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/utils/utils.js:36-54` **Vulnerability Type**: Insecure storage of reusable task state **Risk Level**: Low ### Vulnerable Code ```js function lastTaskPath() { return path.join(downloadPath(), ".last_task_id"); } function saveLastTask(id) { try { fs.mkdirSync(downloadPath(), { recursive: true }); fs.writeFileSync(lastTaskPath(), String(id), "utf-8"); } catch (_) { /* Non-critical path; ignore failure */ } } function loadLastTask() { try { if (fs.existsSync(lastTaskPath())) { return fs.readFileSync(lastTaskPath(), "utf-8").trim(); } } catch (_) { /* Ignore read failure */ } return ""; } ``` ### Technical Analysis The most recent task identifier is stored at a predictable project-local path, `tmp/.last_task_id`. Neither the temporary directory nor the file is created with an explicit restrictive mode. Effective access therefore depends on the process umask and permissions of the surrounding project directory. The identifier is reusable through the `--id last` workflow and references previously processed content. Although the API token is still required by the client, another local user or process that can read the task identifier and access the same runtime token may request the associated transcript. The code also does not validate file ownership or reject symbolic links before reading or writing this state file. In a writable shared project directory, this can create state-integrity concerns. ### Attack Path 1. The Skill successfully creates a video-analysis task. 2. The task ID is written to the predictable `tmp/.last_task_id` path using default filesystem permissions. 3. Another local user or process with access to the project directory reads or replaces the file. 4. If the attacker can invoke the Skill in an environment containing the account token, they use `--id last` or the recovered ID to request prior transcript content. 5. Replacing the ...[truncated 674 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store state in a per-user application-data directory rather than inside the project tree. 2. Create the state directory with mode `0700`. 3. Create or replace the task-state file with mode `0600`. 4. Use atomic writes through a newly created temporary file followed by a controlled rename. 5. Reject symbolic links and verify that the file and parent directory are owned by the expected user. 6. Validate loaded task IDs against the expected identifier format and maximum length. 7. Provide an option to disable historical task persistence. 8. Delete the stored identifier when it expires or when the user requests cleanup. 9. Avoid silently ignoring permission and integrity errors; report them without exposing sensitive values. ]]>
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 (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
代码片段仅包含 args.js 工具函数:readValueAfterFlag、parseArgs 和 buildHelp,用于命令行参数读取、校验、帮助信息生成。这属于底层 CLI 支持逻辑,而声明描述的是一个视频转写与内容加工技能。依据评估标准,若代码主行为与描述的核心能力 materially different,则应判定为不匹配。当前片段没有任何视频读取、音视频转录、网络请求、云端解析、文本生成或内容分析相关实现,因此与声明用途明显不符。

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
声明描述的核心能力是“视频内容转文字并进一步生成总结/文案”等内容理解与文本处理能力;而这段代码的实际功能只是底层网络下载组件。它访问远程 URL、将响应内容保存到本地、支持重试和续传,但没有任何音视频解析、ASR 转写、字幕提取、摘要生成、翻译或内容分析实现。因此其主要目的与声明明显不一致。虽然下载远程视频文件可能是整条技能链路中的辅助步骤,但仅凭该代码块来看,实际行为是通用文件下载而非视频转写加工,应判定为描述与行为不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose centers on media ingestion and text generation from video content. The actual code chunk is a small utility that loads package.json from disk and returns the package name. This behavior is materially unrelated to the declared functionality and does not implement any of the advertised capabilities. While this could be a supporting helper within a larger project, based on the supplied chunk alone, its behavior does not accurately represent the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
声明描述的是一个视频内容转写与文案加工技能,但该代码片段的实际功能只是验证一个 API token 是否符合格式要求,并在无效时打印暂停提示和获取私有 token 的广告信息。这不属于声明中的核心能力,也没有体现对视频文件、平台链接、云端解析、转写、总结或文案生成的任何实现。虽然 token 校验可被视为某些技能的辅助组件,但就当前提供的代码片段而言,其行为与声明目的明显不一致,且主要表现为鉴权/营销提示而非视频处理,因此应判定为描述与行为不匹配。

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.

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
91% confidence
Finding
The trigger description is extremely broad and covers generic content-analysis intents, increasing the chance the agent invokes this skill for unrelated user requests. Because the skill can upload local files or fetch remote video links to a third-party service, overbroad auto-invocation can cause unnecessary data disclosure and unintended external processing.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The 'when to use' section instructs priority invocation for many broad intents without sufficient guardrails. In a skill that sends user-supplied media to an external service and supports reuse via historical task IDs, this can lead to unintended third-party data transfer or the wrong tool being selected for sensitive content-processing tasks.

Whitespace Padding

Medium
Category
Prompt Injection
Content
当用户用自然语言下达指令时,按以下映射生成命令,保证识别与执行一致:

| 用户自然语言指令                                     | 生成的命令                                                                                                  |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| 提取 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.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The package metadata advertises very broad capabilities and includes an extensive set of generic content-creation and transcription keywords, which can cause the skill to activate for a wide range of loosely related user requests. In an agent ecosystem, overbroad triggering increases the chance of unintended invocation, exposing user files, URLs, or prompts to this skill when a narrower tool would have been more appropriate.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This JavaScript file contains natural-language comments, error messages, and help output entirely in Chinese, including user-facing strings such as parameter errors and usage text. Under the policy, forcing a specific language without user opt-in or a documented locale-specific justification is a natural-language policy violation.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This code performs network requests to arbitrary URLs and writes the response to disk, and related methods can delete partially downloaded files on stop or failure. While the module emits internal events, there is no confirmation prompt, print/log statement, or inline comment/docstring warning users that data will be fetched and files may be created or removed locally.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This JavaScript file contains natural-language comments and runtime status/error messages exclusively in Chinese, such as the download docstring and progress/failure notices. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is explicitly justified, which is not present here.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The code emits user-visible error messages exclusively in Chinese, including parsing, timeout, and token failure messages. That imposes a specific language on users without any visible opt-in, language selection, or justification for a locale-specific constraint.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code emits all user-facing warning and status text exclusively in Chinese, including the error path and recovery instructions. That creates a language/locale policy issue because the skill does not offer a language choice or document that it is intentionally limited to Chinese-speaking users.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The comment explicitly promises that uploaded videos will be automatically deleted after transcription, but this function only performs an upload and contains no deletion logic, retention control, or verification that downstream cleanup occurs. In a skill handling potentially sensitive user videos, this creates a privacy and data-retention risk because users may rely on the stated guarantee while their content could remain stored indefinitely or longer than expected.

Natural-Language Policy Violations

Low
Confidence
76% confidence
Finding
The user-facing instructions, examples, and operational guidance are entirely in Chinese, and the skill does not state that Chinese is optional or that other languages are supported for interaction. Under the language/locale policy, forcing a specific language without user opt-in can be a natural-language policy issue unless the locale restriction is documented and justified.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
This JavaScript file contains natural-language strings exclusively in Chinese, including user-visible error messages such as "token 必须是非空字符串" and operation labels like "预上传". Under the policy rules, forcing a specific language without user opt-in can be a locale-policy violation when no justification or language-selection mechanism is present.

Missing User Warnings

Low
Confidence
88% confidence
Finding
This code constructs and sends an HTTPS POST request with a TOKEN header and JSON body, which may transmit user or system data to a remote service. Within this file, there is no confirmation prompt or user-facing notice describing that network transmission, only internal error handling and retry logging.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The banner text is hard-coded in Chinese, which indicates the skill presents itself in a specific language regardless of user preference. The policy allows locale constraints only when users are given a choice or the restriction is clearly documented and justified, neither of which is evident in this file.

Static analysis

No suspicious patterns detected.