Back to skill

Security audit

unisound-surgery-review

Security checks for vulnerabilities and agentic risk

Overview

This medical-record review skill has a legitimate purpose, but it can send sensitive records and bearer keys to configurable network endpoints and can optionally write plaintext prepared records to disk.

Review before installing in any environment with real patient data. Use only approved HTTPS guideline and LLM endpoints, avoid caller-supplied --base values, use --no-llm when records cannot leave the host, redact identifiers before input, and do not enable --save-prepared unless plaintext local retention is explicitly approved and protected.

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/surgery_review.py:255
Finding
Caller-Controlled LLM Endpoint Can Receive Medical Records and Bearer Credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/surgery_review.py:255-276`, `scripts/surgery_review.py:451-465`, and `scripts/surgery_review.py:873-879`; endpoint exposed through `scripts/run.py:153,183` **Vulnerability Type**: Unrestricted transmission of sensitive information and credentials to a caller-controlled network endpoint **Risk Level**: High ### Vulnerable Code The LLM client sends the prompt and bearer credential to the configured model URL: ```python def complete(self, prompt: str, model_name: str | None = None) -> str: selected_model = model_name or self._settings.default_model model_config = self._settings.models.get(selected_model) if model_config is None: raise ValueError(f"未找到模型配置: {selected_model}") payload = { "model": model_config.model_id or selected_model, "messages": [{"role": "user", "content": prompt}], "temperature": model_config.temperature, } if model_config.type == "openai_compatible": return self._post_chat( url=f"{model_config.base_url.rstrip('/')}/chat/completions", payload=payload, headers={"Authorization": f"Bearer {model_config.api_key}"}, ) raise ValueError(f"不支持的模型类型: {model_config.type}") def _post_chat(self, url: str, payload: dict[str, Any], headers: dict[str, str]) -> str: body = json.dumps(payload, ensure_ascii=False).encode("utf-8") req = request.Request( url=url, data=body, headers={"Content-Type": "application/json", **{key: value for key, value in headers.items() if value}}, method="POST", ) opener = request.urlopen(req) if not self._settings.timeout else request.urlopen(req, timeout=self._settings.timeout) ``` Selected medical-document contents are embedded directly in that prompt: ```python evidence_text = "\n\n".join( ( f"【文书#{doc['doc_index']} / {doc['doc_type']} / {doc['file_name']}】\n" f"{doc['content']}" ) ...[truncated 3157 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove caller-controlled model endpoint overrides in production and use a fixed, trusted service URL. 2. If endpoint configurability is operationally required, enforce: - HTTPS only. - An exact allowlist of approved hostnames and ports. - Proper URL parsing rather than prefix or substring checks. - Rejection of embedded credentials, fragments, nonstandard schemes, loopback addresses, link-local addresses, and unapproved private-network destinations. 3. Disable automatic cross-origin redirects or validate every redirect destination against the same allowlist before forwarding credentials or request content. 4. Bind each credential to its approved destination. Never attach the internal model credential to an arbitrary caller-supplied URL. 5. Minimize medical evidence before transmission. Send only the necessary excerpts and remove patient identifiers where possible. 6. Require an explicit data-transfer opt-in instead of enabling LLM transmission by default when handling medical records. 7. Reject an empty `appkey` when LLM use is enabled and avoid exposing credentials in logs or exception output. 8. Add tests proving that HTTP URLs, unapproved domains, alternate ports, redirect-based bypasses, and malformed URLs are rejected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:90
Finding
Optional Debug Feature Persists Complete Medical Records as Plaintext<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:90-95` and `scripts/run.py:177-179` **Vulnerability Type**: Plaintext persistence of sensitive medical information **Risk Level**: Medium ### Vulnerable Code The debugging feature serializes the preprocessed medical documents to an ordinary plaintext file: ```python def save_prepared(payload: dict[str, Any], output_json: str, input_path: Path) -> None: save_dir = Path(output_json).parent if output_json else ICD_DRG_DIR / "runs" / "surgery-review" save_dir.mkdir(parents=True, exist_ok=True) prepared_path = save_dir / f"{input_path.stem}.prepared.txt" prepared_path.write_text(record_to_prepared_text(payload), encoding="utf-8") print(f"Prepared text saved to: {prepared_path}", file=sys.stderr) ``` It is invoked when the optional command-line flag is supplied: ```python payload = load_record_payload(input_path, args.input_type, args.encoding, args.sheet) if args.save_prepared: save_prepared(payload, args.output_json or args.output, input_path) surgeries = merge_candidate_args(args.surgeries_json, args.surgery, default_role="other") ``` ### Technical Analysis `record_to_prepared_text` concatenates the contents of the record's documents, and `save_prepared` writes that content unencrypted to a persistent `.prepared.txt` file. The code does not explicitly set restrictive file permissions, enforce a retention period, redact patient identifiers, or remove the file after processing. The feature is opt-in and documented as a debugging option, which reduces exploitability. Nevertheless, medical records are highly sensitive, and writing complete intermediate data to a normal file creates a confidentiality risk beyond the minimum processing necessary for the coding-review operation. The behavior also conflicts with the broad statement in `SKILL.md` that the Skill does not persist request bodies or intermediate results locally. Although the later CLI documentation mentions `- ...[truncated 1526 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the prepared-record persistence option from production builds unless it is operationally essential. 2. If retained, require explicit confirmation that sensitive medical data will be written to disk. 3. Create files with owner-only permissions, such as mode `0600`, and use a directory with mode `0700`. 4. Refuse to write into shared, world-readable, or otherwise unsafe directories. 5. Redact direct identifiers and retain only the minimum excerpts needed for debugging. 6. Implement a documented retention period and reliable deletion mechanism. 7. Avoid including prepared-record files in backups, telemetry, build artifacts, or support bundles. 8. Clearly document the exception in the privacy statement so it does not claim that no intermediate data is persisted when `--save-prepared` is enabled. 9. Consider encrypted temporary storage when persistence is unavoidable, with keys managed separately from the stored files. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tainted flow: 'req' from os.getenv (line 270, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers["Authorization"] = f"Bearer {self._settings.api_key}"
        req = request.Request(url=url, headers=headers, method="GET")
        try:
            with request.urlopen(req, timeout=self._settings.timeout) as response:
                return json.loads(response.read().decode("utf-8"))
        except error.HTTPError as exc:
            if exc.code == 404:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.getenv (line 270, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json", **{key: value for key, value in headers.items() if value}},
            method="POST",
        )
        opener = request.urlopen(req) if not self._settings.timeout else request.urlopen(req, timeout=self._settings.timeout)
        with opener as response:
            response_payload = json.loads(response.read().decode("utf-8"))
        choices = response_payload.get("choices") or []
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Ae1

High
Category
analysis-evasion
Content
- `scripts/run.py` 是唯一对外入口,复用 `scripts/surgery_review.py` 的核心审核逻辑。
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- 发布目录只保留 `SKILL.md`、`_meta.json`、`scripts/`;示例输入、运行输出、自测脚本放在 skill 包外。
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
def load_guideline_api_settings_from_env() -> GuidelineApiSettings:
    return GuidelineApiSettings(
        base_url=_required_env("GUIDELINE_API_BASE"),
        api_key=os.getenv("GUIDELINE_API_KEY", ""),
        timeout=_env_int("GUIDELINE_API_TIMEOUT", 30),
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The code sends selected medical-record document content and audit prompts to a configurable LLM endpoint, which is a material data exfiltration path not disclosed by the manifest. Because evidence docs can contain protected health information and narrative clinical details, transmitting them to external model providers creates significant confidentiality and compliance exposure.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
for candidate in surgery_code_lookup_candidates(surgery_code):
        guideline = guideline_repository.find_guideline_by_code(candidate)
        if guideline is not None:
            return guideline
    return None
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
"source": "列出对应的原文摘录(含文书类型/页码/关键句)"
}}
    """
    return prompt


def _normalize_text(value: str) -> str:
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill transmits medical-record content to external services without any user-facing warning, consent mechanism, or disclosure at the point of use. For clinical documents, silent outbound transfer increases the risk of privacy violations, regulatory noncompliance, and misuse of sensitive patient data.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
Caller-controlled base URL and app key allow the skill to send case data to arbitrary attacker-chosen endpoints under the guise of LLM review. In a medical-record auditing skill, this greatly amplifies exfiltration risk because untrusted callers can redirect sensitive case content outside approved infrastructure.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill documentation advertises capabilities that imply access to environment variables, local file read/write, and outbound network calls, but it does not declare any explicit tool scope such as permissions or allowed-tools. In a medical-record review skill, this is risky because sensitive patient data, API keys, and prepared record text could be accessed or exfiltrated through undeclared channels, and operators cannot enforce least-privilege from the manifest alone.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code sends the full medical record payload and an authentication key to `review_surgery_payload`, together with configurable `base` and `model` parameters, implying transmission of PHI to an external or internal LLM endpoint. In a medical-coding review skill, this is especially sensitive because records may contain extensive patient data, and the endpoint is user-selectable via CLI, creating data disclosure and secret-handling risk if misconfigured or pointed to an untrusted service.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script can persist preprocessed medical-record text to disk via `--save-prepared`, and the generated content includes concatenated document contents from the patient record. Because this skill processes highly sensitive healthcare data, writing plaintext debug artifacts to local storage materially increases confidentiality risk through accidental retention, backup exposure, shared-host access, or later exfiltration.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The skill performs remote guideline retrieval over HTTP(S), which introduces undisclosed external data flows into a medical-coding review workflow that appears local from the manifest. In healthcare contexts, even metadata such as procedure codes and identifiers can be sensitive, and hidden network dependencies increase privacy and supply-chain risk.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
Authenticated remote guideline lookup occurs without user disclosure, creating a hidden external dependency and undisclosed data transfer channel. While less severe than full document transmission, sending procedure-related context to remote services can still expose sensitive operational or patient-linked information.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The prompt and expected response values force Chinese-language interaction and output, and multiple user-facing errors/help strings elsewhere in the file are also Chinese-only. The file does not offer user language selection or explain that the skill is intentionally limited to a Chinese-language regional workflow.

Static analysis

No suspicious patterns detected.