Back to skill

Security audit

天津安信华瑞科技有限公司-可燃气体报警器主机-配套技能

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its gas-reporting purpose, but its template includes automatic hardcoded OTA updates and overbroad identifier sharing that need review before deployment.

Review before installing or deploying. Disable or replace the default OTA endpoint, require signed update manifests and hash verification before any update or reboot, minimize identifiers sent to update services, and confirm the customer explicitly needs IMEI, ICCID, or IMSI in telemetry.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T03 · Remote Payload Retrieval and Execution

Error
Location
assets/template/main.py:325
Finding
Remote OTA Packages Are Installed Without Cryptographic Authenticity Verification<![CDATA[ ## Vulnerability Details **File Location**: `assets/template/config.py:40`; `assets/template/main.py:325-370` **Vulnerability Type**: Unauthenticated remote firmware installation **Risk Level**: Critical ### Vulnerable Code ```python # assets/template/config.py:40 URL_OTA = "https://hu-wei-di-tu-a98abc-1258458441.ap-shanghai.app.tcloudbase.com/ota" ``` ```python # assets/template/main.py:325-370 resp = request.post(url, data=body, headers=headers) print("[HTTP][{}] 状态码:".format(tag), resp.status_code) if resp.status_code != 200: led_flash(led_net, 0.1, 0.1, 3) report_fail_cnt += 1 return False # 成功 led_flash(led_net, 0.25, 0.25, 4) led_net.on() report_fail_cnt = 0 # 解析响应(流式读取) for chunk in resp.text: try: ret = ujson.loads(chunk) print("[HTTP][{}] 响应:".format(tag), ret) # OTA 升级检测 if tag == "ota" and ret.get("code") == 200: file_list = ret.get("file_list", []) if file_list: _run_ota(file_list) except: pass return True def _run_ota(file_list): """下载文件列表并触发 OTA 重启。""" print("[OTA] 开始升级,文件列表:", file_list) try: fota = app_fota.new() fota.bulk_download(file_list) fota.set_update_flag() Power.powerRestart() except Exception as e: print("[OTA] 升级失败:", e) ``` ### Technical Analysis The OTA service controls the `file_list` passed directly to `app_fota.bulk_download()`. After downloading the supplied files, the code sets the update flag and immediately restarts the device. No application-level authenticity or authorization controls are present. In particular, the implementation does not: - Verify a digital signature over the update manifest or firmware. - Verify a trusted cryptographic hash for every downloaded artifact. - Restrict download URLs to an approved host and path. - Pin the OTA service's public key or certificate. - Validate the target device model or firmware compatibility. - Enforce a ...[truncated 1769 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable OTA by default and skip `check_ota()` when `URL_OTA` is empty. 2. Use a vendor-controlled, access-restricted OTA endpoint. 3. Require a signed manifest containing the firmware hash, version, device model, size, and approved download location. 4. Verify the manifest and firmware with an asymmetric signature whose trusted public key is embedded in protected device storage. 5. Verify a SHA-256 or stronger hash before setting the update flag. 6. Allowlist the exact OTA scheme, hostname, port, and path; reject redirects and user-controlled download hosts. 7. Add certificate or public-key pinning where supported. 8. Enforce device-model compatibility, monotonic versioning, expiration times, and anti-rollback protection. 9. Use staged deployment and explicit administrative authorization for production updates. 10. Fail closed: malformed responses, unknown fields, failed signature checks, and unexpected URLs must prevent installation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
assets/template/main.py:382
Finding
IMEI, IMSI, and Sensor Telemetry Are Disclosed to a Hardcoded OTA Service<![CDATA[ ## Vulnerability Details **File Location**: `assets/template/config.py:40`; `assets/template/main.py:382-394` **Vulnerability Type**: Excessive collection and transmission of persistent cellular identifiers **Risk Level**: Medium ### Vulnerable Code ```python # assets/template/config.py:40 URL_OTA = "https://hu-wei-di-tu-a98abc-1258458441.ap-shanghai.app.tcloudbase.com/ota" ``` ```python # assets/template/main.py:382-394 now = utime.localtime() ota_body = { "time" : "{}-{}-{} {}:{}:{}".format(*now[:6]), "ts" : utime.mktime(now), "imei" : cell_info.get("IMEI", ""), "imsi" : cell_info.get("IMSI", ""), "manu" : MANUFACTURER, "product" : TERMINAL_TYPE, "project" : PROJECT_NAME, "softver" : PROJECT_VERSION, "http_body": last_payload, } print("[OTA] 检查 OTA 更新...") http_post(URL_OTA, ota_body, tag="ota") ``` ### Technical Analysis The OTA request includes the full IMEI, full IMSI, and `last_payload`, which contains device and gas-detector telemetry. These values are sent to a separate hardcoded OTA service during startup and subsequent update checks. An IMSI is a persistent subscriber identifier and is generally unnecessary for determining whether a firmware update is available. Including the complete operational payload also exceeds the minimum data needed for an update query. The implementation does not show user or operator consent, identifier pseudonymization, a retention policy, or application-layer protection for these fields. Although the configured URL uses HTTPS, the receiving service obtains the identifiers in plaintext at the application layer. A compromise or misuse of that service would expose persistent device and subscriber identifiers together with operational data. ### Attack Path 1. A device boots or reaches its scheduled daily OTA check. 2. The code obtains the device's IMEI and SIM subscriber IMSI. 3. It combines those identifiers with manufacturer information, firmware ...[truncated 1034 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the IMSI from OTA requests unless a documented, necessary, and consented use case requires it. 2. Do not include `last_payload` or sensor readings in a firmware-update query. 3. Replace the IMEI with a randomly generated, update-specific pseudonymous identifier. 4. Send only the minimum required values, such as device model, hardware revision, current firmware version, and update channel. 5. Document the collection purpose, recipient, retention period, and deletion process. 6. Require explicit deployment-owner approval before sending persistent cellular identifiers. 7. Protect the OTA API with mutual TLS or device-bound request authentication. 8. Separate OTA metadata from operational telemetry storage and apply strict least-privilege access controls. 9. Avoid logging full IMEI, IMSI, or request bodies in production. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
assets/template/modbus.py:102
Finding
Modbus RTU Responses Are Accepted Without CRC and Responder Validation<![CDATA[ ## Vulnerability Details **File Location**: `assets/template/modbus.py:102-127` and `assets/template/modbus.py:145-159` **Vulnerability Type**: Insufficient validation of untrusted serial-bus responses **Risk Level**: High ### Vulnerable Code ```python # assets/template/modbus.py:102-127 hex_list = self._recv_hex_list() # 最小有效帧长度:从机地址(1)+功能码(1)+字节数(1)+数据(reg_count*2)+CRC(2) expected_len = reg_count * 2 + 5 try: func_code = int(hex_list[1], 16) if func_code > 0x10: # 错误响应(0x83 等异常码) print("[Modbus] 从机返回异常码:", hex(func_code)) return {"ok": False, "data": []} byte_count = int(hex_list[2], 16) if byte_count != reg_count * 2: print("[Modbus] 数据字节数不符,期望", reg_count * 2, "实得", byte_count) return {"ok": False, "data": []} if len(hex_list) < expected_len: print("[Modbus] 报文太短,期望", expected_len, "实得", len(hex_list)) return {"ok": False, "data": []} # 数据区:跳过地址/功能码/字节数,去掉末尾 CRC 两字节 payload = hex_list[3 : 3 + byte_count] return {"ok": True, "data": [b.decode() for b in payload]} except Exception as e: print("[Modbus] 解析失败:", e) return {"ok": False, "data": []} ``` ```python # assets/template/modbus.py:145-159 hex_list = self._recv_hex_list() try: func_code = int(hex_list[1], 16) if func_code != 0x10: # FC=16 正常响应为 0x10 (16) print("[Modbus] 写寄存器异常响应:", hex(func_code)) return {"ok": False, "reg_count": 0} return {"ok": True, "reg_count": int(hex_list[2], 16)} except Exception as e: print("[Modbus] 写寄存器响应解析失败:", e) return {"ok": False, "reg_count": 0} ``` ### Technical Analysis The driver calculates and appends CRC16 values to outbound frames, but it never validates the CRC16 of received frames. The read path also fails to verify that: - The response slave address matches the requested slave. - The function code is exactly the requested read function (`0x03`). - The frame has exactly the expec ...[truncated 2020 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Retain the raw received bytes and reject frames shorter than the minimum Modbus RTU response length. 2. Calculate CRC16 over every received frame except its final two CRC bytes. 3. Compare the calculated CRC with the received CRC in Modbus little-endian order. 4. Verify that the response slave address exactly matches the requested slave. 5. Require the normal response function code to equal the requested function code. 6. Handle exception responses explicitly by testing the high bit and parsing the exception code. 7. Require the frame length to exactly match the declared byte count. 8. For function `0x10`, validate the echoed starting address and register count against the original request. 9. Flush stale UART bytes before sending a new request and handle partial frames until a complete frame is received. 10. Discard unexpected trailing bytes, malformed frames, and unsolicited responses. 11. Consider physical bus protections, device access controls, and authenticated protocols or gateways where the deployment threat model includes hostile bus participants. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description focuses on AX100 gas detector data acquisition over Modbus RTU and HTTP reporting in QuecPython. The actual code chunk does none of that: it is a standalone hardware utility for controlling an LED through a GPIO pin. This is not a supporting implementation detail for the declared behavior as presented, because the code contains only LED control logic and no telemetry, protocol, controller, or networking functionality related to the stated purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The supplied code is a generic Modbus RTU transport/helper module, not a full skill for collecting AX100 gas controller data and uploading it over HTTP. While Modbus RTU communication is consistent with part of the description, the primary declared purpose emphasizes end-to-end data acquisition and HTTP reporting for AX100 devices. Those key behaviors are not present in this chunk. Additionally, the code exposes write-multiple-registers functionality, which goes beyond the declared read-and-report framing. This is therefore a description-behavior mismatch.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The skill content is presented entirely in Chinese and the title/use instructions assume Chinese-language operation, but there is no indication that the user may choose another language. This creates a locale/language policy concern because the skill effectively imposes a specific language without documenting opt-in or a justified regional constraint.

External Transmission

Medium
Category
Data Exfiltration
Content
### 示例一:换上报地址 + 改频率
```
用户:上报地址改成 https://api.xxx.com/gas/upload,每5分钟上报一次
```
→ 只修改 `config.py`:
```python
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
### 示例一:换上报地址 + 改频率
```
用户:上报地址改成 https://api.xxx.com/gas/upload,每5分钟上报一次
```
→ 只修改 `config.py`:
```python
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
1. 工具:使用 QPYcom(移远官方 PC 工具)连接模组
2. 上传路径:所有 `.py` 文件 → 模组 `/usr/` 目录
3. 自动运行:在 QPYcom「文件」标签页中,将 `main.py` 设为 `auto run`
4. 日志查看:通过 QPYcom 串口终端查看 `print` 输出,定位问题

---
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill’s stated purpose is Modbus-to-HTTP gas reporting, but the code also imports and uses OTA update functionality that can download files and trigger an immediate reboot. In an industrial gas-monitoring context, hidden remote update capability materially expands the trust boundary and creates a path for remote code modification or device disruption if the OTA channel or server is compromised.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The network setup collects and later uses highly sensitive device and subscriber identifiers including IMEI, IMSI, and ICCID. Transmitting such identifiers without clear minimization or disclosure increases privacy and tracking risk, and if intercepted or mishandled can expose fleet inventory and subscriber metadata.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The OTA path downloads files from a server-controlled list and immediately sets an update flag and reboots, with no operator confirmation, maintenance window, integrity check shown in this file, or safety interlock. In a gas-detection system, an unexpected reboot or malicious update could interrupt monitoring or replace trusted logic at a critical moment.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The code sends telemetry and the full last reporting payload to a separate OTA endpoint, even though the skill description only mentions reporting to a customer HTTP platform. This creates undisclosed data exfiltration of operational data and device identifiers to an additional service, which is especially sensitive for industrial safety deployments.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The file’s human-facing comments, docstrings, and usage descriptions are entirely in Chinese, which imposes a specific language on maintainers or users reading this skill. Under the stated policy, language constraints should either be optional or clearly justified as region-specific; no such opt-in or justification appears here.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This method writes IMEI and IMSI values, which are sensitive device/network identifiers, to the controller via Modbus registers. Although there is a post-action log message, there is no prior user disclosure, confirmation, or warning in this file that sensitive identifiers will be transmitted to external hardware.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The guide explicitly documents building and transmitting identifiers such as ICCID and full IMEI to a customer HTTP platform, but provides no privacy, minimization, consent, or protection guidance. These identifiers can enable device tracking, inventory correlation, and exposure of telecom subscriber metadata if the platform, transport, or logs are compromised.

Natural-Language Policy Violations

Low
Confidence
83% confidence
Finding
This file contains natural-language comments exclusively in Chinese, such as the header and section descriptions, with no indication that the skill is intended only for Chinese-speaking users or maintainers. Under the policy, forcing a specific language without user opt-in or documented justification is a natural-language locale violation.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The file’s human-readable comments, docstrings, and operational messages are written entirely in Chinese, which imposes a specific language on maintainers or operators without any opt-in or alternative. This can violate language/locale policy where tools are expected to offer choice or document locale constraints.

Static analysis

No suspicious patterns detected.