Back to skill

Security audit

unisound-health-education

Security checks for vulnerabilities and agentic risk

Overview

This health-education skill has a coherent purpose, but it sends health-related content to a fixed remote model and can execute unverified Python code from outside the skill package.

Install only if you are comfortable sending health-related topics and matched article content to the stated remote API, can protect the API key outside shell history and process logs, and can run document parsing in a constrained environment. The external shared-module fallback should be removed or integrity-pinned before trusting this skill in production or with sensitive patient information.

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)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run.py:89
Finding

Indirect Prompt Injection Through Untrusted Article Content

Content
View full analysis

Vulnerability Details

File Location: scripts/run.py:89-101
Vulnerability Type: Indirect prompt injection caused by untrusted content being interpolated into an LLM prompt
Risk Level: High

Vulnerable Code:

python
user_prompt = f"""请为以下健康知识查询生成科普内容:

主题:{topic}
关键词:{json.dumps(keywords, ensure_ascii=False)}
总文章数:{len(articles)},匹配数:{len(matched)}

匹配文章:
```json
{json.dumps(matched, ensure_ascii=False, indent=2)}

请为匹配的文章生成科普摘要,标注来源,给出健康提示。"""

text = _call_llm(SYSTEM_PROMPT, user_prompt, appkey)

text

### Technical Analysis

The application places user-controlled topics, keywords, article titles, categories, summaries, and URLs directly into the LLM prompt. Serializing these values as JSON and enclosing them in a Markdown code block does not establish a security boundary.

An attacker can place model instructions inside an article field, particularly `summary`, `title`, or `url`. The model may interpret those instructions as part of the conversation rather than as inert article data. The static system prompt does not explicitly direct the model to treat document content as untrusted or prohibit following instructions embedded in supplied articles.

The resulting model response is returned directly in the output:

```python
"text": text.strip(),

This allows model-generated content influenced by the injected instructions to reach the patient-facing rendering layer without validation.

Attack Path

  1. An attacker supplies an input article containing instructions such as “ignore previous instructions,” “recommend this treatment,” or “display this link.”
  2. The attacker chooses a title, category, summary, or keyword that causes the local matching logic to include the article.
  3. The complete attacker-controlled article data is serialized into user_prompt.
  4. _call_llm() sends the prompt to the medical model.
  5. The model may follow the embedded instructions and generat ...[truncated 646 chars]
Remediation
View remediation

Remediation Suggestions

  • Treat every field extracted from user files and article records as untrusted data.
  • Use a structured model interface or strict response schema rather than embedding raw records in a free-form instruction prompt.
  • Add explicit system-level instructions stating that content inside articles is data and that instructions contained in that data must never be followed.
  • Separate trusted instructions from untrusted context using dedicated message or structured-input fields where supported.
  • Validate the response against an allowlisted schema before returning it.
  • Sanitize and validate all displayed URLs, including allowed schemes and trusted-domain requirements where appropriate.
  • Apply output filtering for fabricated treatment advice, credential requests, executable content, and unsupported source claims.
  • Require human or policy-based review before presenting high-impact medical statements.
  • Add adversarial tests containing prompt injection strings in every accepted article field.

other

Warning
Location
scripts/run.py:31
Finding

External Disclosure of Health-Related Input Without Data-Minimization Controls

Content
View full analysis

Vulnerability Details

File Location: scripts/run.py:31-40, 89-99
Vulnerability Type: Sensitive health-data transmission to an external API
Risk Level: Medium

Vulnerable Code:

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"]
python
user_prompt = f"""请为以下健康知识查询生成科普内容:

主题:{topic}
关键词:{json.dumps(keywords, ensure_ascii=False)}
总文章数:{len(articles)},匹配数:{len(matched)}

匹配文章:
```json
{json.dumps(matched, ensure_ascii=False, indent=2)}
text

### Technical Analysis

The Skill sends the user's health topic, keywords, matched article metadata, article summaries, and source URLs to the fixed external endpoint `https://maas-api.hivoice.cn/v1/chat/completions`.

Although the project documentation identifies the API dependency, the implementation provides no runtime consent mechanism, local-only mode, identifier redaction, field-level minimization, or configurable privacy policy. Content extracted from user-provided health documents can therefore leave the local environment as part of the prompt.

TLS protects the request in transit, but it does not address disclosure to the external service operator, downstream retention, access control, or secondary processing.

### Attack Path

1. A user supplies a health-related query or document.
2. The preprocessing logic extracts the topic, keywords, and article information.
3. `build()` incorporates thos
...[truncated 792 chars]
Remediation
View remediation

