Back to skill

Security audit

unisound-clinical-trial-design

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches a clinical-trial design helper, but it has under-disclosed sensitive data handling and a hidden fallback that can execute an external local Python module outside the reviewed package.

Install only in an environment where the remote medical-model endpoint, credential handling, and document-processing tools are approved for clinical or proprietary R&D data. Prefer JSON input for sensitive work, avoid passing real appkeys on the command line when possible, sandbox untrusted documents, and review or remove the external _shared fallback before relying on the skill.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:187
Finding
API Credential Exposure Through Command-Line Arguments## Vulnerability Details **File Location**: `scripts/run.py:187` **Additional Documentation Location**: `SKILL.md:81`, `SKILL.md:143` **Vulnerability Type**: Insecure credential handling **Risk Level**: Medium ### Vulnerable Code ```python parser.add_argument("--appkey", required=True, help="内部医疗大模型鉴权key(必填)") ``` The documented invocation also instructs users to place the credential directly in the command: ```bash python3 scripts/run.py --input input.json --output output.json --appkey YOUR_KEY ``` ### Technical Analysis The application requires the bearer credential to be supplied as a command-line argument. Command-line arguments may be exposed through: - Operating-system process listings such as `ps`. - Shell history files. - Process monitoring and observability systems. - CI/CD job logs. - Container or workload metadata. - Diagnostic reports that capture process arguments. HTTPS protects the credential while it is transmitted to the API, but it does not mitigate disclosure through local process metadata or command history. ### Attack Path 1. A user follows the documented invocation and passes a valid credential using `--appkey`. 2. The shell records the command in its history, or the operating system exposes it in the process argument list while the program is running. 3. A local user, administrator, monitoring service, or party with access to execution logs reads the argument. 4. The party extracts the credential and submits unauthorized requests to the medical-model API. ### Impact Assessment Exploitation does not directly grant operating-system privileges. It can grant access equivalent to the compromised API credential, including unauthorized consumption of API quota, submission of model requests, and access to any capabilities associated with that credential. The impact is limited by the permissions, expiration, rate limits, and billing scope assigned to the exposed credential.
Remediation
## Remediation Suggestions - Read the credential from a protected environment variable or operating-system secret store rather than a command-line argument. - Support secure standard-input entry, preferably without terminal echo, for interactive execution. - Integrate with the deployment platform's native secret-management mechanism. - Remove the credential-bearing command example from `SKILL.md`. - If `--appkey` must remain for backward compatibility, clearly mark it as deprecated and insecure. - Ensure application and orchestration logs redact authorization credentials. - Use short-lived, narrowly scoped credentials and provide a rotation procedure.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:87
Finding
LLM Prompt Injection Through Untrusted Clinical-Trial Fields## Vulnerability Details **File Location**: `scripts/run.py:87-98` **Related Parsing Location**: `scripts/run.py:156-165` **Vulnerability Type**: Untrusted input incorporated into an instruction-bearing LLM prompt **Risk Level**: Medium ### Vulnerable Code ```python user_prompt = f"""请审阅以下临床试验设计: 适应症:{indication} 干预措施:{intervention} 试验分期:{phase} 研究目的:{objective} 目标人群:{population} 对照方式:{control} 随机化方式:{randomization} 盲法:{blinding} 终点列表:{json.dumps(endpoints, ensure_ascii=False)} 访视安排:{json.dumps(visits, ensure_ascii=False)} 请审阅设计合理性,指出潜在偏倚,给出优化建议。""" ``` Input values parsed from text are preserved without semantic validation: ```python for line in lines: match = pattern.match(line) if not match: continue key = header_map.get(normalize_header(match.group(1).strip())) if key is None: continue value_str = match.group(2).strip() try: result[key] = json.loads(value_str) except (json.JSONDecodeError, ValueError): result[key] = value_str ``` ### Technical Analysis Values originating from untrusted JSON, text, document, spreadsheet, PDF, or OCR input are interpolated directly into the LLM user prompt. The prompt does not clearly isolate these values as inert data, and the system prompt does not explicitly prohibit following instructions embedded inside clinical-trial fields. An attacker can place model instructions inside a recognized field such as `indication`, `objective`, `population`, or `endpoints`. Because both the legitimate request and attacker-controlled directives appear in the same user message, the model may treat the embedded content as instructions rather than clinical data. The model response is subsequently returned as the `text` field without task-specific response validation: ```python text = _call_llm(SYSTEM_PROMPT, user_prompt, appkey) ``` This issue affects the integrity and reliability of generated clinical-trial guidance. There is n ...[truncated 1274 chars]
Remediation
## Remediation Suggestions - Serialize all clinical-trial fields into a clearly delimited data block, preferably a strict JSON object. - Add a system-level instruction stating that content inside input fields is untrusted data and that embedded instructions must never be followed. - Apply type, length, character, and structural limits to every accepted field. - Reject unexpectedly large or instruction-like values where appropriate. - Request a structured model response and validate it against an explicit schema before returning it. - Verify that the response remains within the clinical-trial review task and contains the required safety disclaimer. - Treat output as untrusted content when rendering it; do not allow generated Markdown to trigger privileged actions. - For high-impact use, require qualified human review before recommendations are used in a protocol.

