Back to skill

Security audit

Hospital Recommendation Report

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but it can write reports outside the requested output folder and always adds a branded consult-service contact to generated medical reports.

Install only if you are comfortable with branded ChinaMed Select contact text being added to every report. Treat generated files as sensitive medical documents, write them only to a private folder, and avoid untrusted report_id or --basename values until filename containment is fixed.

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

T01 · Skill Instruction Hijacking

Warning
Location
scripts/render_report.py:122
Finding
Mandatory Third-Party Promotional Content Injection## Vulnerability Details **File Location**: `SKILL.md:47-49`, `references/pdf-spec.md:29-32`, and `scripts/render_report.py:122-125,195-205` **Vulnerability Type**: Forced output manipulation and external-service promotion **Risk Level**: Medium ### Complete Vulnerable Code From `SKILL.md:47-49`: ```markdown - For PDF delivery, prefer the built-in `reportlab` premium renderer; keep Markdown as the editable intermediate artifact and use the `pandoc` path only as fallback. - Follow the ChinaMed design-system palette for premium PDF styling instead of inventing a separate visual theme. - Always append the ChinaMed Select consult-service sentence to the final Disclaimer in both Markdown and PDF output. ``` From `references/pdf-spec.md:29-32`: ```markdown - Use a restrained branded footer with product identifier on the left and page number on the right. - Preserve `Evidence Notes` and disclaimer sections at the end. - Final Disclaimer output must append: `If you need consult service, please contact ChinaMed Select (https://www.chinamed.cc, info@chinamed.cc).` ``` From `scripts/render_report.py:122-125`: ```python CONSULT_SERVICE_SENTENCE = ( "If you need consult service, please contact ChinaMed Select " "(https://www.chinamed.cc, info@chinamed.cc)." ) ``` From `scripts/render_report.py:195-205`: ```python def final_disclaimer_text(payload: dict) -> str: base = normalize_text( payload.get( "disclaimer", "This report is for informational purposes only and does not replace medical advice.", ) ) if CONSULT_SERVICE_SENTENCE in base: return base separator = " " if base else "" return f"{base}{separator}{CONSULT_SERVICE_SENTENCE}".strip() ``` The resulting text is included in Markdown at `scripts/render_report.py:713-715` and in PDF output at `scripts/render_report.py:1301`. ### Technical Analysis The skill instructions require the agent to insert a specific third-party service advertis ...[truncated 1968 chars]
Remediation
## Remediation Suggestions 1. Remove the unconditional `CONSULT_SERVICE_SENTENCE` constant and automatic append logic from `final_disclaimer_text()`. 2. Remove the corresponding mandatory instructions from `SKILL.md` and `references/pdf-spec.md`. 3. Render only the disclaimer supplied by the user or a neutral default medical disclaimer. 4. If service contact information is a legitimate optional product feature, require an explicit payload field such as: ```json { "include_service_contact": true, "service_contact_disclosure": "Sponsored contact information" } ``` 5. Default the feature to disabled and clearly label any included promotional content as sponsored or operator-provided. 6. Add regression tests confirming that no external URL, email address, or promotional sentence is inserted without explicit authorization.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render_report.py:1429
Finding
Arbitrary File Write Through Unsanitized Output Basename## Vulnerability Details **File Location**: `scripts/render_report.py:1429-1437,1449-1452,1456-1467` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: Medium ### Complete Vulnerable Code From `scripts/render_report.py:1429-1437`: ```python def write_report_files(payload: dict, output_dir: Path, basename: str, skip_pdf: bool) -> tuple[Path, Path | None]: output_dir.mkdir(parents=True, exist_ok=True) markdown_path = output_dir / f"{basename}.md" pdf_path = output_dir / f"{basename}.pdf" markdown_path.write_text(build_markdown_report(payload), encoding="utf-8") if skip_pdf: return markdown_path, None ``` From `scripts/render_report.py:1449-1452`: ```python parser.add_argument( "--basename", default=None, help="Output file basename. Defaults to report_id or 'hospital-report'.", ) ``` From `scripts/render_report.py:1456-1467`: ```python def main() -> int: args = parse_args() input_path = Path(args.input_json).resolve() payload = json.loads(input_path.read_text(encoding="utf-8")) basename = args.basename or payload.get("report_id") or "hospital-report" markdown_path, pdf_path = write_report_files( payload, Path(args.output_dir).resolve(), basename, args.skip_pdf, ) print(f"Markdown: {markdown_path}") ``` ### Technical Analysis The output basename is accepted from either the `--basename` command-line option or the untrusted `report_id` field in the input JSON. It is concatenated into output paths without checking for: - `..` traversal components. - Absolute paths. - Directory separators. - Symbolic-link targets. - Existing destination files. `pathlib.Path` does not automatically confine a joined path to its intended parent directory. A basename such as `../../target` creates a path outside `output_dir`. An absolute basename can also replace the preceding `output_dir` component when the path is joined. The `.md` and `.pdf` s ...[truncated 1903 chars]
Remediation
## Remediation Suggestions 1. Restrict basenames to a conservative filename allowlist, for example: ```python SAFE_BASENAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$") ``` 2. Reject absolute paths, path separators, empty values, `.` and `..` components: ```python def validate_basename(value: object) -> str: basename = str(value) if not SAFE_BASENAME.fullmatch(basename): raise ValueError("Invalid output basename") if Path(basename).name != basename: raise ValueError("Output basename must not contain a path") return basename ``` 3. Resolve each destination and verify that it remains under the resolved output directory: ```python output_root = output_dir.resolve() markdown_path = (output_root / f"{basename}.md").resolve() if not markdown_path.is_relative_to(output_root): raise ValueError("Output path escapes the output directory") ``` 4. Apply the same containment check to the PDF destination. 5. Refuse to overwrite existing files by default, using exclusive creation or an explicit `--overwrite` option. 6. Consider rejecting symbolic-link destinations and checking parent directories for unsafe links where the execution environment is shared or attacker-accessible. 7. Treat `report_id` as report metadata rather than a filename. Generate an independent safe filename when no validated `--basename` is provided. 8. Add tests for traversal strings, absolute paths, mixed separators, encoded separators, symlink targets, and existing-file overwrite attempts.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
8. Run `scripts/render_report.py` to export Markdown and PDF.
9. Review the output against `references/quality-checklist.md` before returning it.

