Back to skill

Security audit

unisound-recheck-reminder

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent medical reminder purpose, but it sends sensitive health reports to a remote model without implementing its promised de-identification and allows the API destination to be changed.

Install only if you are comfortable sending medical report contents and the supplied API key to the configured model endpoint. Do not include names, IDs, phone numbers, addresses, or other identifiers unless the skill is fixed to redact them locally first, and do not use a custom --base URL unless it is an approved HTTPS endpoint for that exact app key.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run.py:145
Finding
Medical Reports Are Transmitted to an External LLM Without De-identification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:77-91`, `scripts/run.py:145-151`, and `scripts/run.py:31-37` **Vulnerability Type**: Sensitive health information disclosure caused by missing de-identification **Risk Level**: High ### Complete Code Snippet ```python def load_input(path: Path, encoding: str) -> str: suffix = path.suffix.lower() if suffix == ".json": with path.open(encoding=encoding) as f: data = json.load(f) if isinstance(data, str): return data if isinstance(data, dict): for key in ("text", "content", "record", "input", "report"): v = data.get(key) if isinstance(v, str) and v.strip(): return v return json.dumps(data, ensure_ascii=False, indent=2) raise ValueError("JSON input must be a string or an object containing a supported report field.") return path.read_text(encoding=encoding) ``` ```python def run_recheck_reminder(report_text: str, llm, output_path: str = "") -> int: prompt = f"""Please generate a re-examination reminder list based on the following medical examination report. [Medical Examination Report] {report_text.strip()} Please strictly follow the required JSON and reminder output format.""" print("Generating re-examination reminder...") result = llm([sys_msg(SYSTEM_PROMPT), user_msg(prompt)]) ``` The original source contains Chinese user-facing prompt text; the material security behavior shown above is a direct English rendering of that prompt. The executable data flow is unchanged: `report_text.strip()` is interpolated verbatim. ```python def _http_post(url: str, payload: Dict[str, Any], headers: Dict[str, str], *, timeout: int = 0) -> 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", **headers}, ...[truncated 2482 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement de-identification before prompt construction: - Remove names, patient identifiers, telephone numbers, addresses, email addresses, exact birth dates, and other direct identifiers. - Replace necessary identifiers with per-request pseudonyms. - Detect common identifier formats using validated rules rather than relying only on free-form model instructions. 2. Apply data minimization: - Parse reports into an allowlisted structure containing only medically necessary findings. - Do not submit complete source documents when only abnormal findings are needed. - Reject or warn on inputs where de-identification cannot be performed reliably. 3. Add an explicit transmission notice and consent step describing: - The remote service receiving the data. - The categories of information transmitted. - Applicable retention, logging, and processing policies. 4. Add automated tests using reports containing representative identifiers and assert that those values are absent from serialized HTTP payloads. 5. Update `SKILL.md` so its privacy statement precisely matches implemented behavior. If the API retains or logs requests, disclose that behavior rather than claiming destruction solely on completion of the local invocation. 6. Avoid logging request bodies and ensure remote-side access controls, retention limits, and transport encryption are contractually and technically enforced. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run.py:47
Finding
Unrestricted API Base URL Can Exfiltrate the Bearer Credential and Medical Report<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:47-50`, `scripts/run.py:206`, and `scripts/run.py:219` **Vulnerability Type**: Unvalidated external destination and credential forwarding **Risk Level**: High ### 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}"} def llm(messages: List[Dict[str, str]]) -> str: payload = {"model": model, "messages": messages, "temperature": 0} resp = _http_post(url, payload, headers, timeout=timeout) try: return resp["choices"][0]["message"]["content"].strip() except (KeyError, IndexError, TypeError) as e: raise RuntimeError(f"Unexpected LLM response: {resp}") from e return llm ``` ```python parser.add_argument( "--base", default=DEFAULT_LLM_BASE, help=f"Internal model base URL (default: {DEFAULT_LLM_BASE})." ) ``` The original source uses a Chinese help description; the executable argument definition and default are represented completely above. ```python llm = make_llm_caller(args.appkey, args.base, args.model, args.timeout) ``` ### Technical Analysis The command-line `--base` argument accepts an arbitrary URL. `make_llm_caller()` appends `/chat/completions` and forwards the API key in an `Authorization: Bearer` header to that destination. The request body also contains the complete medical report embedded in the LLM messages. The implementation does not enforce HTTPS, validate or allowlist the hostname and port, prohibit local or private-network destinations, or verify that the selected origin is authorized to receive the credential. Consequently, anyone who can influence the invocation arguments can redirect both the secret API key and sensitive report data to a server they control. The unrestricted URL also creates an SSRF-lik ...[truncated 1976 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the configurable `--base` option from production builds if alternate providers are not required. 2. If configurability is required, enforce an explicit allowlist: - Require the `https` scheme. - Compare a normalized hostname and port against approved values. - Reject embedded credentials, fragments, unexpected ports, IP literals, loopback addresses, link-local addresses, and private-network addresses. - Resolve and validate DNS results to reduce DNS-rebinding risk. 3. Bind credentials to destinations: - Only attach the bearer header when the normalized origin exactly matches the approved API origin. - Use separate credentials for separate providers. - Prefer short-lived, narrowly scoped tokens over long-lived application keys. 4. Disable redirects or validate every redirect target before forwarding authorization headers. Never forward credentials across origins. 5. Add startup validation that fails closed before reading or transmitting a report when the destination is unapproved. 6. Add tests covering malicious values such as non-HTTPS URLs, alternate domains, deceptive subdomains, loopback hosts, private IP addresses, encoded hostnames, unexpected ports, and cross-origin redirects. 7. Rotate any API keys that may already have been used with untrusted base URLs, and review API access logs for misuse. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (5)

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
Allowing the caller to override the base URL means sensitive medical reports can be exfiltrated to any arbitrary server while still using the same workflow. In a healthcare skill, this greatly elevates risk because an operator can intentionally or accidentally route protected health data outside approved infrastructure with no validation or allowlist.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documents capabilities that involve reading local input files, writing output files, and calling a remote API, but it does not declare an explicit tool/permission scope. That creates a policy and containment gap: a host platform may grant broader-than-necessary access or fail to present clear consent boundaries for data handling, which is especially sensitive here because the input is medical report data and an app key is used for outbound network access.

Natural-Language Policy Violations

Medium
Confidence
87% confidence
Finding
The file consistently specifies the skill in Chinese, including the description, examples, and expected reminder text, but does not state that Chinese is optional or limited to a China-specific deployment context. Under the language/locale policy, forcing a single language without user opt-in can be a natural-language policy violation.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The function sends the full medical report text to a remote LLM service, which is sensitive health data and therefore high-risk personal information. In this skill's context, transmitting entire exam reports to an external API is more dangerous because the data may contain diagnoses, biomarkers, and identifiers, and the code does not minimize, redact, or constrain what is sent.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill processes sensitive medical report contents through a remote LLM call without any explicit user-facing notice that their health data will be transmitted off-process or off-device. This is dangerous in the healthcare context because users may reasonably assume local formatting/analysis, creating consent, compliance, and privacy exposure even if the endpoint is legitimate.

Static analysis

No suspicious patterns detected.