Back to skill

Security audit

OneBot Adapter

Security checks for vulnerabilities and agentic risk

Overview

This QQ/OneBot adapter is mostly coherent, but it exposes moderation actions and private message data in ways users should review carefully before installing.

Install only if you are comfortable giving the skill access to your configured OneBot server and bot account. Before production use, restrict the token and endpoint to trusted hosts, prefer TLS for remote connections, disable or redact verbose event logging, and add explicit authorization and confirmation around message deletion, kicks, bans, and group-setting changes.

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

Warning
Location
scripts/onebot_ws_listener.py:81
Finding
Unredacted OneBot Events Expose Message and Identity Data in Logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/onebot_ws_listener.py:81-87` **Vulnerability Type**: Sensitive data exposure through unrestricted event logging **Risk Level**: Medium ### Vulnerable Code ```python message = await ws.recv() event = json.loads(message) # Print event for debugging print(f"\n[Event] {json.dumps(event, ensure_ascii=False, indent=2)}") # Handle event await self._handle_event(event) ``` Additional event data is printed by the example handlers at `scripts/onebot_ws_listener.py:119-120`, `scripts/onebot_ws_listener.py:133-134`, and `scripts/onebot_ws_listener.py:138-140`: ```python user_id = event.get("user_id") message = event.get("message") print(f"[Private] {user_id}: {message}") ``` ```python message = event.get("message") print(f"[Group {group_id}] {user_id}: {message}") ``` ```python notice_type = event.get("notice_type") print(f"[Notice] {notice_type}: {event}") ``` ### Technical Analysis Every received OneBot event is serialized and written to standard output without redaction, filtering, or an opt-in debug setting. Events can contain private or group message contents, QQ user identifiers, group identifiers, message identifiers, timestamps, and notice metadata. Standard output is frequently captured by container runtimes, service managers, CI systems, terminal recorders, or centralized logging platforms. Consequently, data that is only intended for message processing may be retained and exposed to parties with log access. The handler-level logging duplicates this exposure even if the full-event statement is later removed. ### Attack Path 1. An attacker or ordinary user sends a private or group message to the bot. 2. The OneBot server forwards the resulting event over WebSocket. 3. The listener parses and serializes the complete event. 4. The event and its sensitive fields are written to standard output. 5. A process supervisor, container platform, or logging service retains that output. 6. A user with ...[truncated 527 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove full-event logging from the default execution path. - Introduce an explicit debug flag that defaults to disabled. - Log only operational metadata needed for troubleshooting, such as event type and a generated correlation identifier. - Redact message bodies, authorization data, QQ identifiers, group identifiers, and other personal data before logging. - Apply the same redaction policy to all example handlers. - Configure restrictive access controls, short retention periods, and encryption for any logs that must contain message metadata. - Document that production deployments must not enable verbose event logging without a privacy and retention review. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/onebot_client.py:25
Finding
Configurable Plaintext Transports Can Expose OneBot Tokens and Message Traffic<![CDATA[ ## Vulnerability Details **File Location**: `scripts/onebot_client.py:25-38`; `scripts/onebot_ws_listener.py:26-27,67-75`; `SKILL.md:15-19` **Vulnerability Type**: Transmission of bearer credentials and sensitive traffic over potentially unencrypted channels **Risk Level**: Medium ### Vulnerable Code The HTTP client accepts an arbitrary URL, adds the bearer token, and sends requests without enforcing TLS: ```python self.base_url = base_url or os.getenv("ONEBOT_HTTP_URL", "http://127.0.0.1:3000") self.token = token or os.getenv("ONEBOT_TOKEN", "") self.headers = {"Content-Type": "application/json"} if self.token: self.headers["Authorization"] = f"Bearer {self.token}" def _request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]: """Make HTTP request to OneBot API""" url = f"{self.base_url}{endpoint}" try: if method == "GET": resp = requests.get(url, headers=self.headers, timeout=10) else: resp = requests.post(url, headers=self.headers, json=data, timeout=10) ``` The WebSocket listener behaves similarly: ```python self.ws_url = ws_url or os.getenv("ONEBOT_WS_URL", "ws://127.0.0.1:3001") self.token = token or os.getenv("ONEBOT_TOKEN", "") ``` ```python headers = {} if self.token: headers["Authorization"] = f"Bearer {self.token}" while self.running: try: print(f"Connecting to {self.ws_url}...") async with websockets.connect(self.ws_url, extra_headers=headers) as ws: ``` The documented configuration uses plaintext URL schemes: ```bash export ONEBOT_WS_URL="ws://127.0.0.1:3001" export ONEBOT_HTTP_URL="http://127.0.0.1:3000" export ONEBOT_TOKEN="your-token" ``` ### Technical Analysis The loopback defaults reduce exposure when the OneBot server runs on the same trusted host. Nevertheless, both connection URLs are configurable and the implementation accepts remote `http://` and `ws://` endpoints without rejection or warning. When a bea ...[truncated 1662 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require `https://` for remote HTTP endpoints and `wss://` for remote WebSocket endpoints. - Permit `http://` and `ws://` only when the parsed destination is a verified loopback address. - Reject insecure remote URLs by default rather than merely displaying a warning. - If insecure remote transport is required for development, place it behind an explicit, clearly named opt-in setting. - Update the documentation to use TLS-secured examples for networked deployments. - Validate TLS certificates and hostnames; do not introduce a global certificate-verification bypass. - Rotate any bearer token that may previously have crossed an untrusted plaintext network. - Restrict OneBot tokens to the minimum available permissions and use network access controls to limit API reachability. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/onebot_ws_listener.py:45
Finding
Incorrect Handler Registration Processes Group Messages as Private Messages<![CDATA[ ## Vulnerability Details **File Location**: `scripts/onebot_ws_listener.py:45-60,116-127,147-153` **Vulnerability Type**: Incorrect event dispatch and message-context confusion **Risk Level**: Low ### Vulnerable Code The dispatcher invokes handlers registered for the general post type and then invokes handlers registered for the specific message type: ```python # Call general handlers if post_type in self.handlers: for handler in self.handlers[post_type]: try: await handler(event) except Exception as e: print(f"Handler error: {e}") # Call specific message type handlers if post_type == "message": message_type = event.get("message_type") if message_type in self.handlers: for handler in self.handlers[message_type]: try: await handler(event) except Exception as e: print(f"Handler error: {e}") ``` The private handler can send a private reply: ```python async def handle_private_message(event: dict): """Handle private messages""" user_id = event.get("user_id") message = event.get("message") print(f"[Private] {user_id}: {message}") # Auto-reply example if message == "ping": # Send reply via HTTP API from onebot_client import OneBotClient client = OneBotClient() client.send_private_msg(user_id, "pong") ``` It is registered for both all message events and private message events: ```python # Register handlers listener.on("message", handle_private_message) listener.on("private", handle_private_message) listener.on("group", handle_group_message) listener.on("notice", handle_notice) ``` ### Technical Analysis For every event whose `post_type` is `message`, the dispatcher first calls all handlers registered under `"message"`. Because `handle_private_message` is registered under that key, it receives both private and group messages. For a private message, the same function is invoked a ...[truncated 1520 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `listener.on("message", handle_private_message)`. - Register `handle_private_message` only for the `"private"` message type. - If a generic message handler is needed, implement it as a separate function that does not perform private-specific actions. - Add an explicit guard inside the private handler: ```python if event.get("message_type") != "private": return ``` - Ensure that each event is dispatched once to the intended handler unless multiple invocation is explicitly required. - Add tests for private and group events, verifying that group input cannot invoke private-only side effects and private input does not produce duplicate replies. - Require authorization checks inside handlers that perform moderation or other privileged actions rather than relying exclusively on routing. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared description frames the skill as a message send/receive adapter, but the referenced client capabilities imply substantially broader powers including enumerating contacts/groups and performing destructive or administrative actions such as recalls, kicks, bans, and renames. This mismatch is dangerous because downstream users or agents may grant or invoke the skill under the assumption of low-risk messaging behavior while it can actually perform moderation and information-gathering operations on QQ/OneBot targets.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill documents network access and use of environment variables for connection endpoints and tokens, but it does not declare any explicit tool scope or permissions. This creates a capability transparency problem: an agent or reviewer may invoke the skill without realizing it can access sensitive configuration and communicate with external/local services, increasing the chance of unintended data exposure or unauthorized actions.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This markdown file includes a group-management example that removes a user from a group via `client.set_group_kick(group_id, user_id)` without any accompanying warning, confirmation, or note that the action is administrative and disruptive. For markdown files, examples that can affect user access or system integrity should disclose such behavior so users understand the consequence before adopting the pattern.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code performs outbound HTTP requests and can transmit message content, group identifiers, and user identifiers to a configured OneBot endpoint. While there are internal docstrings, there is no user-facing confirmation, log message, or warning in this file indicating that data will be sent over the network.

External Transmission

Medium
Category
Data Exfiltration
Content
if method == "GET":
                resp = requests.get(url, headers=self.headers, timeout=10)
            else:
                resp = requests.post(url, headers=self.headers, json=data, timeout=10)
            
            resp.raise_for_status()
            return resp.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill is described as QQ message integration, but this client also exposes administrative operations such as kicking users, banning users, and changing group metadata. In an agent-skill context, capability expansion beyond the stated purpose increases the risk of unintended or unauthorized destructive actions if the skill is invoked with untrusted inputs or granted broad access.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
Group moderation and configuration functions are not necessary for simple send/receive QQ integration, so their presence violates least privilege for the advertised purpose. In an autonomous agent environment, these extra functions could be abused to remove users, mute members, or rename groups without operator awareness.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The listener logs the full incoming OneBot event payload, which can include message bodies, user IDs, group IDs, and other metadata. In a QQ/OneBot integration context this is sensitive user data, and printing it to stdout can expose private conversations or identifiers through console output, process supervisors, centralized logs, or shared hosting environments.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The keyword-response example activates on the Chinese term `帮助` and returns a Chinese response, which imposes a specific language behavior without any opt-in or note that the example is locale-specific. This can violate language/locale policy when a skill implicitly assumes one language instead of offering a choice or documenting the constraint.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The client reads ONEBOT_TOKEN from the environment and automatically attaches it as a Bearer token to outbound requests. Although this is a normal implementation pattern, the file does not provide any user-facing notice that credentials are being read from environment variables and transmitted to the remote service.

Missing User Warnings

Low
Confidence
87% confidence
Finding
This code accesses a sensitive credential source via an environment variable to authenticate WebSocket connections. While this is a normal implementation pattern, the file does not include any comment or docstring warning that credentials are consumed from the environment.

Static analysis

No suspicious patterns detected.