Back to skill

Security audit

unisound-prescription-assist

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent as a prescription-assistance tool, but it handles sensitive patient data and its privacy protections are weaker than the documentation claims.

Review before installing or using with real patient data. Do not submit names, IDs, phone numbers, addresses, record numbers, or other identifiers unless the remote model service and legal/privacy arrangements explicitly allow it. Use only an approved HTTPS model endpoint, avoid overriding --base, and use a narrowly scoped AppKey that can be rotated. Clinicians should treat the output as decision support, not an authoritative prescription.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run.py:131
Finding
Patient information is transmitted without the promised de-identification<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:26-30`; `scripts/run.py:131-142` **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: High ### Vulnerable Code The documentation claims that identifying information is removed before any external transmission: ```markdown 数据安全、隐私与伦理声明 ------------------------ - **最小必要原则**:仅处理开具处方所必需的患者信息;不要求也不鼓励包含姓名、证件号、手机号等身份信息。 - **严格脱敏**:在发送至任何模型/接口前,会对可识别个人身份的信息进行脱敏/去标识化处理。 - **不做本地持久化**:仅在内存中短暂处理;**本次调用结束即销毁**。 ``` However, the implementation places the complete input directly into the prompt and sends it to the external model: ```python def run_prescription(prescription_info: str, llm, output_path: str = "") -> int: prompt = f"""请根据以下信息,为患者提供处方开具辅助建议。 【处方信息】 {prescription_info.strip()} 请严格按照要求输出 JSON + 摘要。""" print("正在分析处方信息...") result = llm([sys_msg(SYSTEM_PROMPT), user_msg(prompt)]) ``` ### Technical Analysis There is no de-identification, redaction, field filtering, or validation between loading the clinical record and transmitting it to the model API. `prescription_info` is interpolated into the outbound prompt without modification. Although the documentation discourages users from supplying names, identification numbers, and telephone numbers, a warning is not a technical control. Unstructured clinical records commonly contain direct and indirect identifiers, including names, contact details, dates, record numbers, addresses, and rare diagnoses. This discrepancy creates a false security expectation: operators may reasonably rely on the explicit statement that de-identification occurs automatically and therefore submit records containing protected health information. ### Attack Path 1. A clinician supplies a text or JSON input containing patient identifiers and clinical information. 2. `load_input()` reads the record and returns its contents without redaction. 3. `run_prescription()` interpolates the entire record into `prompt`. 4. `make_llm_caller()` pas ...[truncated 958 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement local de-identification before prompt construction: - Remove names, telephone numbers, email addresses, addresses, identity numbers, patient record numbers, and other direct identifiers. - Normalize or generalize dates and rare identifying attributes where clinically appropriate. - Apply redaction to both unstructured text and every supported JSON field. 2. Reject or require explicit confirmation for input that still appears to contain identifiers after redaction. 3. Minimize transmitted fields by parsing the input into a strict clinical schema and sending only fields required for medication assessment. 4. Add automated tests covering representative identifiers, Unicode text, nested JSON objects, malformed inputs, and attempts to bypass redaction. 5. Clearly disclose the destination, retention policy, and privacy boundary of the model service. 6. If reliable de-identification cannot be guaranteed, remove the documentation claim and require users to provide pre-de-identified data explicitly. 7. Ensure that the model service and data-processing arrangements are approved for the applicable category of medical information. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run.py:44
Finding
Configurable API endpoint can receive the bearer credential and sensitive clinical records<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:44-45`; `scripts/run.py:205` **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: High ### Vulnerable Code The bearer credential is attached to requests sent to the caller-controlled base URL: ```python def make_llm_caller(appkey: str, base: str = DEFAULT_LLM_BASE, model: str = DEFAULT_LLM_MODEL, timeout: int = 0): url = f"{base.rstrip('/')}/chat/completions" headers = {"Authorization": f"Bearer {appkey}"} ``` The command-line interface accepts the base URL without restricting its scheme or destination: ```python parser.add_argument("--base", default=DEFAULT_LLM_BASE, help=f"内部大模型 base URL(默认:{DEFAULT_LLM_BASE})。") ``` The resulting request also contains the complete clinical prompt: ```python resp = _http_post(url, payload, headers, timeout=timeout) ``` ### Technical Analysis The program treats `--base` as trusted while allowing an arbitrary string to control the request destination. It does not enforce HTTPS, verify that the hostname belongs to an approved model provider, or require additional confirmation before forwarding credentials to a non-default origin. Standard TLS certificate verification may apply when an HTTPS URL is used, but the application itself neither requires HTTPS nor constrains the hostname. Consequently, a plain HTTP endpoint or an attacker-operated HTTPS endpoint can receive: - The `Authorization: Bearer <appkey>` header. - The full model request, including patient and prescription information. This is a trust-boundary failure because a credential intended for the internal medical model is forwarded to whichever destination is supplied through `--base`. ### Attack Path 1. An attacker influences an invocation command, deployment configuration, wrapper script, or setup instruction. 2. The attacker sets `--base` to an endpoint they control, such as `https://attacker.example/v1` or a plain HTTP URL. 3. `make_llm_caller()` const ...[truncated 995 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--base` unless alternate endpoints are operationally necessary. 2. If endpoint configuration is required: - Parse the URL with `urllib.parse`. - Require the `https` scheme. - Reject embedded credentials, fragments, and unexpected ports. - Allowlist exact approved hostnames and expected path prefixes. - Resolve redirects carefully and prohibit redirects to untrusted origins. 3. Bind credentials to destinations: - Do not send the internal AppKey to a non-default origin. - Use separate, narrowly scoped credentials for each approved service. - Prefer short-lived credentials with minimal permissions and usage limits. 4. Require explicit operator confirmation for any endpoint differing from the approved default, while still enforcing an allowlist. 5. Redact clinical data before transmission and log only non-sensitive request metadata. 6. Add tests proving that HTTP URLs, localhost, private-network addresses, malformed URLs, and unapproved domains are rejected. 7. Rotate any AppKey that may already have been sent to an untrusted endpoint and review its usage history for unauthorized activity. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • 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 (4)

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documentation describes code that reads input files, writes output files, and sends patient prescription data to a remote medical model API, but it does not declare any explicit tool permissions or allowed-tools scope. In a medical context this is dangerous because the skill handles sensitive health data and can perform network egress and filesystem access without clear least-privilege boundaries, increasing the risk of unintended data exposure or misuse.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file’s natural-language instructions and usage documentation are presented only in Chinese, which imposes a specific language/locale on users without stating that this is optional. The policy for SQP-3 flags language or locale constraints when the skill does not offer the user a language choice or explicit opt-in.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The system prompt and user-facing instructions require the model to operate and format output entirely in Chinese, including a fixed Chinese summary marker and Chinese JSON field semantics. This is a language policy constraint embedded in natural-language instructions, and the file does not offer a user language choice or explain a justified region-specific limitation.

Static analysis

No suspicious patterns detected.