Back to skill

Security audit

AigoHotel MCP

Security checks for vulnerabilities and agentic risk

Overview

This hotel-booking skill has a coherent purpose, but it installs and runs mutable external software while handling real bookings and personal data, so it needs review before installation.

Install only if you trust the RollingGo npm package and GitHub release channel. Avoid running the installer with administrator privileges, review any update before applying it, and be aware that the skill can submit real hotel orders and expose booking history, names, email addresses, and payment links through its CLI workflow.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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 (3)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/install.py:129
Finding
Mutable Remote Executable Downloaded and Executed Without Integrity Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install.py:129-167` **Vulnerability Type**: Unverified remote executable retrieval and execution **Risk Level**: Critical ### Vulnerable Code ```python # Try fetching from GitHub API assets, tag = get_latest_release_assets() download_url = None if assets: for asset in assets: name = asset.get('name', '').lower() # Match keywords (e.g. 'win' or 'windows' for Windows, 'macos' for Mac, 'linux' for Linux) if asset_keyword in name: download_url = asset.get('browser_download_url') print(f"Found matching asset for version {tag}: {asset.get('name')}") break # Fallback to hardcoded URL patterns if API fails or asset not found if not download_url: print("Using hardcoded fallback download URL...") if system == "windows": download_url = "https://github.com/RollingGo-AI/oauth-hotel-cli/releases/latest/download/rgh-win.exe" elif system == "darwin": download_url = "https://github.com/RollingGo-AI/oauth-hotel-cli/releases/latest/download/rgh-macos" else: download_url = "https://github.com/RollingGo-AI/oauth-hotel-cli/releases/latest/download/rgh-linux" success = download_binary(download_url, dest_path) if not success and assets and system == "windows": # Extra fallback for windows naming differences (win vs windows) print("Retrying with alternative Windows asset name...") download_url = "https://github.com/RollingGo-AI/oauth-hotel-cli/releases/latest/download/rgh-windows.exe" success = download_binary(download_url, dest_path) if success: # Chmod on Linux/macOS if system != "windows": try: os.chmod(dest_path, 0o755) print("Permissions set to executable.") ``` The download function writes the HTTP response directly to the destination: ```python def download_binary(url, dest_path): """Download a file from url to dest_path with progress indication. ...[truncated 3378 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin the installer to an exact, reviewed release version instead of using `latest`. 2. Maintain an in-repository allowlist containing the exact expected filename, platform, architecture, version, and SHA-256 digest for every supported binary. 3. Verify the digest before granting executable permission or replacing an existing installation. 4. Prefer a signed release mechanism, such as Sigstore provenance or a detached signature verified against a pinned public key. 5. Match assets by exact filename and architecture; do not use substring matching. 6. Download to a newly created temporary file, validate it, set restrictive permissions, and atomically rename it into `bin` only after all checks succeed. 7. Apply explicit download-size and timeout limits. 8. Validate redirects and reject final download URLs outside an explicit trusted-host allowlist. 9. Preserve a previously verified binary if an update fails validation. 10. Remove the instruction requiring immediate automatic upgrades. Updates should require an independently verified release and explicit operator approval. ]]>

T08 · Insecure Dependencies

