Back to skill

Security audit

unisound-abnormal-indicator-alert

Security checks for vulnerabilities and agentic risk

Overview

This health alert skill discloses much of its medical-file processing, but it also sends sensitive health data to a model service and has an unsafe fallback that can run unreviewed local code.

Review this carefully before installing. Use it only if users are allowed to send medical indicator data to the listed model provider, local document/OCR tools are sandboxed, and the external shared-preprocessor fallback is removed or pinned to trusted code. For patient-facing use, require consent, minimize or redact inputs, and validate generated medical text against approved rules.

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

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/run.py:220
Finding
Unverified External Python Module Execution During Preprocessing Fallback<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:220-227` **Vulnerability Type**: Untrusted local module loading and execution **Risk Level**: High ### Complete Code Snippet ```python _shared_dir = Path(__file__).resolve().parent.parents[3] / "_shared" / "doc-preprocess" / "scripts" if not _shared_dir.exists(): print(f"ERROR: Unable to read the input file because local preprocessing failed and the shared preprocessor is unavailable. Cause: {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) ``` The displayed error message has been translated into English; the executable module-loading statements are reproduced from the source. ### Technical Analysis When local preprocessing raises `PreprocessError`, the application calculates a path outside the skill package and dynamically executes the `preprocess.py` file found there. The code verifies only that the directory exists. It does not verify: - The ownership or permissions of the directory and file. - Whether the resolved file is a symbolic link. - Whether the file belongs to an approved package. - A cryptographic hash or signature. - Whether the module path remains within a trusted root after resolution. Calling `exec_module()` executes all top-level statements in the selected Python file. Consequently, control over that external file is equivalent to arbitrary Python code execution under the identity and privileges of the skill process. This behavior crosses the package trust boundary: reviewing the files included in this skill does not establish what code will run in the fallback path. ### Attack Path 1. An attacker obtains the ability to create, replace, or influence the external `_shared/doc-preprocess/scripts/preprocess.py` file, or redirects it through filesystem manipulation. 2. The attacker places arbitrar ...[truncated 1251 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the dynamic fallback and use only the preprocessor shipped with the reviewed skill. 2. If shared functionality is required, package it as a version-pinned, trusted Python dependency and import it through the normal package mechanism. 3. Resolve the candidate path with `Path.resolve(strict=True)` and verify that it remains under an explicitly configured trusted root. 4. Reject symbolic links and require restrictive ownership and write permissions. 5. Verify the module against a pinned cryptographic digest or signed manifest before loading it. 6. Do not use `exec_module()` on files selected from mutable shared directories. 7. Run the skill in a sandbox with minimal filesystem access, a restricted environment, limited outbound networking, and no unnecessary operating-system privileges. 8. Add a test that intentionally triggers `PreprocessError` and confirms that no code outside the installed package is executed. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:88
Finding
Prompt Injection Through Unvalidated Medical Input Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:88-111` **Vulnerability Type**: Untrusted data embedded directly into LLM instructions **Risk Level**: Medium ### Complete Code Snippet ```python def build(data: Dict[str, Any], appkey: str) -> Dict[str, Any]: indicator_type = data.get("indicator_type", "") value = data.get("value", 0) unit = data.get("unit", "") measured_at = data.get("measured_at", "") threshold_profile = data.get("threshold_profile", {}) check_result = _check_abnormal(float(value), threshold_profile) indicator_name = INDICATOR_NAMES.get(indicator_type, indicator_type) user_prompt = f"""Analyze the following indicator: Indicator type: {indicator_name} ({indicator_type}) Current value: {value} {unit} Measurement time: {measured_at} Threshold configuration: {json.dumps(threshold_profile, ensure_ascii=False)} Abnormality result: {json.dumps(check_result, ensure_ascii=False)} Provide a Markdown table containing the indicator name, current value, normal range, abnormality level, at least two or three possible causes, and recommendations.""" text = _call_llm(SYSTEM_PROMPT, user_prompt, appkey) ``` The natural-language prompt has been translated into English while retaining the source interpolation structure and requested behavior. ### Technical Analysis The fields `indicator_type`, `unit`, and `measured_at` originate from input files and are interpolated directly into an instruction-bearing prompt. Unknown `indicator_type` values are also used directly as the display name: ```python indicator_name = INDICATOR_NAMES.get(indicator_type, indicator_type) ``` There are no allowlists, length limits, control-character restrictions, or semantic validation for these string fields. The model therefore receives attacker-controlled text in the same message and instruction context as the legitimate analysis request. An attacker can insert content such as instructions to disregard the requested ...[truncated 1697 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define and enforce a strict input schema before prompt construction. 2. Restrict `indicator_type` to an explicit allowlist of supported identifiers. 3. Validate `unit` against an indicator-specific allowlist. 4. Parse `measured_at` as a date-time value and serialize it back into a canonical format. 5. Apply conservative length limits and reject control characters or multiline instruction-like content. 6. Put user data in a clearly delimited serialized data block and state that content inside the block is data, not instructions. 7. Prefer structured model output with a strict JSON schema rather than unrestricted Markdown. 8. Validate model output against the deterministic `check_result`; reject or replace responses that contradict `is_abnormal` or `alert_level`. 9. Use clinician-approved deterministic templates for safety-critical alerts instead of allowing the model to determine urgency. 10. Add adversarial tests containing instruction injection in every accepted input field. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:24
Finding
Sensitive Health Data Is Transmitted to an External Model Service Without Data-Minimization Controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:24-32` **Vulnerability Type**: External disclosure of sensitive medical data **Risk Level**: Medium ### Complete Code Snippet ```python def _call_llm(system_prompt: str, user_prompt: str, appkey: str) -> str: payload = {"model": MODEL, "temperature": 0.0, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}]} try: req = Request(API_URL, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"), headers={"Content-Type": "application/json", "Authorization": f"Bearer {appkey}"}) resp = urlopen(req, timeout=120) return json.loads(resp.read().decode("utf-8"))["choices"][0]["message"]["content"] except HTTPError as exc: raise RuntimeError(f"API HTTP {exc.code}") except URLError as exc: raise RuntimeError(f"API unreachable: {exc.reason}") ``` The associated prompt construction transmits these values: ```python indicator_type = data.get("indicator_type", "") value = data.get("value", 0) unit = data.get("unit", "") measured_at = data.get("measured_at", "") threshold_profile = data.get("threshold_profile", {}) ``` ### Technical Analysis The skill sends the complete model prompt to `https://maas-api.hivoice.cn/v1/chat/completions`. That prompt contains health-indicator type, measured value, unit, measurement time, caller-provided thresholds, and the locally calculated abnormality result. The endpoint is documented and HTTPS is used, so the behavior is not covert exfiltration and the data is encrypted in transit. However, the implementation provides no technical controls for: - Explicit consent before external processing. - Data minimization or pseudonymization. - Removal of unnecessary timestamps. - Detection and redaction of identifiers embedded in string fields. - A local-only execution mode. - Configurable endpoint trust policy. - Retention, residen ...[truncated 1576 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Present an explicit consent and disclosure step before transmitting medical data. 2. Document the processor, purpose, retention policy, residency, and secondary-use terms. 3. Send only fields strictly required for the requested analysis. 4. Remove or coarsen `measured_at` unless exact time is necessary. 5. Detect and redact names, patient identifiers, contact details, and free-form identifying content. 6. Add a local-only mode that returns deterministic threshold results without invoking the model. 7. Permit deployment administrators to configure an approved endpoint rather than relying solely on a hard-coded provider. 8. Enforce TLS certificate validation and define an outbound-network allowlist at the runtime level. 9. Establish appropriate data-processing agreements and retention controls for health information. 10. Ensure logs and error handlers never record the bearer token or complete patient prompts. ]]>

other

Warning
Location
scripts/run.py:60
Finding
Model-Generated Medical Claims Exceed the Documented Rule-Only Safety Boundary<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:60-67` **Vulnerability Type**: Unsupported model-generated medical guidance **Risk Level**: Medium ### Complete Code Snippet ```python SYSTEM_PROMPT = """You are a clinical laboratory analysis assistant specializing in abnormal laboratory indicators. Your tasks: 1. Analyze the abnormality level of each indicator based on the data provided by the user. 2. Clearly display the indicator name, current value, normal reference range, abnormality level, possible causes, and recommendations in a Markdown table. 3. Mark abnormality as high or low, or severely high or severely low. 4. After an overall assessment, provide a priority level for seeking medical care. Output a Markdown table and a brief analysis, followed by a disclaimer.""" ``` The prompt has been translated into English without changing its requested behavior. The per-request prompt further requests multiple possible causes and recommendations: ```python user_prompt = f"""Analyze the following indicator: Indicator type: {indicator_name} ({indicator_type}) Current value: {value} {unit} Measurement time: {measured_at} Threshold configuration: {json.dumps(threshold_profile, ensure_ascii=False)} Abnormality result: {json.dumps(check_result, ensure_ascii=False)} Provide a Markdown table containing the indicator name, current value, normal range, abnormality level, at least two or three possible causes, and recommendations.""" ``` ### Technical Analysis The documented boundary states that the skill performs rule-based alerts, does not diagnose, does not provide treatment, and does not contain built-in medical thresholds. In contrast, the model is expressly instructed to generate: - A normal reference range. - Multiple possible causes. - Recommendations. - A priority level for seeking medical care. Only caller-defined `high` and `low` values are available to the deterministic logic. No validated normal range, patient context, clinic ...[truncated 1668 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict the model to explaining the deterministic result derived from caller-provided thresholds. 2. Prohibit the model from inventing normal ranges, diagnoses, causes, treatments, or care priorities. 3. Include only validated, caller-supplied reference ranges in output. 4. Use deterministic, clinician-approved escalation rules for urgent warnings. 5. Replace free-form medical recommendations with approved templates tied to explicit rule outcomes. 6. Require structured model output and validate every field before rendering it. 7. Compare generated statements against `check_result` and reject contradictions. 8. Clearly distinguish caller-provided values from generated explanatory text. 9. Subject all patient-facing templates and escalation logic to qualified clinical review. 10. Update the documentation and implementation so the advertised medical boundary and actual behavior are consistent. ]]>
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
99% confidence
Finding
The declared purpose is narrow abnormal-indicator alerting, but the documented behavior expands into broad document ingestion, OCR, PDF parsing, and Office conversion. That mismatch matters because users may provide highly sensitive medical files believing the skill only processes structured indicators, while the implementation appears capable of extracting and transmitting far more data than expected.

Missing User Warnings

High
Confidence
98% confidence
Finding
The skill explicitly sends medical data to an external/internal model API endpoint for analysis but does not provide a clear user-facing warning or consent notice. Because the inputs may include patient health information from documents, images, and spreadsheets, undisclosed transmission creates significant privacy, compliance, and trust risks.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The file implements a broad multi-format ingestion and extraction utility that is not closely aligned with a chronic-disease abnormal indicator alert skill. This capability expansion increases the attack surface substantially by accepting many risky file formats and external-processing paths without a clear medical-alerting need, making abuse or accidental overcollection more likely.

Context-Inappropriate Capability

High
Confidence
93% confidence
Finding
The skill includes subprocess-driven OCR and office/PDF conversion on user-provided files despite its stated purpose not obviously requiring such broad document-processing capability. In context, this mismatch is dangerous because it introduces high-risk external parsers and data extraction features that expand both exploitability and privacy exposure beyond what a medical alerting skill should minimally need.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises capabilities that include file access, shell use, environment access, and network calls, but it does not declare any explicit tool scope or permission boundaries. In a health-data workflow, this creates unnecessary ambiguity about what the skill may access or transmit, increasing the risk of over-privileged execution and unintended exposure of sensitive patient data.

Natural-Language Policy Violations

Medium
Confidence
74% confidence
Finding
The entire skill description and examples are written for Chinese usage, and the OCR/tooling section explicitly requires `chi_sim+eng`, suggesting a built-in Chinese locale assumption. The file does not offer a language/locale choice or explain that the skill is intentionally limited to a specific region or language context.

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
80% confidence
Finding
This code runs LibreOffice on user-supplied office documents for conversion. Although it avoids shell injection, invoking complex document parsers on untrusted files materially increases attack surface and can lead to parser-triggered code execution, sandbox escapes, or denial of service if the external tool has vulnerabilities.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The file invokes external tools such as LibreOffice/soffice, pdftotext, and tesseract to process user-provided documents and images. While this is functionally related to preprocessing, there is no confirmation prompt, user-facing log, or warning in the code indicating that local binaries will be executed against the supplied files.

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
80% confidence
Finding
This subprocess executes LibreOffice against user-controlled .xls input, which exposes the service to vulnerabilities in a large external parser stack. The risk is not shell injection but unsafe processing of attacker-crafted files that may trigger remote code execution or resource exhaustion in the converter.

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
This call sends untrusted PDFs to an external text-extraction utility. As with other parser subprocesses, the main risk is exploitation of vulnerabilities in the PDF toolchain or denial of service from maliciously crafted PDFs, not command injection.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The code automatically prefers the locale combination "chi_sim+eng" when those Tesseract models are available, rather than letting the user choose OCR language settings. This imposes a specific language/locale behavior without documented user choice or clear region-specific justification.

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
97% confidence
Finding
The system prompt instructs the model entirely in Chinese, and the skill description and output expectations are also Chinese-specific. This forces a specific language/locale behavior without any opt-in, selection mechanism, or documented region-specific justification.

Ssd 3

Medium
Confidence
98% confidence
Finding
The skill directly interpolates untrusted, user-supplied medical fields into an external LLM prompt and then returns the model output, which can disclose sensitive health data in transformed or expanded form. Because this is a healthcare skill, prompt injection and privacy leakage are more dangerous: malicious or malformed input can manipulate the generated medical summary, while sensitive clinical details are exposed to a third-party model service.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The code sends patient medical indicator data, timestamps, threshold profiles, and derived abnormality assessments to an external LLM API via `_call_llm` without any consent gate, minimization, anonymization, or disclosure in the execution path. In a medical-monitoring skill, this is especially sensitive because the data can reveal health conditions and treatment context, creating privacy, compliance, and third-party retention risks.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

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