T09 · Insecure Skill Coding Practices
- Location
scripts/run.py:95- Finding
Prompt Injection Through Untrusted Patient Data
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill broadly matches a patient follow-up reminder, but it handles sensitive medical files and sends patient data to a remote model without enough controls, and it can load unaudited code from outside the skill.
Review before installing in any real patient or regulated healthcare setting. Use only non-sensitive test data unless the remote model provider, retention terms, consent process, and data-minimization requirements are approved. Prefer JSON-only inputs, avoid free-form notes with identifiers, avoid --save-prepared for real records, and remove or verify the external _shared preprocessing fallback before deployment.
scripts/run.py:95Prompt Injection Through Untrusted Patient Data
scripts/run.py:221Unverified Execution of a Python Module Outside the Audited Skill Directory
scripts/run.py:30External Disclosure of Patient Health Information Without Data Minimization
The declared purpose is a narrow follow-up reminder, but the documented behavior expands into general document ingestion, OCR, office conversion, and preprocessing. This mismatch is dangerous because it creates hidden attack surface and can cause users or reviewers to underestimate the amount of sensitive medical data being collected, transformed, and potentially exposed.
The file implements a broad document ingestion and extraction utility that is not aligned with the stated patient follow-up reminder purpose. This capability expansion materially increases attack surface by adding parsing of PDFs, Office files, spreadsheets, JSON, and OCR, including execution of external converters, without clear necessity for the skill's mission.
The code sends patient medical context to an external HTTP API without any visible user warning, consent flow, or policy guardrails. Because the transmitted fields can reveal health status and treatment history, this creates a meaningful confidentiality and compliance risk, especially in healthcare workflows.
The skill advertises broad capabilities including environment access, file read/write, network access, and shell execution, but does not declare any explicit tool scope or permission boundaries. In a medical reminder context, this lack of least-privilege controls increases the chance that a simple reminder skill can access sensitive files, invoke external tools, or transmit data beyond what users would reasonably expect.
The skill accepts unrestricted documents and images, extracts their contents via OCR/parsers, and then generates user-facing output through a remote LLM API. In a patient follow-up setting, that means arbitrary uploaded medical content may be collected and transmitted off-process, greatly increasing confidentiality risk and enabling overcollection far beyond the minimum needed for reminder logic.
The skill explicitly states that API inference is mandatory and that the user's appkey is used to send content to a remote model endpoint, even though the business purpose is a simple reminder/overdue calculation that can be done locally. Forcing remote transmission of patient follow-up data without necessity increases privacy exposure and creates avoidable dependency on an external service for sensitive healthcare processing.
The documentation notes transmission of medical input to an internal model API but does not prominently warn users about privacy, retention, sharing, or consent implications. In a healthcare context, insufficient disclosure can lead to unauthorized handling of sensitive patient information and undermines informed use of the skill.
The module docstring is written entirely in Chinese and the OCR logic later prefers Chinese plus English when available, indicating a language-specific default without any visible user choice or justification in this file. Under the policy, forcing a specific language or locale without opt-in is a natural-language policy concern.
The code invokes external office, PDF, and OCR executables even though the skill is described as a patient follow-up/reminder capability. In context, this mismatch makes the behavior more suspicious because it enables processing complex untrusted content and expands system interaction beyond what users or reviewers would reasonably expect from a reminder workflow.
The code silently performs subprocess-based conversion and OCR on user-supplied files without any visible disclosure, warning, or consent mechanism in this component. In a medical context, that can violate user expectations and complicate privacy, audit, and risk review because sensitive patient documents may be handed to external tools unexpectedly.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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 calls execute external commands. Without careful input validation, this enables command injection.
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 calls execute external commands. Without careful input validation, this enables command injection.
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,
)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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 calls execute external commands. Without careful input validation, this enables command injection.
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,
)
The skill transmits patient follow-up details, including disease type, visit dates, and notes, to an external LLM endpoint. In a medical context this is sensitive health information, and the manifest/code shown does not provide clear disclosure, consent handling, minimization, or contractual/privacy controls for third-party processing.
The system prompt instructs the model entirely in Chinese and requires markdown output in a specific Chinese-language tone, with no mechanism for user language selection. This is a natural-language locale constraint that appears mandatory rather than optional or region-justified.
Accepting the API credential via a command-line argument can expose the secret through shell history, process listings, job logs, and orchestration metadata. In shared systems this can allow other users or services to recover the token and make unauthorized requests to the medical LLM backend.
The comment says this block falls back to shared document preprocessing, implying equivalent behavior after preprocessing succeeds. However, line L243 calls build(data, args.appkey) even though build requires (data, today, appkey), so the fallback path does not actually perform the documented reminder generation behavior and will instead error or misbehave.