Back to skill

Security audit

Shuttle AI Chatbot

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Shuttle AI query tool, but crafted queries or settings could make it run unintended local shell commands.

Install only if you trust the Shuttle AI endpoint and will avoid sensitive prompts unless that backend is approved for them. Do not pass untrusted query files, query text, --url values, or --lang values to this version; the CLI should be fixed to use fetch/http.request or execFile/spawn with argument arrays plus URL and language validation before routine use.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
index.js:47
Finding
OS Command Injection Through User-Controlled CLI Inputs<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 47–57 **Vulnerability Type**: OS command injection through unsafe shell-command construction **Risk Level**: High ### Vulnerable Code ```js // 構建 curl 命令 const curlCmd = `curl -s -X POST ${options.url}/chat_direct \\\n` + ` -H "Content-Type: application/json" \\\n` + ` -d '{"question":${JSON.stringify(query)},"session_id":"${sessionId}","lang":"${options.lang || 'zh'}"}'`; console.log(`📡 發送查詢到 ${options.url}/chat_direct`); console.log(`🆔 Session ID: ${sessionId}`); // 執行 curl const { stdout } = await execPromise(curlCmd); ``` ### Technical Analysis The implementation constructs a shell command by interpolating user-controlled values and passes the resulting string to `child_process.exec` through `execPromise()`. The following values are attacker-controllable: - `options.url`, supplied through `--url` - `query`, supplied as the positional query argument or read from a batch file - `options.lang`, supplied through `--lang` `options.url` is inserted without quoting or validation, allowing shell metacharacters such as semicolons, command substitutions, redirections, and pipes to change the command executed by the shell. Although `query` is passed through `JSON.stringify()`, that function only produces JSON-safe syntax. It does not provide shell escaping. The generated JSON is enclosed in a shell single-quoted argument, but an apostrophe contained in a query remains present after `JSON.stringify()` and can terminate that shell quote. The remainder can then be interpreted as shell commands. The `lang` value is also interpolated without validation against the documented `zh` and `en` choices. It is inside the same shell-quoted JSON argument and can similarly participate in quote termination. Using `exec()` is the underlying dangerous sink because it invokes a command shell rather than passing arguments directly to `curl`. ### Attack Path #### Injecti ...[truncated 2232 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Do not invoke a command shell.** Replace `child_process.exec()` with Node.js `fetch()` or `http.request()` so no shell command is constructed. ```js async function callApi(query, options, sessionId) { const baseUrl = validateServiceUrl(options.url); const endpoint = new URL('/chat_direct', baseUrl); const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question: query, session_id: sessionId, lang: options.lang }), signal: AbortSignal.timeout(30_000) }); if (!response.ok) { throw new Error(`API returned HTTP ${response.status}`); } return response.json(); } ``` 2. **If `curl` must be retained, use `execFile()` or `spawn()` with an argument array.** Do not concatenate values into a command string. ```js const payload = JSON.stringify({ question: query, session_id: sessionId, lang: options.lang }); const { stdout } = await execFilePromise('curl', [ '-sS', '-X', 'POST', `${validatedUrl}/chat_direct`, '-H', 'Content-Type: application/json', '--data-binary', payload ]); ``` 3. **Validate the language option explicitly.** ```js if (!['zh', 'en'].includes(options.lang)) { throw new Error('Invalid language; expected zh or en'); } ``` 4. **Parse and validate the destination URL.** The documentation describes the endpoint as local-only, but the implementation accepts unrestricted destinations. Use `new URL()` and permit only the intended protocol, port, and local/private hosts. Reject embedded credentials, unexpected schemes, malformed ports, and public destinations if they are outside the skill's intended behavior. 5. **Apply an effective timeout.** The declared `--timeout` option is currently unused. Enforce a bounded request timeout to prevent indefinite hangs. 6. **Add regression tests.** Include queries, language values, and URLs containing apostrophes, semicolo ...[truncated 224 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (12)

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README explicitly encourages direct calls to a local AI service and provides a default internal-looking URL, but it does not warn users that their prompts and batch query contents will be transmitted to that service. This can lead to inadvertent disclosure of sensitive product, customer, or internal business information, especially because the batch mode makes bulk transmission easy and the target is a non-loopback network host rather than a clearly isolated local process.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill explicitly sends user queries and a session identifier to an HTTP service, but the documentation does not clearly warn users that their inputs and tracking metadata are transmitted and may be logged by the backend. Because the endpoint uses plain HTTP and a fixed local-network address by default, sensitive queries could be exposed to interception or unintended retention on the network or service side.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The documentation states that URL validation only allows localhost or private IPs, but the same skill usage examples and parameter description allow user-supplied service URLs without clearly enforcing that restriction. This inconsistency can mislead users and integrators into assuming outbound requests are constrained when they may not be, increasing the risk of SSRF-like access to unintended internal or external services.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The module comment says it "directly" calls the backend API and removed browser automation for stability, which implies an in-process API integration. In reality, the code invokes `curl` through `child_process.exec`, so it still relies on external process execution rather than a direct HTTP client call.

External Transmission

Medium
Category
Data Exfiltration
Content
* 
 * 优化:
 * - 移除浏览器自动化(不稳定)
 * 直接使用 curl 调用本地 AI 服务
 * - session_id 格式:shuttle-cli-YYYYMMDD
 * - 更快速、更稳定
 */
Confidence
87% confidence
Finding
The skill transmits user queries to an external or separate backend service, which is a real data exposure surface because prompts and returned content leave the local process boundary. This is more significant here because the default endpoint is a hardcoded private-network host, so users may unknowingly send internal or sensitive content to a service they did not explicitly configure.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill sends user-supplied queries to a backend service over the network without an explicit warning or consent prompt, which can expose sensitive prompts, secrets, or proprietary data to another system. In a CLI skill context, users may assume local processing unless clearly told otherwise, making inadvertent data disclosure more likely.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The request payload hard-codes the default language to 'zh' when no language is specified, which forces a specific locale by default. While a --lang option exists elsewhere, users who do not discover or set it will still be subject to a non-neutral language choice without explicit opt-in.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The manifest sets the `lang` option default to `zh`, which imposes a specific language by default rather than requiring the user to choose. The policy allows language constraints only when the user is given a choice or the constraint is clearly documented and justified; here a choice exists, but the default still forces Chinese unless the user overrides it.

Natural-Language Policy Violations

Low
Confidence
86% confidence
Finding
The parameter table specifies `--lang` supports `zh` or `en`, but sets the default to `zh`. This imposes a specific language by default rather than prompting for or preserving user preference, which can violate language/locale choice expectations.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
The parameter table sets `--lang` default to `zh`, which forces a specific response language unless the user overrides it. Although English is supported, the default behavior imposes a locale choice rather than prompting or adapting based on user preference.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The package description includes Chinese-language instructions and behavior notes, but there is no indication that language is configurable or that the skill is intentionally limited to a Chinese-speaking audience. This can violate language/locale policy expectations when a skill implicitly enforces a language without user opt-in.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"author": "Shuttle AI",
  "license": "MIT",
  "dependencies": {
    "commander": "^14.0.3"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

Detected: suspicious.install_untrusted_source

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
skill.json:42