Back to skill

Security audit

med-chronic-disease-review

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent medical-review purpose, but it can send raw medical records and a bearer token to a configurable model endpoint and save sensitive outputs despite promising de-identification and no local storage.

Review this skill carefully before installing. Use it only with data you are authorized to process, pre-redact patient identifiers yourself, keep --base pinned to a trusted HTTPS endpoint, and store outputs only in a private controlled directory. Do not rely on the package's stated no-persistence or automatic de-identification guarantees as implemented.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/chronic_disease_review.py:114
Finding

Medical Records Are Transmitted Without the Documented De-identification

Content
View full analysis

Vulnerability Details

File Location: scripts/chronic_disease_review.py:114-146, 178-187
Vulnerability Type: Sensitive medical-data disclosure
Risk Level: High

The documentation states that identifiable information is strictly redacted before being sent to any model or interface. The implementation contains no redaction step: filenames and complete OCR text are inserted directly into the prompt and transmitted to the configured model service.

Complete Code Snippet

python
def format_ocr_for_prompt(ocr_data: List[Dict[str, Any]]) -> str:
    blocks: List[str] = []
    for item in ocr_data:
        file_name = item.get("fileName") or "未知文件"
        page = item.get("page", "")
        doc_type = item.get("docType") or "未分类文书"
        text = item.get("ocrText") or ""
        blocks.append(f"【{doc_type}】{file_name} 第{page}页\n{text}")
    return "\n\n".join(blocks)


SYSTEM_PROMPT = """你是医疗保险门诊慢特病(慢病)理赔审核助手。
根据用户提供的 OCR 病历/检验等文书文本,判断是否符合该慢病的门诊慢特病认定或理赔审核要求。
仅依据给定文本作答,不要编造未出现的检查结果或诊断。
输出必须是合法 JSON,且只包含一个 JSON 对象,不要 markdown 代码块或额外说明。"""


def build_review_user_prompt(*, disease_code: str, review_type: str, ocr_data: List[Dict[str, Any]]) -> str:
    ocr_text = format_ocr_for_prompt(ocr_data)
    return f"""请对以下材料进行「{review_type}」,病种:{disease_code}。

材料正文:
{ocr_text}

请输出 JSON,字段如下(均为字符串):
- final_decision:审核结论,取值为「通过」「不通过」「待补充」之一
- reasoning:审核原因说明,需与结论一致

示例:
{{"final_decision": "通过", "reasoning": "..."}}"""
python
user_prompt = build_review_user_prompt(
    disease_code=disease_code,
    review_type=review_type or "慢病审核",
    ocr_data=ocr_data,
)
content = llm(
    [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_prompt},
    ]
)

Technical Analysis

format_ocr_for_prompt copies fileName, docType, and the complete ocrText value into the model prompt. Medical records can ...[truncated 1367 chars]

Remediation
View remediation

Remediation Suggestions

  1. Implement a dedicated de-identification stage before prompt construction.
  2. Detect and redact names, identity and insurance numbers, telephone numbers, email addresses, full addresses, dates of birth, and other identifying attributes.
  3. Exclude filenames and document metadata unless they are strictly required for the review.
  4. Run residual-sensitive-data detection after redaction and block transmission when unresolved identifiers remain.
  5. Use context-aware medical de-identification rather than relying exclusively on regular expressions.
  6. Add tests containing representative identifiers and verify that outbound request bodies do not contain them.
  7. Require explicit informed consent if any sensitive data must be transmitted.
  8. Update the documentation if the implementation cannot guarantee the stated level of redaction.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/chronic_disease_review.py:54
Finding

Arbitrary Model Endpoint Can Capture the Bearer Token and Medical Data

Content
View full analysis

Vulnerability Details

File Location: scripts/chronic_disease_review.py:54-61, 242-243, 253-261 and scripts/run.py:67-68, 307-315
Vulnerability Type: Unrestricted sensitive-data destination
Risk Level: High

The user-controlled --base option determines the server receiving the bearer credential and medical prompt. The implementation does not enforce HTTPS, validate the destination hostname, restrict ports, or prevent redirects to a different origin.

Complete Code Snippet