Error
Location
scripts/install.py:20
Finding
Unpinned Package Installed Globally Through npm<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install.py:20-32` **Vulnerability Type**: Unsafe, mutable global dependency installation **Risk Level**: High ### Vulnerable Code ```python def install_via_npm(): """Attempt to install @rollinggo/hotel via npm.""" print("Checking npm environment...") npm_cmd = "npm.cmd" if platform.system() == "Windows" else "npm" # Try global installation print("Attempting to install @rollinggo/hotel globally via npm...") code, stdout, stderr = run_command([npm_cmd, "install", "-g", "@rollinggo/hotel@latest"]) if code == 0: print("✅ Successfully installed @rollinggo/hotel globally via npm!") return True print("⚠️ npm global installation failed (might need administrator/sudo permissions).") print(stderr) return False ``` The same mutable installation method is declared in `SKILL.md:14-19`: ```json { "id": "node", "kind": "node", "package": "@rollinggo/hotel@latest", "bins": ["rgh"], "label": "Install @rollinggo/hotel (npm)" } ``` ### Technical Analysis The installer executes `npm install -g @rollinggo/hotel@latest`. The `latest` tag is mutable and can resolve to package content that did not exist when the Skill was reviewed. npm packages may execute lifecycle scripts during installation, so installing a compromised future release can cause arbitrary code execution before the CLI is ever invoked. The `-g` option modifies the user's or system's global Node.js installation rather than confining the dependency to the Skill directory. This exceeds least privilege because the declared hotel workflow can use a Skill-local, version-pinned dependency. The warning that global installation may need administrator or sudo permissions can also encourage installation under an elevated account, magnifying the impact of malicious lifecycle scripts. ### Attack Path 1. An attacker compromises the npm publisher account, organization, package release process, ...[truncated 1201 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `@rollinggo/hotel@latest` with an exact, audited version. 2. Commit a lockfile containing registry-resolved integrity values. 3. Install the package inside the Skill directory rather than using `-g`. 4. Execute the local package binary through an explicit path, avoiding dependence on mutable global PATH state. 5. Avoid administrator or sudo installation instructions. 6. Disable lifecycle scripts with `--ignore-scripts` unless they are strictly required and separately audited. 7. If lifecycle scripts are required, document them and verify the package archive integrity before installation. 8. Use an automated dependency-update process that produces a reviewable version and integrity change rather than resolving `latest` during installation. 9. Consider distributing the CLI as part of a signed, reproducible release whose provenance can be verified independently. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/rgh.js:22
Finding
Windows Shell Invocation Permits Command Injection Through Forwarded Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rgh.js:22-50` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```javascript function resolveExecutable() { if (fs.existsSync(skillBinDir)) { const localExeName = isWin ? 'rgh.exe' : 'rgh'; const localPath = path.join(skillBinDir, localExeName); if (fs.existsSync(localPath)) { return { cmd: localPath, shell: false }; } } if (isWin) { return { cmd: 'rgh.cmd', shell: true, fallbackCmd: 'rgh' }; } else { return { cmd: 'rgh', shell: false }; } } // 3. 对带空格的参数进行安全的引名包裹(解决 Windows cmd.exe 字符串拆分边界问题) function sanitizeArgs(args) { return args.map((arg) => { if (typeof arg === 'string' && arg.includes(' ') && !arg.startsWith('"')) { return `"${arg}"`; } return arg; }); } const { cmd, shell, fallbackCmd } = resolveExecutable(); function runChild(targetCmd, useShell) { const args = useShell ? sanitizeArgs(process.argv.slice(2)) : process.argv.slice(2); const child = spawn(targetCmd, args, { stdio: 'inherit', env: process.env, shell: useShell, }); ``` A corresponding shell-enabled execution path exists in `scripts/rgh.py:45-49`: ```python def main(): args = [target_cmd] + sys.argv[1:] try: use_shell = is_win and target_cmd.endswith(".cmd") res = subprocess.run(args, shell=use_shell) ``` ### Technical Analysis On Windows, both wrappers use a command shell when the resolved executable is a `.cmd` launcher. Arguments are derived directly from `process.argv` or `sys.argv`, and the Skill workflow uses user-derived values such as locations, original search queries, hotel names, email addresses, and guest data as CLI parameters. The JavaScript `sanitizeArgs` function only adds quotes when an argument contains a space. It does not safely escape Windows command-shell metacharacters such as `&`, `|`, `<`, `>`, `^`, `%`, `!`, parentheses, or embedded quotation marks. Arg ...[truncated 1910 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not invoke `rgh.cmd` through `cmd.exe`; resolve and execute a native `rgh.exe` directly with shell execution disabled. 2. Keep `shell: false` in Node.js and `shell=False` in Python for every user-influenced invocation. 3. Resolve the executable to a trusted absolute path rather than relying on shell or PATH interpretation. 4. Remove the custom quoting function; argument arrays with shell execution disabled preserve argument boundaries without shell escaping. 5. Validate subcommands against an explicit allowlist such as `whoami`, `login`, `hotel-tags`, `search-hotels`, `hotel-detail`, `price-confirm`, `book`, `orders`, and `order-detail`. 6. Validate structured parameters by type and format before invocation: - Dates must match the documented date format. - Numeric identifiers and counts must contain only valid digits. - Email addresses must meet a constrained email format. - Enumerated values must match documented options. 7. Reject command-shell control characters in free-form fields as defense in depth. 8. Add Windows-specific automated tests containing `&`, `|`, `^`, `%`, `!`, quotes, and redirection characters to verify that inputs remain literal. 9. If a `.cmd` launcher cannot be eliminated, replace it with a small native executable or invoke the underlying Node.js entry point directly without a shell. ]]>
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The manifest presents the skill as a hotel search/booking assistant, but the instructions also require installing software, downloading binaries, modifying the local filesystem, and changing execution flow based on tool versioning. This mismatch increases the chance that users or orchestrators grant trust appropriate for a booking skill while the skill performs privileged system-management actions.

Ae1

