Back to skill

Security audit

unisound-followup-mgmt

Security checks for vulnerabilities and agentic risk

Overview

The skill performs the advertised medical follow-up task, but it can send full health reports and the API key to a configurable remote service without enforcing the promised de-identification or endpoint limits.

Review before installing. Use only with de-identified reports unless you have explicit authorization and privacy controls for sending medical data to the remote model provider. Avoid custom --base values unless they are trusted HTTPS endpoints, and use a dedicated, scoped appkey.

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:48
Finding

Unrestricted API Endpoint Allows Credential and Medical-Data Exfiltration

Content
View full analysis

Vulnerability Details

File Location: scripts/run.py:48-55 and scripts/run.py:221-229
Vulnerability Type: Unrestricted remote endpoint configuration and sensitive-data disclosure
Risk Level: High

Vulnerable Code

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)

The endpoint is accepted directly from the command line and passed to the caller without validation:

python
parser.add_argument("--base", default=DEFAULT_LLM_BASE, help=f"Internal model base URL (default: {DEFAULT_LLM_BASE}).")
parser.add_argument("--model", default=DEFAULT_LLM_MODEL, help=f"Model name (default: {DEFAULT_LLM_MODEL}).")
parser.add_argument("--timeout", type=int, default=0, help="HTTP timeout in seconds; 0 waits indefinitely.")
parser.add_argument("--output", default="", help="Output file path; the default is standard output.")
parser.add_argument("--encoding", default="utf-8", help="Input encoding; the default is UTF-8.")
return parser.parse_args()

# ...

llm = make_llm_caller(args.appkey, args.base, args.model, args.timeout)

Technical Analysis

The user-controlled --base argument determines the destination of the LLM request. The application attaches the supplied API key as a bearer token and includes the complete health report in the request body. It does not enforce HTTPS, validate the hostname, compare the destination against an allowlist, or prevent credentials intended for the default service from being forwarded to another origin.

This creates a credential-forwarding and sensitive-data exfiltration vulnerability. An attacker ...[truncated 1527 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the --base override from production distributions unless endpoint customization is an explicit requirement.
  2. Maintain an exact allowlist of trusted HTTPS origins and reject all other schemes, hosts, ports, user-information components, and redirects.
  3. Parse endpoints with urllib.parse.urlparse and require scheme == "https" and an approved normalized hostname.
  4. Ensure the HTTP client does not follow redirects that cross to a different origin while retaining the authorization header.
  5. Bind credentials to a specific service or audience where the authentication platform supports scoped or audience-restricted tokens.
  6. Do not send a credential configured for the default service to a custom endpoint. Require a separately supplied credential after an explicit warning if custom endpoints must be supported.
  7. Add automated tests covering HTTP endpoints, lookalike domains, embedded user information, unexpected ports, subdomain confusion, and cross-origin redirects.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.py:139
Finding

Documented De-identification Is Not Implemented Before Remote Transmission

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:27-29 and scripts/run.py:139-145, 224-229
Vulnerability Type: Unredacted transmission of sensitive medical and identity data
Risk Level: Medium

Vulnerable Code

The report is inserted directly into the remote model prompt:

python
prompt = f"""Please create a post-examination follow-up management plan based on the following health examination report.

[Health Examination Report]
{report_text.strip()}

Return JSON and a follow-up notice in the required format."""

The input is loaded and transmitted without an intervening sanitization or de-identification step:

python
try:
    report_text = load_input(input_path, args.encoding)
except Exception as e:
    print(f"Failed to read input file: {e}", file=sys.stderr)
    return 1

llm = make_llm_caller(args.appkey, args.base, args.model, args.timeout)
try:
    return run_followup(report_text, llm, args.output)

The skill documentation states that identifiable information must be de-identified before it is sent, but no code performs that operation.

Technical Analysis

load_input returns the report content, after which run_followup interpolates report_text.strip() directly into the user message sent to the remote LLM API. There is no local detection, masking, rejection, or confirmation step for names, national identification numbers, patient identifiers, telephone numbers, addresses, dates of birth, email addresses, or other direct identifiers.

Consequently, the privacy behavior does not enforce the documented de-identification expectation. A user can provide an ordinary health report containing both identity information and clinical findings, and the application will transmit all of it to the selected endpoint. The risk is increased by the unrestricted endpoint issue, but it also exists when the default endpoint is used because the remote service receives more personal info ...[truncated 1229 chars]

Remediation
View remediation

Remediation Suggestions

  1. Implement local de-identification before constructing or transmitting the LLM request.
  2. Detect and mask common identifiers, including names, patient numbers, government identifiers, telephone numbers, email addresses, street addresses, and exact dates of birth.
  3. Prefer a structured input schema that separates clinically necessary fields from identity fields, and never include identity fields in the outbound model request.
  4. Reject or require explicit confirmation for reports that still appear to contain direct identifiers after sanitization.
  5. Present a preview of the exact redacted content that will be transmitted.
  6. Document that data is sent to a remote service, identify the intended recipient, and accurately state any provider-side retention or logging behavior.
  7. Add tests using representative identifier formats and verify that outbound request bodies contain only de-identified data.
  8. Treat automated redaction as defense in depth rather than a perfect control; apply contractual, transport, access-control, and retention safeguards at the remote service as well.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill documentation advertises capabilities that require file read, file write, and network access, but it does not declare any explicit tool scope or permissions. This creates a governance gap: the runtime may grant broader access than reviewers or users expect, increasing the risk of unintended data access or exfiltration, especially because the skill handles sensitive medical report content and an appkey for a remote API.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The natural-language content of the skill, including description, instructions, and examples, is presented only in Chinese. Under the policy, forcing a specific language without user opt-in is a locale/language policy violation unless the constraint is explicitly documented and justified as region-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The system prompt mandates Chinese output, including a notification beginning with a specific Chinese marker, and the rest of the tool's user-facing text is likewise fixed in Chinese. This is a natural-language locale constraint without visible user opt-in or explanation that the skill is intended only for a Chinese-language environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code sends the full medical report text directly to a remote LLM service, which can include highly sensitive personal health information. In a healthcare context, transmitting PHI without explicit user notice, consent, minimization, or documented transfer controls creates significant privacy, compliance, and data-governance risk even if the endpoint is an internal vendor or company-hosted API.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.