python
def make_llm_caller(
    appkey: str,
    *,
    base: str = DEFAULT_LLM_BASE,
    model: str = DEFAULT_LLM_MODEL,
    timeout: int = 0,
):
    url = f"{base.rstrip('/')}/chat/completions"
    headers = {"Authorization": f"Bearer {appkey}"}
python
parser.add_argument("--base", default=DEFAULT_LLM_BASE, help=f"LLM base URL (default: {DEFAULT_LLM_BASE})")
parser.add_argument("--model", default=DEFAULT_LLM_MODEL, help=f"LLM model (default: {DEFAULT_LLM_MODEL})")
parser.add_argument("--timeout", type=int, default=120, help="HTTP timeout seconds (default: 120).")
parser.add_argument("--dry-run", action="store_true", help="Skip LLM call; emit placeholder response.")
parser.add_argument("--output-json", default="", help="Path to save raw response JSON.")
parser.add_argument("--output-text", default="", help="Path to save natural language summary.")
python
resp = review_chronic_disease(
    ocr_data,
    disease_code=disease_code,
    review_type=args.review_type,
    appkey=args.appkey.strip(),
    base=args.base,
    model=args.model,
    timeout=args.timeout,
)

The unified entry point exposes the same behavior:

python
parser.add_argument("--base", default=DEFAULT_LLM_BASE, help=f"LLM base URL (default: {DEFAULT_LLM_BASE})")
python
resp = review_chronic_disease(
    ocr_data,
    disease_code=disease_code,
    review_type=args.review_type or "慢病审核
...[truncated 1745 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the runtime endpoint override unless it is operationally necessary.
  2. Maintain an explicit allowlist of approved HTTPS hostnames and compare normalized hostnames exactly.
  3. Reject plain HTTP, IP-literal destinations, embedded credentials, fragments, unexpected ports, and malformed URLs.
  4. Restrict paths to the expected API route.
  5. Disable redirects or enforce that every redirect remains on the approved origin.
  6. Bind credentials to a particular service host and never attach them to an untrusted destination.
  7. Use short-lived, narrowly scoped credentials and rotate any token that may have been exposed.
  8. Record destination validation failures without logging credentials or medical content.

T01 · Skill Instruction Hijacking

Error
Location
scripts/chronic_disease_review.py:114
Finding

Untrusted OCR Content Can Manipulate the Medical Review Through Prompt Injection

Content
View full analysis

Vulnerability Details

File Location: scripts/chronic_disease_review.py:114-146, 178-196
Vulnerability Type: Indirect prompt injection
Risk Level: High

OCR-derived document content is concatenated directly into an instruction-bearing user prompt. A crafted document can contain instructions that attempt to override the review task and force a chosen decision.

Complete Code Snippet

python
def format_ocr_for_prompt(ocr_data: List[Dict[str, Any]]) -> str:
    blocks: List[str] = []
    for item in ocr_data:
        file_name = item.get("fileName") or "未知文件"
        page = item.get("page", "")
        doc_type = item.get("docType") or "未分类文书"
        text = item.get("ocrText") or ""
        blocks.append(f"【{doc_type}】{file_name} 第{page}页\n{text}")
    return "\n\n".join(blocks)
python
def build_review_user_prompt(*, disease_code: str, review_type: str, ocr_data: List[Dict[str, Any]]) -> str:
    ocr_text = format_ocr_for_prompt(ocr_data)
    return f"""请对以下材料进行「{review_type}」,病种:{disease_code}。

材料正文:
{ocr_text}

请输出 JSON,字段如下(均为字符串):
- final_decision:审核结论,取值为「通过」「不通过」「待补充」之一
- reasoning:审核原因说明,需与结论一致

示例:
{{"final_decision": "通过", "reasoning": "..."}}"""
python
content = llm(
    [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_prompt},
    ]
)
parsed = normalize_llm_decision(extract_json_object(content))
return {
    "success": True,
    "source": "llm",
    "model": model,
    "results": [
        {
            "disease_code": disease_code,
            "review_type": review_type or "慢病审核",
            "scenario_code": "chronic-disease-llm",
            "final_decision": parsed["final_decision"],
            "reasoning": parsed["reasoning"],
            "raw_llm_content": content,
        }
    ],
}

Technical Analysis

The model receives policy instructio ...[truncated 1617 chars]

Remediation
View remediation

Remediation Suggestions

  1. State in the system message that all OCR content is untrusted evidence and that instructions found inside it must never be followed.
  2. Place evidence in a clearly delimited structured field rather than interpolating it into prose instructions.
  3. Escape or encode delimiter-like sequences so documents cannot terminate the evidence boundary.
  4. Detect instruction-like content in OCR data and flag affected records for human review.
  5. Enforce an exact allowlist for final_decision.
  6. Require the model to return citations to specific evidence locations and validate those citations.
  7. Independently evaluate critical eligibility rules instead of relying solely on unconstrained model output.
  8. Require human confirmation before an LLM-generated result triggers a consequential insurance action.
  9. Add adversarial tests covering multilingual prompt injection, hidden document text, and OCR-manipulated instructions.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:317
Finding

Sensitive Medical Data Is Persisted Contrary to the No-Persistence Guarantee

Content
View full analysis

Vulnerability Details

File Location: scripts/run.py:317-329 and scripts/chronic_disease_review.py:264-272
Vulnerability Type: Insecure sensitive-data persistence
Risk Level: Medium

Every successful invocation writes the model response and natural-language summary to disk. The optional --save-prepared mode additionally writes the full normalized OCR array. This conflicts with the documented statement that inputs and intermediate results are not written to persistent local storage.

Complete Code Snippet

python
out_json.parent.mkdir(parents=True, exist_ok=True)
out_json.write_text(json.dumps(resp, ensure_ascii=False, indent=2), encoding="utf-8")

text = build_natural_language(resp)
out_text.parent.mkdir(parents=True, exist_ok=True)
out_text.write_text(text, encoding="utf-8")

if args.save_prepared:
    prepared_path = out_json.with_name(f"{out_json.stem}.prepared.json")
    prepared_path.write_text(json.dumps(ocr_data, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"✓ Prepared OCR array saved to: {prepared_path}")

The direct entry point also persists results by default:

python
out_json.parent.mkdir(parents=True, exist_ok=True)
out_json.write_text(json.dumps(resp, ensure_ascii=False, indent=2), encoding="utf-8")

text = build_natural_language(resp)
out_text.parent.mkdir(parents=True, exist_ok=True)
out_text.write_text(text, encoding="utf-8")

The response includes the complete raw model content:

python
"final_decision": parsed["final_decision"],
"reasoning": parsed["reasoning"],
"raw_llm_content": content,

Technical Analysis

Output persistence is enabled by default rather than requiring explicit consent. The JSON response can contain raw model output derived from sensitive medical records, while the prepared file contains the full OCR input. Files are created with ordinary process defaults and no explicit restrictive permission ...[truncated 1168 chars]

Remediation
View remediation

Remediation Suggestions

  1. Make all persistence explicitly opt-in rather than writing outputs by default.
  2. Do not store raw_llm_content unless it is strictly required.
  3. Keep prepared OCR data in memory by default and clearly warn users before saving it.
  4. Create output files atomically with owner-only permissions such as 0600.
  5. Store sensitive outputs only in an approved private directory with access-control checks.
  6. Add configurable retention periods and reliable deletion workflows.
  7. Encrypt persisted records when business requirements mandate storage.
  8. Prevent output paths from resolving to unintended shared directories or symbolic-link targets.
  9. Ensure logs and console previews do not reveal sensitive reasoning or patient information.
  10. Revise the documentation so its persistence statement accurately matches the implemented behavior.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (15)

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill states that user input and intermediate results are not written to persistent local storage, but later sections explicitly describe saving prepared OCR data and model outputs to local files. This contradiction is dangerous because users may supply highly sensitive medical records under false assumptions about non-retention, leading to inadvertent storage of PHI on disk.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 142)May include surrounding context.

md
- **发布约束**:示例输入、运行输出、自测脚本均放在 skill 包外(分别位于 `../data/`、`../runs/`、`../self_tests/`),skill 目录内仅保留可发布的核心文件(`scripts/`、`SKILL.md`、`_meta.json`)。

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The function sends OCR medical records, which are highly sensitive health data, to a remote LLM API endpoint without any explicit consent prompt, warning, redaction, or data-handling disclosure. In this skill context the risk is elevated because medical OCR commonly contains diagnoses, identifiers, and test results, so external transmission can violate privacy requirements and create serious compliance exposure.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill documents capabilities to read files, write outputs, and call a networked medical model, but it does not declare any explicit tool scope or permission boundaries. This weakens least-privilege controls and can cause operators or enforcement layers to underestimate what the skill is allowed to do, especially given it processes sensitive medical OCR content.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation promises strict de-identification before any model or API transmission, yet the interface accepts raw OCR arrays containing free-form ocrText and does not show any mandatory sanitization boundary before transmission. In a medical context, this creates a real risk of sending personally identifiable or protected health information to the remote model if callers assume the skill guarantees de-identification when it may not.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The documentation's privacy assurances conflict with later instructions that outputs and preprocessed data may be saved locally, without a clear warning at the point of use. This can mislead users into unsafe handling of sensitive data and increases the chance that OCR text or model responses remain on developer workstations or shared systems.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 136)May include surrounding context.

md
| `pdftotext`(poppler-utils) | 提取 PDF 文本 | 输入为 pdf 且未安装 pypdf 时必须 |
| `tesseract`(含 chi_sim+eng 语言包) | 图片 OCR | 输入为 png/jpg/bmp/tif 等图片时必须 |

安装(Ubuntu/Debian):`sudo apt-get install libreoffice poppler-utils tesseract-ocr tesseract-ocr-chi-sim`

> 仅使用 JSON 输入时,无需安装任何第三方包或外部工具。

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The system prompt requires Chinese output, and the script's disease codes and review workflow are hard-coded around Chinese-language values. This is a natural-language locale constraint with no user opt-in or explanation that the skill is intentionally limited to a Chinese-only regulatory or operational context.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script persists raw review JSON and a natural-language summary to local files by default, including OCR-derived medical content and the model's raw response. In a medical-review context this creates unnecessary at-rest exposure of sensitive health data beyond the stated purpose of processing OCR input and returning a conclusion, increasing leakage risk on shared hosts or insecure workspaces.

Content

No source excerpt is available for this finding.

Tainted flow: 'text' from pathlib.Path.read_text (line 86, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/format_review_nl.py (reported line 91)May include surrounding context.

python
if args.output:
        out_path = Path(args.output)
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_text(text, encoding="utf-8")
    else:
        print(text)
    return 0

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill description says it expects an OCR-result JSON array, but the code accepts and preprocesses many local formats including pdf/doc/docx/xls/xlsx/csv/txt/json. This expands the skill’s effective capability and attack surface beyond the declared contract, which can surprise callers, trigger unintended local file parsing, and expose the workflow to parser risks or unauthorized handling of additional local data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The code sends OCR-derived medical records to an LLM service via review_chronic_disease using a configurable base URL, yet there is no explicit warning, consent flow, or sensitivity check before transmission. In this skill context, the data is highly sensitive health information, so silent outbound transfer materially increases privacy, regulatory, and third-party exposure risk.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill writes raw response JSON, natural-language output, and optionally prepared OCR data to local files, but this persistence behavior is not reflected in the description. Because the processed data is medical OCR content, undisclosed local storage can leave sensitive information at rest in predictable locations and create compliance, privacy, and data-retention issues.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
74% confidence
Finding

The manifest description and the rest of the skill instructions are entirely in Chinese, and the file does not indicate that language selection is optional or that the skill is intentionally restricted to a Chinese-speaking environment. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation unless the locale constraint is clearly documented and justified.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The --input path is user-controlled and the helper resolves arbitrary filesystem paths, with an additional fallback into a data directory. While this is a local CLI tool and does not exfiltrate files by itself unless later sent to the LLM, the capability is broader than necessary for a narrowly scoped chronic-disease review skill and can expose unintended local files if the operator points it at sensitive JSON content.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.