Back to skill

Security audit

unisound-operation-record

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent medical-record generation purpose, but it handles sensitive clinical text and credentials with an overly configurable remote endpoint.

Review this skill before installing in any healthcare or PHI-adjacent workflow. Use it only with de-identified records, keep --base pinned to an approved internal HTTPS endpoint, protect the appkey outside command history where possible, and require clinician review of generated records before use.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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:183
Finding
Arbitrary LLM Endpoint Can Expose API Credentials and Sensitive Medical Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:183-194, 211-222` **Vulnerability Type**: Unrestricted transmission of credentials and sensitive data to a user-controlled endpoint **Risk Level**: High ### Vulnerable Code ```python def call_llm(prompt: str, *, base: str, model: str, appkey: str, timeout: int) -> str: url = f"{base.rstrip('/')}/chat/completions" headers = {"Authorization": f"Bearer {appkey}"} if appkey else {} payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0, } response = _http_post(url, payload, headers, timeout=timeout) try: return str(response["choices"][0]["message"]["content"]).strip() except (KeyError, IndexError, TypeError) as exc: raise RuntimeError(f"Unexpected LLM response: {response}") from exc ``` ```python parser.add_argument( "--base", default=DEFAULT_LLM_BASE, help=f"内部大模型 base URL(默认:{DEFAULT_LLM_BASE})。", ) parser.add_argument("--model", default=DEFAULT_LLM_MODEL, help=f"模型名称(默认:{DEFAULT_LLM_MODEL})。") parser.add_argument("--timeout", type=int, default=0, help="HTTP 超时秒数;0 表示一直等待(默认:0)。") parser.add_argument("--appkey", required=True, help="必须传入。内部医疗大模型鉴权 key,使用 Bearer 方式认证。") ``` ### Technical Analysis The `--base` argument accepts an arbitrary URL and is used directly to construct the LLM request destination. The code performs no validation of the URL scheme, destination hostname, port, or origin before attaching the Bearer credential and sending the complete generated prompt. The prompt can contain sensitive medical-record content. Consequently, anyone able to control the command-line arguments or the workflow configuration can redirect both the medical data and the API credential to an attacker-controlled server. The unrestricted URL may also permit requests to network services reachable from the execution environment. The vulnerability is especially significant because the ...[truncated 1620 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--base` override from production deployments when only one approved service is required. 2. If configurability is necessary, enforce an explicit allowlist of approved HTTPS hostnames and ports. 3. Parse the URL with `urllib.parse.urlsplit` and reject: - Non-HTTPS schemes. - Embedded usernames or passwords. - Unapproved hostnames and ports. - IP literals and loopback, link-local, private, or reserved addresses unless explicitly required. 4. Bind the Authorization header to the approved origin. Never forward it when the destination origin differs. 5. Disable automatic cross-origin redirects or revalidate every redirect target before following it. 6. Store the API credential in a protected secret manager or environment-based secret channel rather than exposing it in process arguments. 7. Apply outbound firewall or proxy controls so the process can reach only the approved LLM service. 8. Minimize and de-identify medical content before transmission, and establish explicit remote retention and deletion controls. 9. Add tests confirming that malicious, plaintext, local, and unapproved endpoint URLs are rejected. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
scripts/run.py:111
Finding
Untrusted Input Can Replace or Override Clinical Record Generation Instructions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:111-126, 183-190` **Vulnerability Type**: Prompt injection through unrestricted prompt passthrough and missing trust separation **Risk Level**: Medium ### Vulnerable Code ```python def build_prompt(payload: dict[str, Any]) -> str: raw_prompt = str(payload.get("prompt") or "").strip() if raw_prompt: return raw_prompt records = payload.get("records") if records: return _wrap_operation_prompt(_format_records_body(records)) record_text = str(payload.get("record") or payload.get("text") or payload.get("content") or "").strip() if not record_text.strip(): raise ValueError("输入缺少 records 或 prompt") return _wrap_operation_prompt(record_text) ``` ```python def call_llm(prompt: str, *, base: str, model: str, appkey: str, timeout: int) -> str: url = f"{base.rstrip('/')}/chat/completions" headers = {"Authorization": f"Bearer {appkey}"} if appkey else {} payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0, } response = _http_post(url, payload, headers, timeout=timeout) ``` ### Technical Analysis When an input JSON object contains a nonempty `prompt` property, `build_prompt()` returns that value directly. The fixed operation-record instructions are omitted entirely. This permits input data to redefine the model's task rather than merely provide clinical source material. For ordinary records, the fixed instructions and untrusted medical content are still combined into one `user` message. No trusted system-level instruction, structured boundary, or explicit injection-resistant handling separates policy from source data. Instructions embedded in uploaded records can therefore compete with the intended task instructions. The output is accepted as arbitrary text without schema validation or checks that all 14 required fields are present and grounded in the supplied ...[truncated 1763 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove unrestricted top-level `prompt` support from production input, or restrict it to trusted administrative callers. 2. Send immutable generation policy in a system or developer message and send medical source material separately as untrusted data. 3. Clearly delimit source records and instruct the model that text inside the record is evidence only and must never be treated as instructions. 4. Validate input against a strict schema: - Permit only expected record fields. - Require `records` to be a list of objects. - Require `section` and `content` to be strings. - Reject unexpected control fields such as `prompt` for untrusted callers. 5. Require structured model output with exactly the 14 documented fields. 6. Validate that mandatory fields exist and reject additional instructions, commentary, or malformed responses. 7. Where feasible, compare extracted names, dates, diagnoses, and procedures against source text and flag unsupported claims. 8. Mark generated records as drafts and require review and approval by a qualified clinician before archival or operational use. 9. Add adversarial tests covering instructions embedded in top-level prompts, section names, record content, and imported documents. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (6)

Ssd 1

High
Confidence
94% confidence
Finding
`build_prompt()` allows a user-provided `prompt` field to completely bypass the fixed medical-record instruction template, meaning any caller can replace the guardrails that require faithful extraction and non-fabrication. In this skill, that increases the risk of prompt injection, unsafe transformations of sensitive records, and output manipulation that could generate misleading or clinically dangerous surgical documentation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill performs file reads, optional file writes, and outbound network requests to an internal model API, but the manifest does not declare any explicit tool scope or permission boundaries. This creates a governance gap: operators and policy engines cannot reliably restrict what the skill is allowed to access, which increases the chance of unintended data exposure, especially because the skill handles sensitive medical records.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The natural-language description and usage instructions are presented exclusively in Chinese, which effectively forces a specific language for users. The file does not offer an opt-in language choice or explain that the skill is restricted to a Chinese-speaking or region-specific environment.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script sends potentially sensitive medical-record content to a remote HTTPS LLM endpoint via `call_llm()` without any explicit consent flow, privacy warning, minimization step, or enforcement that the endpoint is strictly internal and approved for PHI handling. In a healthcare context, transmitting raw surgical and diagnostic records can expose protected health information and create compliance, confidentiality, and third-party processing risks even if the transport is encrypted.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
This JSON record content is entirely written in Chinese and provides no indication that language selection is configurable or user-selected. Under the stated policy, forcing a specific language without opt-in can be a natural-language policy violation unless the locale restriction is explicitly documented and justified.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The natural-language instructions, CLI descriptions, and core prompt are all hard-coded in Chinese, and the skill does not indicate that language is configurable or limited for a documented regional/compliance reason. This can violate language/locale policy when users are not given an opt-in choice.

Static analysis

No suspicious patterns detected.