Back to skill

Security audit

ninebot-device-skill

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its Ninebot vehicle-query purpose, but it handles a sensitive API key and vehicle location data with overly broad local configuration that could expose the key.

Install only if you are comfortable giving the skill access to your Ninebot vehicle data, including location. Prefer using the environment variable for the API key, avoid putting real keys in config.json or command-line arguments, and do not run it from a directory where an untrusted config.json could redirect authenticated requests.

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/ninebot_query.py:51
Finding

Configurable Network Destination Can Disclose the Ninebot API Key

Content
View full analysis
str: section_url = (cfg.get(section) or {}).get("base_url") if section_url: return section_url return cfg.get("base_url", "") ``` ```python def inject_api_key_header(cfg: Dict[str, Any], headers: Dict[str, str], api_key: str): auth_cfg = cfg.get("auth") or {} header_name = auth_cfg.get("api_key_header") or "x-api-key" prefix = auth_cfg.get("api_key_prefix") or "" headers[header_name] = f"{prefix}{api_key}" def list_devices(cfg: Dict[str, Any], api_key: str, lang: str): url = get_base_url(cfg, "devices").rstrip("/") + cfg["devices"]["path"] payload_tpl = cfg["devices"].get("payload") or {} payload = { k: (v.format(api_key=api_key, lang=lang) if isinstance(v, str) else v) for k, v in payload_tpl.items() } headers: Dict[str, str] = {} if api_key: inject_api_key_header(cfg, headers, api_key) payload_to_send = None if cfg["devices"]["method"].upper() == "GET" else payload res = http_request(cfg["devices"]["method"], url, headers=headers, payload=payload_to_send) devices = deep_get(res, cfg["devices"]["list_path"]) or [] return devices def get_device_info(cfg: Dict[str, Any], api_key: str, sn: str): path = cfg["device_info"]["path"].replace("{sn}", urllib.parse.quote(sn)) url = get_base_url(cfg, "device_info").rstrip("/") + path payload_tpl = cfg["device_info"].get("payload") or {} payload = { k: (v.format(api_key=api_key, sn=sn) if isinstance(v, str) else v) for k, v in payload_tpl.items() } headers: Dict[str, str] = {} if api_key: inject_api_key_header(cfg, headers, api_key) res = http_request(cfg["device_inf ...[truncated 2649 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ninebot_query.py:151
Finding

API Key Can Be Exposed Through Plaintext Configuration and Process Arguments

Content
View full analysis
Optional[str]: if arg_api_key: return arg_api_key env_key = os.getenv("NINEBOT_DEVICESERVICE_KEY") if env_key: return env_key cfg_key = cfg.get("apiKey") if isinstance(cfg, dict) else None if cfg_key: return cfg_key return None ``` ### Technical Analysis The Skill supports storing the API key directly in a plaintext JSON file and accepts the secret through the `--api-key` command-line option. A command-line secret may be exposed through shell history, process inspection facilities, diagnostic tools, execution logs, or automation records. A plaintext `config.json` may be exposed through permissive file permissions, backups, synchronization software, accidental repository commits, or other local processes. The implementation does not check configuration-file permissions, integrate with an operating-system credential store, or warn users about command-line exposure. Although an environment variable is supported and preferred by the declared metadata, the less secure alternatives remain documented and available. ### Attack Path #### Command-Line Exposure 1. A user invokes the script with `--api-key` and a valid credential. 2. The command is retained in shell history or becomes visible through local process inspection ...[truncated 1029 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/ninebot_query.py:185
Finding

Multiple-Device Selection Output Discloses Unnecessary Upstream Metadata

Content
View full analysis
1: print(json.dumps({"choose_device": devices}, ensure_ascii=False)) sys.exit(3) ``` ### Technical Analysis When more than one device is returned, the script prints each complete upstream device object. The documented selection workflow only requires the device serial number and display name, but the implementation does not project the response onto those fields. The API specification already shows that device records may contain additional fields such as image data. Future API versions could add further account or device metadata. Because the entire object is serialized, all such fields would be passed to the invoking Agent and could be retained in chat transcripts, execution logs, or downstream systems. This violates data-minimization principles: only the minimum information required to select a device should be emitted. ### Attack Path 1. The authenticated Ninebot account contains more than one vehicle. 2. The upstream device-list response includes fields beyond the serial number and device name. 3. The user runs the script without selecting a specific device. 4. The script serializes the complete `devices` collection. 5. Additional metadata is exposed to the calling Agent, terminal, logs, or downstream processing systems. No separate attacker-controlled code execution is required. Exploitation is primarily a privacy and data-handling concern arising during normal use. ### Impact Assessment The issue can disclose unnecessary vehicle or account metadata present in the upstream response. Known examples include device serial numbers, names, and image-related fields; the exact scope depends on the API response. The issue does not grant s ...[truncated 172 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (9)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill declares access patterns that include environment variables, local file reads, and network use, but it does not explicitly constrain or disclose tool scope via permissions or allowed-tools. This weakens least-privilege protections and makes it easier for an agent runtime to over-grant capabilities beyond what is necessary for simple vehicle queries.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill explicitly supports retrieving precise vehicle location but provides no user-facing privacy warning, confirmation step, or guidance on safe handling of location data. Because location reveals sensitive real-world movement and presence information, exposing it without privacy safeguards increases the risk of stalking, surveillance, or unintended disclosure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill tells users to provide and store an API key but does not warn that the key is a sensitive credential that can authorize access to vehicle data if exposed. Without handling guidance, users may paste the key into insecure channels or leave it in local files, increasing the risk of credential theft and unauthorized tracking or status queries.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The documentation instructs storing the Ninebot API key in a local config.json file, increasing the chance that a long-lived credential is left on disk in plaintext where other local processes, users, backups, or logs may access it. For a vehicle-information skill, persistent local storage of a sensitive token is not clearly necessary and expands the exposure window if the host is compromised.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The spec explicitly instructs users to supply a live API key via an environment variable or config file, but provides no warning about secret handling, least-privilege use, or avoiding disclosure in source control and logs. In an agent skill context, this can lead developers to embed production credentials insecurely, increasing the chance of credential leakage and unauthorized access to vehicle/device data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The example configuration includes a plaintext apiKey field with a realistic placement that users are likely to copy verbatim into a file. Because this skill accesses sensitive vehicle telemetry such as location, battery, and status, insecure credential storage can expose both account access and downstream privacy-sensitive data if the config file is leaked.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code calls remote Ninebot APIs to list devices and fetch dynamic device information, including location data, using an API key. The script has no confirmation prompt or user-facing disclosure in code about transmitting identifiers and retrieving sensitive location/status information.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

Natural-language content in the file includes Chinese-only instructions and examples such as 使用..., 无, and Chinese location/time strings, but the document does not state that the skill is intentionally China-region-only or provide an alternative language option. That can create a language/locale policy issue when a skill implicitly forces one language without user opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The CLI sets the default language to "zh", which imposes a specific locale unless the user explicitly overrides it. This is a natural-language policy concern because the skill defaults to one language rather than asking the user or using a neutral default.

Content

No source excerpt is available for this finding.