Back to skill

Security audit

unisound-clinical-trial-statistics

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent clinical-statistics purpose, but it also sends sensitive trial summaries to a remote model and can execute an unverified external preprocessor.

Install only if you trust the remote medical-model endpoint, are authorized to send trial-derived data there, and can run the skill in a contained environment. Avoid untrusted Office/PDF/image inputs, prefer JSON/CSV/XLSX, and review any AI-generated clinical interpretation with a qualified statistician.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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
Findings (2)

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/run.py:263
Finding
Unauthenticated External Preprocessor Module Execution<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py`, lines 263-270 **Vulnerability Type**: Dynamic execution of an unverified module outside the skill package **Risk Level**: High ### Vulnerable Code ```python _shared_dir = Path(__file__).resolve().parent.parents[3] / "_shared" / "doc-preprocess" / "scripts" if not _shared_dir.exists(): print(f"ERROR: 无法读取输入文件,本地预处理失败且 _shared/doc-preprocess 不可用。原因:{exc}", file=sys.stderr) return 1 import importlib.util as _iu _spec = _iu.spec_from_file_location("_shared_preprocess", _shared_dir / "preprocess.py") _sp = _iu.module_from_spec(_spec) _spec.loader.exec_module(_sp) ``` ### Technical Analysis When local preprocessing raises `PreprocessError`, the application constructs a path outside the skill package and dynamically loads `preprocess.py` from that location. It verifies only that the shared directory exists; it does not verify the module's cryptographic hash, ownership, permissions, provenance, or expected contents. Calling `exec_module()` executes all top-level Python statements in the selected file. Consequently, if another user, process, package, or compromised deployment component can create or replace the external `preprocess.py`, that party can cause arbitrary Python code to run when the fallback path is reached. The exact resolved shared directory depends on the installation layout. Exploitation therefore requires the resolved location, or one of its controlling parent directories, to be writable or otherwise replaceable by the attacker. ### Attack Path 1. The attacker determines the external path generated from `Path(__file__).resolve().parent.parents[3]`. 2. The attacker obtains write access to the resulting `_shared/doc-preprocess/scripts` directory or can replace its `preprocess.py`. 3. The attacker places a malicious `preprocess.py` at that location. Malicious top-level code is sufficient; no preprocessor function needs to be called. 4. The attacker or a victim invokes th ...[truncated 1101 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the external dynamic-import fallback and package all required preprocessing code inside the reviewed skill. 2. If a shared component is required, import it as a properly installed, version-pinned package from a trusted environment rather than loading a file from a computed path. 3. Pin and verify a cryptographic digest or signed manifest before loading shared code. 4. Validate the resolved path with `Path.resolve()` and require it to reside beneath an explicitly configured, administrator-controlled directory. 5. Reject symbolic links and verify that the file and every controlling parent directory are owned by a trusted account and are not group- or world-writable. 6. Apply least privilege to the skill process and isolate it with filesystem and network sandboxing so compromise of a preprocessor has limited impact. 7. Fail closed when preprocessing is unavailable instead of automatically executing an unverified alternative implementation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:128
Finding
Prompt Injection Through Attacker-Controlled Clinical Trial Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py`, lines 128-145 **Vulnerability Type**: Untrusted data embedded directly in an LLM instruction prompt **Risk Level**: Medium ### Vulnerable Code ```python user_prompt = f"""请解读以下临床试验统计结果: 试验ID:{data.get('trial_id', '')} 分析集:{data.get('population', '')} 分组字段:{group_field} 组别:{json.dumps(group_order, ensure_ascii=False)} 总样本量:{len(records)} 统计结果: ```json {json.dumps(statistics, ensure_ascii=False, indent=2)} ``` 请解读各终点指标的统计结果,分析组间差异的临床意义,说明局限性。""" text = _call_llm(SYSTEM_PROMPT, user_prompt, appkey) ``` ### Technical Analysis The trial ID, population, grouping field, group labels, endpoint names, and derived statistical structure originate from user-controlled input. These values are interpolated directly into the same natural-language message that instructs the remote model. JSON serialization protects the surrounding Python and JSON syntax, but it does not establish a security boundary for an LLM. A field can contain text instructing the model to ignore the intended task, omit limitations or disclaimers, fabricate conclusions, or emit attacker-selected content. The system prompt states the desired analysis but does not explicitly identify the embedded fields as untrusted data, and the generated response is returned without validation. This flaw affects the integrity and reliability of the AI-generated interpretation. The descriptive statistics in the structured `data` result are calculated locally and are not directly modified by the model. ### Attack Path 1. The attacker supplies a supported input file containing a crafted `trial_id`, `population`, group label, grouping-field name, or endpoint-field name. 2. The crafted value includes model-facing instructions, such as a request to disregard prior requirements and provide a predetermined clinical conclusion. 3. The application incorporates the value into `user_prompt` alongside its legitimate analytical instructions. 4. `_call_llm ...[truncated 988 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every value derived from an uploaded file as untrusted model input. 2. Validate field names and metadata with strict allowlists, character restrictions, and reasonable length limits. Reject control characters and unexpectedly long labels. 3. Keep instructions and data clearly separated. Prefer a structured model interface or tool schema where available. 4. Place serialized data inside explicit delimiters and add a high-priority instruction stating that content inside those delimiters is data only and must never be interpreted as instructions. 5. Avoid relying on prompt wording as the sole defense. Validate the generated response after inference, including the required disclaimer and prohibitions on unsupported clinical claims. 6. Generate key tables and factual statements deterministically from the locally calculated statistics rather than asking the model to reproduce them. 7. Clearly distinguish AI-generated commentary from verified statistical output and require professional review before the result is used for clinical, regulatory, or safety decisions. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose is narrow clinical-trial statistics, but the documented behavior includes broad document ingestion, conversion, OCR, and text extraction across many formats. That mismatch widens the attack surface far beyond simple statistical processing, enabling risky parsing of untrusted files and use of external utilities that may expose the runtime to malformed-document exploits or unauthorized processing of unrelated sensitive content.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises capabilities that imply file I/O, shell execution, environment access, and network access, but it does not declare any explicit tool scope or permission boundaries. In a clinical-trial context, this creates an opaque trust boundary: sensitive trial data may be read, transformed, transmitted, or written without clear least-privilege constraints, increasing the chance of unintended exfiltration or dangerous tool use.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly sends data to a model API but does not provide a clear privacy or data-handling warning, despite processing clinical-trial records that may include highly sensitive regulated data. Users may unknowingly transmit confidential subject, protocol, or endpoint information to a remote service without understanding retention, jurisdiction, or access implications.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The manifest describes a capability for clinical trial data statistics, specifically referencing descriptive statistics and group comparison. This file instead implements broad ingestion and text/table extraction for pdf/doc/docx/xls/xlsx/csv/txt/json/images, which is a generic document-processing capability rather than statistical analysis support itself.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The skill introduces generic document-conversion and OCR capability via external binaries, which materially increases attack surface for a statistics-focused tool. In this context, broader ingestion of arbitrary office, PDF, and image files is more dangerous because users may submit untrusted research artifacts, causing the host to process attacker-controlled documents with heavyweight parsers.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not office_bin:
        raise PreprocessError("libreoffice/soffice not found for office document conversion.")
    with tempfile.TemporaryDirectory(prefix="med-skill-preprocess-") as tmp_dir:
        proc = subprocess.run(
            [office_bin, "--headless", "--convert-to", "txt:Text", "--outdir", tmp_dir, str(path)],
            stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False,
        )