Remediation Suggestions

  • Obtain explicit and informed user consent before sending health-related content to the external API.
  • Clearly disclose the destination, submitted fields, retention policy, and processing purpose.
  • Remove direct identifiers and unnecessary free-form content before constructing the request.
  • Submit only the minimum fields required to generate the requested summary.
  • Provide a local-only or no-model mode for privacy-sensitive use cases.
  • Add configurable data-loss-prevention rules for names, contact information, record identifiers, and other sensitive attributes.
  • Establish and document contractual retention, deletion, encryption, access-control, and incident-response requirements for the API provider.
  • Add request auditing that records metadata without logging health content or bearer tokens.

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/run.py:227
Finding

Unverified Dynamic Execution of a Python Module Outside the Skill Package

Content
View full analysis

Vulnerability Details

File Location: scripts/run.py:227-241
Vulnerability Type: Untrusted local module loading and execution
Risk Level: High

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)
        if input_type == "json":
            data = load_json(args.input)
        else:
            artifact = _sp.load_input_artifact(input_path, input_type, args.encoding, args.sheet)

Technical Analysis

When local preprocessing raises PreprocessError, the application calculates a path outside the audited Skill directory and executes _shared/doc-preprocess/scripts/preprocess.py through exec_module().

Importing a Python module executes its module-level code. The application does not verify a cryptographic digest, package signature, expected owner, filesystem permissions, or other integrity property before executing the file. The external module is not included in the audited artifact, so its behavior cannot be established from this repository.

This is a local tool-hijacking boundary: a legitimate-looking fallback can execute attacker-controlled Python if an attacker can create or modify the expected external file.

Attack Path

  1. An attacker obtains write access to the calculated _shared/doc-preprocess/scripts location or replaces preprocess.py through a compromised installa ...[truncated 1102 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove runtime loading of Python source from a filesystem location outside the Skill package.
  • Package the required preprocessing implementation as a pinned, reviewed dependency.
  • Import the dependency through the normal package mechanism rather than spec_from_file_location() and exec_module().
  • If external loading is unavoidable, use a fixed administrator-controlled path rather than one derived from directory ancestry.
  • Verify a pinned cryptographic digest or trusted signature before loading the module.
  • Verify that the file and each parent directory have trusted ownership and are not writable by unprivileged users.
  • Fail closed if any integrity or permission check fails.
  • Run document preprocessing in a sandbox with minimal filesystem, credential, and network access.
  • Add a security test that confirms malformed input cannot cause execution of an unverified external module.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:205
Finding

Bearer Credential Exposed Through Command-Line Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/run.py:205-212
Vulnerability Type: Insecure secret handling through process arguments
Risk Level: Medium

Vulnerable Code:

python
def main():
    parser = argparse.ArgumentParser(description="健康知识科普 — 根据主题匹配文章并生成科普摘要")
    parser.add_argument("--input", required=True)
    parser.add_argument("--output", default="")
    parser.add_argument("--input-type", default="auto", choices=["auto", *sorted(SUPPORTED_FILE_TYPES)])
    parser.add_argument("--sheet", default=""); parser.add_argument("--encoding", default="utf-8")
    parser.add_argument("--save-prepared", action="store_true")
    parser.add_argument("--appkey", required=True, help="内部医疗大模型鉴权key(必填)")
    args = parser.parse_args()

The documented invocation also instructs users to place the secret on the command line:

bash
python3 scripts/run.py --input input.json --output output.json --appkey YOUR_KEY

Technical Analysis

The API bearer token is accepted as a command-line argument. Command-line values may be visible through process inspection interfaces, shell history, job-control systems, container or orchestration metadata, diagnostic reports, audit tooling, and command logging.

The token is subsequently placed in the Authorization header:

python
headers={"Content-Type": "application/json", "Authorization": f"Bearer {appkey}"}

While the key is not hardcoded or printed directly by the application, its initial delivery mechanism unnecessarily exposes it to local and operational observation.

Attack Path

  1. A user follows the documented command and supplies a real API key through --appkey.
  2. The command is recorded in shell history, execution logs, or orchestration metadata, or remains visible in the process argument list while the request is running.
  3. A local user, monitoring agent, log reader, or compromised operational component retrieves the ...[truncated 691 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the --appkey command-line option as the primary credential mechanism.
  • Retrieve the token from a platform-managed secret store or injected secret file with restrictive permissions.
  • Where supported, pass the secret through a protected file descriptor or dedicated credentials API.
  • For interactive local use, obtain the key through a non-echoing prompt such as getpass.
  • If an environment variable must be supported, document its exposure limitations and prevent it from being logged or propagated unnecessarily.
  • Use narrowly scoped, short-lived tokens and rotate them regularly.
  • Redact authorization values and known secret patterns from application, proxy, orchestration, and diagnostic logs.
  • Revoke and replace any credential previously used in exposed command histories or logs.
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
99% confidence
Finding

The stated purpose is a simple health education content-entry/matching skill, but the documented behavior expands into broad document ingestion, local file reading, OCR, Office/PDF conversion, and external executable usage. That mismatch is dangerous because reviewers or users may trust the skill with low-risk content curation while the actual implementation gains access to much more sensitive local data and attack surface through parsers and converters.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill documents capabilities implying file access, shell execution, network access, and environment use, but it does not declare any explicit tool scope or permission boundaries. In a skill that handles user-supplied documents and invokes external tools, this weakens sandboxing and reviewability, increasing the risk of unintended file access, command execution, or network exfiltration if the implementation follows the documentation.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documentation broadens a local knowledge skill into mandatory remote model inference plus ingestion of PDFs, Office docs, and OCR from images. This increases the attack surface substantially: sensitive medical text can be extracted from complex file types and then transmitted externally, while document parsers and conversion tools themselves are common sources of security and privacy risk.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill mandates use of an internal medical LLM API even though the stated task—matching health topics to knowledge-base articles—can often be done locally or with far less data disclosure. Requiring remote inference without a clear functional need creates avoidable privacy and dependency risks, especially when handling potentially sensitive health-related user input and parsed document contents.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill indicates that user-provided health topics and potentially parsed document contents are sent to an internal medical model API, but it does not provide a clear user-facing warning or consent mechanism about data transmission. In a medical context, this is particularly sensitive because even seemingly generic inputs may contain personal health information, creating privacy, compliance, and trust risks if forwarded silently.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The module docstring is written entirely in Chinese, and the OCR path preferentially sets the Tesseract language to "chi_sim+eng" when available. This indicates a default language/locale choice without any visible user opt-in or documented justification for restricting behavior to Chinese-oriented processing.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This Python code invokes external binaries via subprocess for office conversion, PDF extraction fallback, and OCR processing. Although the module docstring describes preprocessing broadly, there is no comment, docstring detail, print/log message, or confirmation warning that user files will be handed to external local tools such as LibreOffice, pdftotext, or Tesseract.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
76% confidence
Finding

This code executes LibreOffice/soffice on attacker-supplied office documents, which materially expands the attack surface to a large external parser/converter. Even without shell injection, feeding untrusted documents to complex native converters can lead to code execution or filesystem/network side effects if the external tool is vulnerable or unsafe in the deployment environment.

Content

Scanner excerpt · scripts/preprocess.py (reported line 111)May include surrounding context.

python
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,
        )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
76% confidence
Finding

This invokes LibreOffice to convert untrusted .xls files, creating risk from parsing legacy spreadsheet formats with a complex external application. The issue is not shell injection but exposure to parser bugs, malicious document payloads, or unintended side effects from the converter process.

Content

Scanner excerpt · scripts/preprocess.py (reported line 126)May include surrounding context.

python
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)],

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