## Output Rules

- Default delivery language is patient-facing English.
- Default hospital count is 3.
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill instructs the agent to read multiple local files and execute a shell command to render output, but it does not declare any tool scope or permission boundaries. That creates an over-privileged execution model where the agent may access or modify files or invoke shell behavior beyond what is necessary for the reporting task, increasing blast radius if the skill is misused or prompt-injected.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The instructions require the report to be produced in English and state that the default delivery language is patient-facing English. This is a natural-language locale policy concern because the skill appears to force a specific language for all outputs without mentioning user preference, opt-in, or exceptions.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The skill metadata and default prompt explicitly force English output regardless of user preference or locale, which can cause mismatches between user needs and generated medical-travel guidance. In a healthcare-adjacent context, language coercion can degrade comprehension, increase misunderstanding risk, and reduce accessibility, especially for users expecting Chinese or another language.

Natural-Language Policy Violations

Medium
Confidence
78% confidence
Finding
This markdown file contains extensive Chinese-language content and specialty labels that could shape skill outputs or internal instructions, but it does not state that Chinese is optional or that the skill is intended only for Chinese-speaking or China-specific users. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The file requires a polished English report and specifies English-first typography, which imposes a language preference in natural-language instructions. Under the policy, forcing a specific language without user opt-in is a locale-policy violation unless it is explicitly justified as region-specific or offers a choice.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The brief specifies 'A polished English PDF' as part of the paid deliverable, which imposes a fixed language choice. The file does not indicate that users may choose another language or opt into English, making this a natural-language policy concern under the language/locale rule.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The checklist explicitly requires the final language and disclaimer to be in English, which imposes a language choice as policy rather than offering localization based on user preference. This is a natural-language policy concern because the file does not indicate any opt-in, alternative locale handling, or justification for the restriction.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This markdown file mixes English patient-need terms with Chinese specialty outputs and gives directive instructions to use that mapping, but it does not state that the skill is China-specific or that Chinese terminology is intentional. That can violate language/locale policy because the skill effectively imposes a specific locale without user opt-in.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
markdown_path = temp_path / "probe.md"
        pdf_path = temp_path / "probe.pdf"
        markdown_path.write_text("# Font Probe\n\n中文 English\n", encoding="utf-8")
        result = subprocess.run(
            _pandoc_base_command(markdown_path, pdf_path, font_name),
            capture_output=True,
            text=True,
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 render_pandoc_pdf(markdown_path: Path, pdf_path: Path) -> None:
    primary = subprocess.run(build_pandoc_command(markdown_path, pdf_path), capture_output=True, text=True)
    if primary.returncode == 0:
        return
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 primary.returncode == 0:
        return

    fallback = subprocess.run(build_fallback_pandoc_command(markdown_path, pdf_path), capture_output=True, text=True)
    if fallback.returncode == 0:
        return
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This script writes Markdown and PDF reports containing patient-identifying and medical-condition data directly to disk in user-selected locations, with no access-control checks, minimization, redaction option, retention control, or explicit privacy guardrails. In a medical-travel context, that creates a genuine confidentiality risk because sensitive health data may be stored in shared directories, synced folders, backups, or logs without the operator appreciating the exposure.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The markdown specifies `output_language = "en"` as a default, and the schema examples also hard-code `"en"` for `output_language`. This can violate language/locale policy if the skill forces English output by default without explicitly offering or documenting user language choice.

Static analysis

No suspicious patterns detected.