Confidence
79% confidence
Finding
This code feeds attacker-supplied office documents into LibreOffice conversion, which has a long history of parser and document-processing attack surface. Even without shell injection, invoking a large external parser on untrusted files can expose the host to denial-of-service or exploitation of vulnerabilities in the converter, and this preprocessing utility broadens the skill beyond its core statistics purpose.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not office_bin:
        raise PreprocessError("libreoffice/soffice not found for xls conversion.")
    with tempfile.TemporaryDirectory(prefix="med-skill-preprocess-") as tmp_dir:
        proc = subprocess.run(
            [office_bin, "--headless",
             "--convert-to", "csv:Text - txt - csv (StarCalc):44,34,76,1",
             "--outdir", tmp_dir, str(path)],
Confidence
79% confidence
Finding
This invokes LibreOffice on untrusted XLS files for conversion, exposing the environment to a complex external file parser. The main risk is not command injection but unsafe processing of adversarial documents, which can lead to crashes, resource exhaustion, or exploitation of converter vulnerabilities.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
pass
    pdf_to_text = shutil_which("pdftotext")
    if pdf_to_text:
        proc = subprocess.run(
            [pdf_to_text, "-layout", str(path), "-"],
            stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False,
        )
Confidence
70% confidence
Finding
Running pdftotext on untrusted PDFs creates exposure to parser bugs and decompression/resource-exhaustion issues in external PDF tooling. The command is not shell-injectable as written, but processing arbitrary attacker-controlled PDFs still expands the attack surface of the skill host.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The OCR path automatically sets Tesseract language selection to "chi_sim+eng" when those language packs are available. This imposes a specific language/locale preference in behavior without any user opt-in or documented justification, which matches the language-policy violation criteria.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = [tesseract_bin, str(path), "stdout"]
    if lang_arg:
        cmd.extend(["-l", lang_arg])
    proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
    if proc.returncode != 0 or not proc.stdout.strip():
        raise PreprocessError(f"Image OCR failed: {proc.stderr.strip() or 'no text returned'}")
    return proc.stdout
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def detect_tesseract_langs(tesseract_bin: str) -> Sequence[str]:
    proc = subprocess.run(
        [tesseract_bin, "--list-langs"],
        stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False,
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
Natural-language strings in the module description, CLI description, and system prompt are written to require Chinese-language behavior, including a fixed instruction to output Markdown in that language context. The file does not provide any language or locale opt-in/out mechanism, which can violate language-choice policy for general-purpose skills.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The code performs a remote chat-completions API call that is not obviously required for the core task of descriptive statistics computation. This expands the attack surface and creates data egress, dependency, and availability risks that are out of scope for a purely local statistics utility.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill sends trial-derived statistical results and metadata (for example trial_id, population, group labels, sample size, and endpoint summaries) to an external LLM service for interpretation, despite presenting itself as a local clinical-trial statistics helper. In a clinical context, even derived data can be sensitive or regulated, and this creates a confidentiality and compliance risk if operators assume processing remains local.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Clinical trial data and metadata are embedded into a prompt and transmitted externally without any user-facing warning in this file. In regulated medical/pharma workflows, undisclosed external sharing can violate confidentiality expectations, contractual controls, or data-handling requirements even if the transmitted content is partially aggregated.

Ssd 1

Medium
Confidence
91% confidence
Finding
Untrusted fields such as trial_id, population, and potentially group labels are interpolated directly into the LLM prompt, allowing an attacker to embed natural-language instructions that can influence the model's interpretation output. Although this does not lead to code execution here, it can corrupt clinical analysis narratives, suppress limitations, or produce misleading conclusions in a sensitive medical decision-support context.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/run.py:275