T07 · Tool Hijacking and Spoofing

Warning
Location
scripts/run.py:213
Finding
Unauthenticated Execution of an External Shared Python Module## Vulnerability Details **File Location**: `scripts/run.py:213-224` **Vulnerability Type**: External local module hijacking **Risk Level**: Medium ### Vulnerable Code ```python except PreprocessError as exc: # 回退到 _shared/doc-preprocess 尝试处理 try: _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) input_type = _sp.detect_input_type(input_path, args.input_type) ``` ### Technical Analysis When local preprocessing raises `PreprocessError`, the application derives a path outside the audited skill package and executes a `preprocess.py` file from that location using `exec_module()`. No cryptographic integrity check, trusted ownership check, permission check, package signature, or pinned version validation is performed before execution. Python executes all top-level statements in the external file as soon as `exec_module()` is called. The actual resolved path also depends on the skill's installation depth. This makes the trust boundary implicit and allows a file outside the reviewed package to influence runtime behavior. Exploitation requires the attacker to create or modify the resolved external module. Therefore, practical severity depends on filesystem ownership and permissions in the deployment environment. ### Attack Path 1. An attacker gains write access to the resolved `_shared/doc-preprocess/scripts` directory or replaces its `preprocess.py`. 2. The attacker inserts arbitrary Python statements at module scope. 3. The attacker or a victim supplies input that cause ...[truncated 1057 chars]
Remediation
## Remediation Suggestions - Remove dynamic loading from a path outside the skill package. - Package the fallback preprocessor as a normal, version-pinned dependency and import it through the standard module system. - Prefer failing closed when the packaged preprocessor cannot handle an input. - If shared loading is operationally required, configure an explicit trusted path rather than deriving it from parent-directory depth. - Verify the external file against a pinned cryptographic hash or trusted signature before importing it. - Check that the file and all parent directories are owned by a trusted account and are not writable by untrusted users. - Run the skill under a dedicated least-privileged account with restricted filesystem and network access. - Document the shared component and include it in future security reviews and release integrity checks.
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose is clinical trial design assistance, but the skill also supports broad document ingestion, OCR, format conversion, and external tool execution. This mismatch is dangerous because users may trust it as a narrow medical-design helper while it actually processes arbitrary files and may invoke shell-based converters, increasing attack surface for malicious documents, unexpected data access, or unsafe parser/tool chains.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill declares capabilities that imply file access, shell execution, environment access, and network use, but it does not define any explicit tool scope such as permissions or allowed-tools. That creates an unnecessary trust gap: a user or host system cannot easily determine or constrain what the skill may access when processing untrusted documents or invoking external services.

Natural-Language Policy Violations

Medium
Confidence
78% confidence
Finding
The entire skill description is written as Chinese-only instructions and specifies OCR tooling with `chi_sim+eng`, but there is no statement that language is user-selectable or that the skill is intentionally limited to a China-specific user base. Per the policy, forcing a specific language/locale without opt-in can be a natural-language policy violation.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill mandates sending user-provided clinical trial content to an externalized internal API endpoint, yet the description does not prominently warn that potentially sensitive research, patient-related, or proprietary protocol information will be transmitted off-box. In a pharmaceutical and clinical context, this raises confidentiality, compliance, and data-governance risks even if the endpoint is organizationally controlled.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The manifest describes a skill for assisting pharmaceutical clinical trial design, but this file implements a broad, reusable document-ingestion utility for PDFs, Office docs, spreadsheets, JSON, text, and images. That preprocessing may be useful as a supporting detail, but the code itself is not specialized to protocol or trial-design tasks and materially broadens the skill's actual behavior beyond the declared domain intent.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The top-level docstring is written as a Chinese-only description of a general-purpose preprocessing tool, while the code also prefers Chinese OCR language data later in the file. For a general-purpose skill, this indicates a locale/language preference without any opt-in or documented regional justification.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The skill launches external office, PDF, and OCR executables against user-supplied files, which materially expands the attack surface beyond the stated clinical-trial-design purpose. Even without shell injection, processing untrusted documents through complex third-party parsers can expose the host to parser vulnerabilities, crashes, or resource abuse if these tools are not isolated.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The code automatically performs document conversion and OCR on untrusted user files without any visible consent, warning, or trust-boundary acknowledgment. In a skill that is nominally about clinical trial design, silently invoking heavyweight external parsers increases unexpected processing risk and can subject the environment to malformed-file attacks or denial-of-service conditions.

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
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
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
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
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
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The code automatically sets the OCR language to "chi_sim+eng" when those models are installed, rather than selecting language based on user choice or neutral defaults. This is a natural-language locale policy issue because it imposes a language preference on all image OCR inputs.

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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code sends full clinical trial design inputs, which may include sensitive proprietary R&D or regulated medical information, to an external API endpoint via `_call_llm` without any in-code notice, consent flow, minimization, or policy gating. In this skill context, the data is especially sensitive because trial protocols, endpoints, populations, and interventions can reveal confidential drug development strategy and potentially regulated health-related information.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
Natural-language strings in the module docstring, system prompt, user prompt template, and CLI description consistently assume Chinese-language use. The file does not provide a user opt-in, language selection mechanism, or explicit justification that the skill is intended only for a Chinese-language environment.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

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