Back to skill

Security audit

unisound-chief-complaint-disease-op

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent medical-record quality-control tool, but it sends sensitive record text and a bearer key to a user-configurable LLM endpoint without endpoint validation or an explicit consent gate.

Install only in an environment approved to process de-identified medical records with the configured model provider. Keep --base fixed to the trusted HiVoice HTTPS endpoint, avoid passing real patient identifiers, protect the app key, and have a qualified clinician review outputs before use.

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/emr_qc_impl.py:53
Finding
Unrestricted API Endpoint Can Receive the App Key and Medical Record Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/emr_qc_impl.py:53-61`; endpoint configuration is exposed through `scripts/emr_qc.py:24-35` and `scripts/run.py:48-59` **Vulnerability Type**: Arbitrary outbound API destination and sensitive-data disclosure **Risk Level**: High ### Vulnerable Code ```python def make_llm_caller(appkey: str, base: str = DEFAULT_LLM_BASE, model: str = DEFAULT_LLM_MODEL, timeout: int = 0): """Return an llm(messages) → str calling function.""" url = f"{base.rstrip('/')}/chat/completions" headers = {"Authorization": f"Bearer {appkey}"} def llm(messages: List[Dict[str, str]]) -> str: payload = {"model": model, "messages": messages, "temperature": 0} resp = _http_post(url, payload, headers, timeout=timeout) ``` The destination is directly configurable through the command-line entry points: ```python parser.add_argument( "--base", default=DEFAULT_LLM_BASE, help=f"Model base URL (default: {DEFAULT_LLM_BASE}).", ) ``` ### Technical Analysis The user-controlled `--base` value is used to construct the destination of an authenticated HTTP request without validating the scheme or hostname. The request includes both: - The HiVoice application key in the `Authorization: Bearer` header. - LLM messages containing patient-derived chief-complaint and present-illness data. The implementation does not require HTTPS and does not restrict the destination to the documented HiVoice host. Consequently, an attacker who can influence invocation arguments can redirect the request to an attacker-controlled HTTP or HTTPS server. Using an HTTP URL additionally exposes the bearer credential and medical data to network interception. This issue crosses a significant trust boundary because a credential intended for one service can be transmitted to an unrelated destination. ### Attack Path 1. The attacker gains the ability to influence the Skill invocation, deployment configuration, wrapper script, or ...[truncated 1443 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove arbitrary endpoint configuration in production and use a fixed, trusted API endpoint. 2. If endpoint customization is operationally necessary: - Parse the URL with `urllib.parse.urlsplit`. - Require the `https` scheme. - Enforce an explicit hostname allowlist, such as `maas-api.hivoice.cn`. - Reject embedded credentials, unexpected ports, fragments, and ambiguous host representations. 3. Ensure redirects cannot forward authentication headers or medical data to a different origin. Prefer rejecting cross-origin redirects entirely. 4. Separate credentials by destination so a credential issued for HiVoice is never attached to requests for another host. 5. Store the application key in a protected environment variable or secret manager rather than routinely passing it in command-line arguments, which may be visible in process listings or shell history. 6. Add automated tests confirming that HTTP URLs, unapproved hosts, malformed URLs, and cross-origin redirects are rejected before any request is sent. 7. Minimize the transmitted record content and maintain the documented de-identification requirement. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/emr_qc_impl.py:125
Finding
Medical Record Content Is Vulnerable to LLM Prompt Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/emr_qc_impl.py:125-170`, `scripts/emr_qc_impl.py:173-210`, and `scripts/emr_qc_impl.py:217-285` **Vulnerability Type**: Prompt injection through untrusted medical-record fields **Risk Level**: Medium ### Vulnerable Code The chief complaint is directly interpolated into the first classification prompt: ```python llm1_result = llm([user_msg( f"""Task: determine whether the chief complaint consists only of a disease name and duration. Now assess the following chief complaint: {cc}""" )]) ``` The same untrusted field is used in the second classification prompt: ```python llm2_result = llm([user_msg( f"""Task: determine whether the chief complaint consists only of an operation name and duration. Now assess the following chief complaint: {cc}""" )]) ``` The implementation then uses permissive substring matching on unconstrained model output: ```python if "是" not in llm1_result and "是" not in llm2_result: return "无缺陷" ``` Finally, both the chief complaint and present illness are interpolated into another instruction-bearing prompt: ```python return llm([user_msg( f"""You are a medical-record quality-control expert. Assess the following record. 【Medical record】 Chief complaint: {cc} Present illness: {hpi} 【Quality-control result】""" )]) ``` ### Technical Analysis The `cc` and `hpi` values originate from an input medical record and must therefore be treated as untrusted data. They are concatenated into user-role messages containing executable natural-language instructions without a strong boundary between the task instructions and record content. A malicious or compromised record can contain text that instructs the model to ignore the preceding task, emit a chosen result, reproduce misleading output, or reinterpret record content as higher-priority directions. Although delimiters can improve clarity, delimiters alone do not provide a security boundary for an LLM. The intermedi ...[truncated 2125 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every medical-record field as untrusted content and explicitly state in a system message that instructions appearing inside record data must never be followed. 2. Put stable application policy in a system-role message rather than combining instructions and record data in one user-role message. 3. Use clearly identified structured data fields and encode the record as JSON, while recognizing that formatting alone does not eliminate prompt injection. 4. Require strict structured responses, for example: ```json {"classification":"yes"} ``` Validate the response against an allowlisted schema and reject all extra text or unexpected values. 5. Replace substring tests with exact enum comparisons after normalization. Fail closed or route uncertain responses to manual review. 6. Consider replacing the initial LLM checks with deterministic terminology and duration parsing where practical. 7. Apply input length limits and reject control patterns or irrelevant instruction-like content when compatible with legitimate medical records. 8. Add adversarial tests covering embedded instructions, delimiter injection, role-like text, example-block termination, multilingual attacks, and unexpected model responses. 9. Keep a qualified human reviewer in the decision path, particularly when the model returns an ambiguous result or when input-integrity checks fail. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documentation describes capabilities that read local files, write output files, and send medical record content to an external network API, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates a policy and containment gap: a caller or platform cannot clearly restrict what the skill is allowed to access or where sensitive data may be sent, which is especially risky given the medical-data context and the external LLM call.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This code file contains user-facing natural-language strings entirely in Chinese, including the module docstring, CLI description, and argument help text. Under the policy, forcing a specific language without user opt-in or a documented justification is a natural-language policy violation.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The code sends chief complaint and history of present illness from outpatient medical records to an external LLM endpoint over the network, but this file contains no consent gate, disclosure, minimization, or configuration that restricts use to an approved internal deployment. Because the data is medical in nature, transmitting it to a third-party service can create serious privacy, compliance, and data-governance risk if users are unaware or the endpoint is not contractually and technically approved for PHI handling.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script sends raw outpatient medical record text to an LLM service via run_qc using a configurable base URL, but it provides no explicit consent prompt, warning, masking, or transport/privacy notice to the operator. Because medical records commonly contain highly sensitive personal and health data, silent transmission to a model endpoint creates a real confidentiality and compliance risk, especially if the endpoint is remote, misconfigured, or logs requests.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The module docstring, prompts, parsing logic, and printed outputs are all fixed in Chinese, and record parsing depends on Chinese field labels such as '主诉' and '现病史'. This enforces a specific language/locale behavior with no opt-in or alternative, which matches the language-policy concern described by the rule.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The module docstring is written entirely in Chinese, and the CLI argument descriptions are also presented only in Chinese. This imposes a single language/locale without any user opt-in or alternative, which matches the language policy violation criteria.

Static analysis

No suspicious patterns detected.