Back to skill

Security audit

unisound-discharge-record

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent medical-document purpose, but it sends sensitive records and an API key to a configurable remote endpoint and can persist prepared medical text despite saying it does not.

Install only if you trust the publisher and deployment environment, can restrict the model endpoint to the intended internal HTTPS service, and have a policy for de-identifying medical records before use. Avoid --save-prepared with real patient data, treat outputs as draft documentation requiring clinician review, and do not pass arbitrary prompt files from untrusted sources.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run.py:223
Finding

Arbitrary Endpoint Can Receive Medical Records and API Credentials

Content
View full analysis
Any: data = json.dumps(payload, ensure_ascii=False).encode("utf-8") req = urllib.request.Request( url=url, data=data, method="POST", headers={"Content-Type": "application/json", **{key: value for key, value in headers.items() if value}}, ) try: opener = urllib.request.urlopen(req) if not timeout else urllib.request.urlopen(req, timeout=timeout) with opener as resp: body = resp.read().decode("utf-8", errors="replace") return json.loads(body) except urllib.error.HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace") raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc except urllib.error.URLError as exc: raise RuntimeError(f"Network error: {exc}") from exc def call_llm(prompt: str, *, base: str, model: str, appkey: str, timeout: int) -> str: url = f"{base.rstrip('/')}/chat/completions" headers = {"Authorization": f"Bearer {appkey}"} if appkey else {} payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0, } response = _http_post(url, payload, headers, timeout=timeout) ``` The destination is exposed through an unrestricted command-line argument: ```python parser.add_argument("--base", default=DEFAULT_LLM_BASE, help=f"Internal LLM base URL (default: {DEFAULT_LLM_BASE}).") ``` ### Technical Analysis The user-controlled `--base` value is concatenated with `/chat/completions` and used directly as the request destination. The implementation does not enforce HTTPS, validate the hostname against an a ...[truncated 1705 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
scripts/run.py:65
Finding

Untrusted JSON and Medical-Record Content Can Override LLM Instructions

Content
View full analysis
str: raw_prompt = str(payload.get("prompt") or "").strip() if raw_prompt: return raw_prompt records = payload.get("records") if records: records_text = [] current_section = "" for idx, record in enumerate(records): section = str(record.get("section") or "").strip() title = str(record.get("title") or "").strip() content = str(record.get("content") or record.get("text") or "").strip() if section and section != current_section: current_section = section records_text.append(f"#### {section}\n") if title: records_text.append(f"{title}:\n") if content: records_text.append(f"{content}\n") records_text.append("\n") return f"""You are a professional medical-record organization expert. Generate a standard discharge record strictly from the supplied source record according to the fixed rules: 1. Chief complaint: Extract the original chief complaint from the admission record. 2. Admission status: Generate using one of the documented alternatives. 3. Admission diagnosis: Extract the admission or preliminary diagnosis. 4. Treatment course: Summarize disease progression, examinations, treatment, and procedures chronologically. 5. Discharge diagnosis: Consolidate admission, revised, and supplemental diagnoses. 6. Discharge status: Derive the status from the final progress record. 7. Discharge instructions: Extract medication, follow-up examination, rehabilitation, lifestyle, and follow-up advice. Requirements: Remain completely faithful to the source, do not fabricate or add conditions, use standard medica ...[truncated 2529 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:202
Finding

Prepared Medical Records Can Be Persisted Without Protective File Controls

Content
View full analysis
None: save_dir = Path(output_path).parent if output_path else SCRIPT_DIR.parents[1] / "runs" / "discharge-record" save_dir.mkdir(parents=True, exist_ok=True) prepared_path = save_dir / f"{input_path.stem}.prepared.txt" prepared_path.write_text(payload_to_prepared_text(payload), encoding="utf-8") print(f"Prepared text saved to: {prepared_path}", file=sys.stderr) ``` The persistence operation is enabled here: ```python if args.save_prepared: save_prepared(payload, args.output, input_path) ``` The behavior conflicts with the statement in `SKILL.md:23` that input and intermediate results are not written to persistent local storage and are destroyed when the invocation ends. ### Technical Analysis The `--save-prepared` option writes the prepared prompt or medical-record text to a plaintext file. The implementation does not set restrictive permissions, encrypt the file, redact patient identifiers, enforce an approved storage directory, define retention, or remove the file after use. The destination can derive from the user-controlled output path. If no output path is provided, the record is stored under the default `runs/discharge-record` directory and remains there after execution. The filename is predictable from the input filename. Although the option must be explicitly enabled, the documentation's categorical non-persistence assurance may cause users to underestimate the privacy consequences. In shared workstations, containers with mounted volumes, backup systems, or permissively configured environments, another party may obtain the persisted record. ### Attack Path 1. A user or wrapper invokes the Skill with `--save-pre ...[truncated 969 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (7)

Ssd 1

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

If payload["prompt"] is present, it is returned verbatim and sent directly to the LLM, bypassing the safety framing and extraction rules otherwise added by build_prompt(). This allows any caller-controlled natural-language instructions to override intended behavior, potentially causing disclosure of raw records, policy bypass, or generation of unsafe/unreliable outputs in a medical context.

Content

No source excerpt is available for this finding.

Ssd 1

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

Untrusted record text is embedded directly into the same prompt channel as instructions, so adversarial content inside the medical document can act as prompt injection and compete with the task instructions. In this skill, that is especially risky because the source material may contain arbitrary text and the output is a medical discharge summary, where manipulated behavior can lead to data leakage, omission, fabrication pressure, or clinically unsafe summaries.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill documentation indicates capabilities to read files, write outputs, and make authenticated network requests to an internal medical model API, but it does not declare any explicit tool scope or permissions boundaries. In a medical-record workflow handling sensitive patient data, this absence of permission scoping increases the risk of overbroad file access, unintended persistence of PHI, and exfiltration to network destinations beyond what operators expect.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest description is written as a definitive Chinese-only workflow and output description, with no indication that users may choose another language or that the restriction is region-specific. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The prompt text is entirely prescriptive in Chinese and instructs the model to generate the discharge record in that language and style, without offering any language or locale choice. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Natural-language strings throughout the file, including the module description and prompt templates, require Chinese-language processing and output, but the script provides no option for user language or locale selection. Under the stated policy, forcing a specific language without user opt-in is a policy violation unless the locale restriction is explicitly justified.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends full patient record content to a remote HTTPS API in run() via call_llm() without any explicit consent gate, warning, minimization, or policy enforcement in the code path. Because the data is medical record content, the sensitivity is unusually high: unauthorized transmission, logging, retention, or cross-boundary processing could create a serious privacy and compliance incident even if the endpoint is an internal service.

Content

No source excerpt is available for this finding.