Back to skill

Security audit

Pill Counter Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real pill-counting tool, but its AI mode can upload medication images externally and its file handling is broader than its declared workspace scope.

Review before installing. Use the local OpenCV mode for sensitive medication photos unless you are comfortable sending the image to Xiaomi MiMo. Run it only on files inside the declared pill-counter workspace, and avoid using output paths that could overwrite important files.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:13
Finding
Unpinned Third-Party Dependencies Create Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:13` **Vulnerability Type**: `T08: Insecure Dependencies` **Risk Level**: Medium ### Vulnerable Code ```bash pip install opencv-python-headless numpy Pillow ``` ### Technical Analysis The documented installation command retrieves mutable package versions from the configured Python package index without version constraints or integrity hashes. Consequently, installations performed at different times may receive different and unreviewed code. This does not prove that any listed dependency is currently malicious. However, it creates a supply-chain exposure: a compromised upstream release, package index, dependency account, or network/package-source configuration could cause attacker-controlled package code to be installed. Python packages may execute code during installation or later when imported by the Skill. ### Attack Path 1. An attacker compromises an upstream package release, maintainer account, package index, or package distribution path. 2. The attacker publishes a malicious version under one of the requested package names. 3. A user follows the setup instructions and runs the unpinned `pip install` command. 4. `pip` resolves and installs the attacker-controlled release. 5. Malicious package code executes during installation or when the Skill imports the dependency. ### Impact Assessment Successful exploitation could run code with the privileges of the user installing or invoking the Skill. Depending on those privileges, an attacker could access user files, credentials, environment variables, network resources, and writable application state. The potential scope is the Python environment and all resources available to its operating-system account. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to a reviewed version in a lock file or requirements file. 2. Pin transitive dependencies where practical. 3. Record cryptographic hashes and install with `pip --require-hashes`. 4. Install dependencies from a trusted, explicitly configured package index. 5. Regularly scan the locked dependency set for known vulnerabilities. 6. Test and review dependency upgrades before changing the lock file. Example hardened installation workflow: ```bash python3 -m pip install --require-hashes -r requirements.txt ``` ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/pill_counter.py:304
Finding
User-Controlled Paths Bypass the Declared Filesystem Scope<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pill_counter.py:304-331` **Related Declaration**: `SKILL.md:18-20` **Vulnerability Type**: `T05: Unauthorized Access and Privilege Escalation` **Risk Level**: Medium ### Vulnerable Code The Skill declares a restricted workspace: ```yaml permissions: paths: - "~/.openclaw/workspace/pill-counter/**" write: true ``` The implementation accepts paths and uses them without canonicalization or workspace validation: ```python parser.add_argument('image', help='药片图片路径') parser.add_argument('--ai', action='store_true', help='使用 MiMo V2 Omni AI 识别') parser.add_argument('--save', help='保存标注结果图片路径') parser.add_argument('--export-csv', help='导出 CSV 统计表格路径') parser.add_argument('--output', choices=['text', 'json'], default='text') args = parser.parse_args() if not os.path.exists(args.image): print(f"❌ 文件不存在: {args.image}"); sys.exit(1) ``` The unvalidated paths subsequently reach file-writing operations: ```python if pills: default_out = str(Path(args.image).with_suffix('')) + '_result.jpg' draw_result(args.image, pills, args.save or default_out) print(f"📁 标注图片: {args.save or default_out}") csv_path = args.export_csv or str(Path(args.image).with_suffix('')) + '_report.csv' export_csv(csv_path, total, categories) print(f"📊 统计表格: {csv_path}") ``` ### Technical Analysis The metadata declares access only under `~/.openclaw/workspace/pill-counter/**`, but the script does not enforce that boundary. The positional image path, `--save`, and `--export-csv` values may be absolute paths, relative traversal paths, or paths resolving through symbolic links. The script therefore relies entirely on external sandbox enforcement. If invoked outside such a sandbox, its effective access is determined by the operating-system account rather than the declared Skill scope. In AI mode, an arbitrary readable image can also be encoded and sent to the documented MiMo API endpoint. The Base64 encoding ...[truncated 1664 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve every input and output path before use: ```python workspace = Path("~/.openclaw/workspace/pill-counter").expanduser().resolve() requested = Path(user_path).expanduser().resolve() requested.relative_to(workspace) ``` 2. Reject paths that cannot be proven to reside under the authorized workspace. 3. Apply validation separately to the input image, annotated-image destination, and CSV destination. 4. Define and enforce a symbolic-link policy. Where supported, use secure open operations that prevent following unexpected symbolic links. 5. Refuse to overwrite existing files unless the user supplies an explicit overwrite option. 6. Validate that the input is a regular file and that output parents are approved directories. 7. Before AI transmission, clearly identify the remote destination and request explicit user confirmation for the selected file. 8. Treat platform sandboxing as defense in depth rather than the sole enforcement mechanism. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/pill_counter.py:326
Finding
Default Execution Creates and May Overwrite Undisclosed Output Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pill_counter.py:326-331` **Vulnerability Type**: `T09: Insecure Skill Coding Practices` **Risk Level**: Low ### Vulnerable Code ```python if pills: default_out = str(Path(args.image).with_suffix('')) + '_result.jpg' draw_result(args.image, pills, args.save or default_out) print(f"📁 标注图片: {args.save or default_out}") csv_path = args.export_csv or str(Path(args.image).with_suffix('')) + '_report.csv' export_csv(csv_path, total, categories) print(f"📊 统计表格: {csv_path}") ``` The CSV helper opens its destination in truncating write mode: ```python def export_csv(csv_path, total, categories): with open(csv_path, 'w', encoding='utf-8-sig', newline='') as f: ``` ### Technical Analysis A successful run always exports a CSV report, even when `--export-csv` is absent. When local detection returns pill details, it also writes an annotated image even when `--save` is absent. The documentation presents `--save` and `--export-csv` as explicit output controls, but the implementation derives default output paths from the input filename and writes them automatically. Existing files with the generated names may be replaced without confirmation. This also amplifies the unrestricted-path issue because output is created adjacent to an input located outside the declared workspace. ### Attack Path 1. A user invokes the basic counting command and expects console-only output. 2. A file named `<input-stem>_report.csv` or `<input-stem>_result.jpg` already exists beside the input. 3. The analysis succeeds. 4. The script opens or writes the derived destination without checking whether it exists. 5. The existing file is truncated or replaced by generated output. Alternatively, no prior file needs to exist: the Skill still creates files that the user did not explicitly request. ### Impact Assessment The issue permits unintended file creation and replacement within directories writable by the invoking p ...[truncated 221 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Write an annotated image only when `--save` is explicitly provided. 2. Write a CSV report only when `--export-csv` is explicitly provided. 3. Before writing, check whether the destination already exists and refuse replacement unless an explicit `--overwrite` option is present. 4. If default exports are intentional, document them clearly and constrain them to an approved output directory. 5. Use atomic file creation where possible: write to a securely created temporary file in the destination directory and replace the destination only after successful completion. 6. Return nonzero status and a clear error when a destination would be overwritten without authorization. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (7)

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill explicitly describes an AI mode that compresses and base64-encodes user images before sending them to an external MiMo API, but the documentation does not clearly warn users that local image content leaves the device and may be processed by a third party. Because pill images can contain sensitive medical context, packaging, labels, or other identifying information, this creates a real privacy and data-handling risk through inadequate disclosure rather than direct code exploitation.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The script’s docstring, prompts, CLI help, and runtime output are written in Chinese, with no user option to select another language or indication that the tool is intentionally limited to a Chinese-speaking context. This can violate language/locale policy when a skill imposes a specific language without opt-in.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
In AI mode, the script base64-encodes the input image and sends it to a third-party API, but the CLI help and flow do not clearly warn users that image contents leave the local machine. If pill photos contain patient information, labels, or other sensitive context, this can cause an unintended privacy and compliance breach.

External Transmission

Medium
Category
Data Exfiltration
Content
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}}
    ]}]}

    req = urllib.request.Request("https://api.xiaomimimo.com/v1/chat/completions",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"})
Confidence
95% confidence
Finding
The script sends user-supplied image data to an external domain over the network as part of AI mode. In this skill context, images of medication may include sensitive health-related information, making silent external transmission materially risky even though it is functionally intended.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The docstring at L198 says this function performs '多参数投票检测' and nearby comments describe using 3 parameter sets and a stabilized voting-style result. However, after collecting all groups, the code at L211-L216 simply selects the one group whose count is closest to the median and assigns it directly to `merged`, never calling the actual merge routine `merge_pills_groups` defined earlier. This is an intent/code contradiction, not merely missing detail.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
文件中的自然语言说明整体仅使用中文,未提供用户可选择语言的说明,也没有表明该技能仅面向特定中文区域或场景。根据规则,若技能强制特定语言而无用户选择或合理地域限定,可构成语言/locale 政策问题。

Missing User Warnings

Low
Confidence
84% confidence
Finding
The code accesses a local configuration file and reads an API credential from it to authorize remote requests. This is sensitive credential handling, but there is no comment, docstring warning, or user-facing notice that the skill will access stored API credentials when AI mode is used.

Static analysis

No suspicious patterns detected.