High
Category
analysis-evasion
Content
> - **每日检查**:若当前目录下的 `.version_check` 不存在或记录日期非今天,静默请求远端 `SKILL.md`(地址拼接规则:将元数据中的 `{repository}` 域名替换为 `raw.githubusercontent.com`,并在末尾追加 `/main/skills/{name}/S
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill declares no explicit tool scope while instructing use of shell commands, network access, environment handling, file writes, and local installation flows. This weakens least-privilege boundaries and can let an agent invoke broader capabilities than users would reasonably expect from a hotel-booking skill.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The description-level trigger list includes generic phrases like lodging-related terms that may appear in normal conversation without clear intent to use this skill. Because the skill can progress into network operations and commerce-related steps, ambiguous activation raises the risk of unintended actions and unnecessary exposure of account-linked flows.

Natural-Language Policy Violations

Medium
Confidence
86% confidence
Finding
The file is written entirely in Chinese and prescribes fixed Chinese response phrases and templates, but it does not state that the skill is China-only or that users may choose another language. This can violate language/locale policy when used in broader environments because it imposes a language without opt-in.

Rp1

Medium
Category
MCP Rug Pull
Confidence
89% confidence
Finding
The update instruction uses an unpinned 'npx skills' invocation, which can fetch and execute whatever package version is current at runtime. That creates a software supply-chain risk: a compromised upstream package or unexpected breaking release could run arbitrary code in the agent environment.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger scope is broad enough to activate on ordinary travel or lodging conversation, increasing the chance the skill runs when the user did not intend to invoke a hotel-booking workflow. In this skill, accidental activation is more dangerous because invocation can lead to login prompts, network calls, software installation, and eventually real purchase flows.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This markdown section documents `book` as accepting email and guest identity details, which are user-sensitive data used to create a formal order. The description does not include any warning that this information will be transmitted to an external booking service or used to place a real reservation.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This section states that the command returns all historical hotel orders and includes personally identifiable information such as contact names and email addresses. The documentation lacks any warning that invoking this command reveals sensitive booking history and personal data.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_command(args):
    """Run a system command and return exit code and output."""
    try:
        result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        return result.returncode, result.stdout, result.stderr
    except Exception as e:
        return -1, "", str(e)
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("✅ Successfully installed @rollinggo/hotel globally via npm!")
        return True
    
    print("⚠️ npm global installation failed (might need administrator/sudo permissions).")
    print(stderr)
    return False
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_latest_release_assets():
    """Query GitHub API for the latest release assets."""
    api_url = "https://api.github.com/repos/RollingGo-AI/oauth-hotel-cli/releases/latest"
    req = urllib.request.Request(
        api_url,
        headers={'User-Agent': 'RollingGo-Installer/1.0'}
Confidence
89% confidence
Finding
The installer retrieves release metadata from GitHub and then downloads an executable for local use, but it performs no cryptographic signature or checksum verification on the fetched artifact. If the release channel, repository, network trust boundary, or upstream account is compromised, the script could install and later execute a malicious binary, which is especially dangerous in an installer context.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Check if node and npm are available
    has_node = False
    try:
        node_code = subprocess.call(["node", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        npm_code = subprocess.call(["npm" if platform.system() != "Windows" else "npm.cmd", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        has_node = (node_code == 0 and npm_code == 0)
    except Exception:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
has_node = False
    try:
        node_code = subprocess.call(["node", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        npm_code = subprocess.call(["npm" if platform.system() != "Windows" else "npm.cmd", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        has_node = (node_code == 0 and npm_code == 0)
    except Exception:
        has_node = False
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The manifest describes a hotel search and booking assistant that calls RollingGo hotel service APIs, but this file implements generic subprocess execution by spawning an external `rgh` program and forwarding all user-provided CLI arguments to it. Launching local executables is a stronger capability than direct API access and is not explicitly justified by the manifest text.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The wrapper forwards arbitrary command-line arguments directly to an external command and, on Windows fallback, may invoke it through a shell. Although the skill appears intended to call its own hotel CLI, there is no validation, allowlist, or user disclosure about what arguments are accepted, which increases the risk of abuse, unexpected side effects, or shell-mediated argument misinterpretation in hostile execution environments.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
args = [target_cmd] + sys.argv[1:]
    try:
        use_shell = is_win and target_cmd.endswith(".cmd")
        res = subprocess.run(args, shell=use_shell)
        sys.exit(res.returncode)
    except KeyboardInterrupt:
        sys.exit(130)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Low
Confidence
94% confidence
Finding
The skill instructs the agent to perform a daily remote fetch of SKILL.md and write a local marker file regardless of immediate booking needs. This introduces unnecessary network and filesystem side effects, expands attack surface, and creates a covert persistence/update-check mechanism unrelated to the core user task.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The file contains natural-language content in Chinese, including comments and user-facing error messages, without indicating that the skill is Chinese-only or giving users a language/locale option. Per the policy, forcing a specific language without opt-in can be a locale-policy violation.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/rgh.js:46