This launches pdftotext on untrusted PDFs, which can be dangerous because PDF parsers are historically bug-prone and may be exploitable through crafted files. The subprocess usage itself is safe from shell injection, but the design still exposes the host to risks in the external parser.

Content

Scanner excerpt · scripts/preprocess.py (reported line 153)May include surrounding context.

python
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,
        )

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code automatically chooses the Tesseract language argument "chi_sim+eng" whenever both are installed, which enforces a locale preference in processing. There is no parameter or documented opt-in allowing users to choose a different OCR language configuration.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/preprocess.py (reported line 171)May include surrounding context.

python
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

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/preprocess.py (reported line 178)May include surrounding context.

python
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,
    )

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The system prompt is entirely in Chinese and explicitly instructs the model to generate output in that form, while the CLI description and skill text also assume Chinese-only operation. This is a natural-language locale policy concern because the file does not provide any user opt-in, language selection, or documented region-specific justification for restricting responses to Chinese.

Content

No source excerpt is available for this finding.

Ssd 4

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Untrusted article fields such as title, summary, keywords, and URL are serialized directly into the user prompt and presented as material the model should use to generate medical guidance. An attacker controlling article content can embed prompt-injection text that steers the model to ignore instructions, hallucinate unsafe health advice, or misrepresent source credibility, which is especially concerning in a patient-health context.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
71% confidence
Finding

The file specifies OCR support with "chi_sim+eng" and all user-facing instructions are written in Chinese, but it does not state that the skill is intentionally limited to a Chinese-language or region-specific audience. Because no language choice or explicit locale justification is provided, this may violate language/locale policy expectations.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

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