Back to skill

Security audit

AI-Cardiac-Rehab

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent cardiac rehab web app, but it handles sensitive medical data and gives exercise guidance with safety and privacy gaps that warrant careful review.

Review this before installing or using with real patient data. It should be treated as experimental decision support only, not medical authority. Use a clinician-reviewed symptom vocabulary, fix the emergency matching issue, self-host frontend assets or disclose third-party requests, pin dependencies, and add strong local data protections before deployment with sensitive health records.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
app.py:217
Finding
Sensitive Medical Records Stored in Plaintext<![CDATA[ ## Vulnerability Details **File Location**: `app.py:217-239` **Vulnerability Type**: Plaintext storage of sensitive health information **Risk Level**: Medium ### Vulnerable Code ```python CREATE TABLE IF NOT EXISTS profiles ( user_id INTEGER PRIMARY KEY, age INTEGER, gender TEXT, heart_disease TEXT, ef INTEGER, comorbid TEXT, resting_hr INTEGER, resting_bp_sys INTEGER, resting_bp_dia INTEGER, medications TEXT, updated_at TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id) ); CREATE TABLE IF NOT EXISTS daily_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, log_date DATE, symptoms TEXT, hr INTEGER, bp_sys INTEGER, bp_dia INTEGER, exercise_min INTEGER, exercise_type TEXT, notes TEXT, UNIQUE(user_id, log_date), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` The application subsequently writes these records directly to SQLite without encryption: ```python c.execute( '''INSERT OR REPLACE INTO profiles (user_id, age, gender, heart_disease, ef, comorbid, resting_hr, resting_bp_sys, resting_bp_dia, medications, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)''', (session['user_id'], age, request.form.get('gender'), request.form.get('heart_disease'), ef, request.form.get('comorbid'), resting_hr, resting_bp_sys, request.form.get('resting_bp_dia'), request.form.get('medications'))) ``` ```python c.execute( '''INSERT OR REPLACE INTO daily_logs (user_id, log_date, symptoms, hr, bp_sys, bp_dia, exercise_min, exercise_type, notes) VALUES (?,?,?,?,?,?,?,?,?)''', (session['user_id'], log_date, request.form.get('symptoms'), hr, bp_sys, bp_dia, exercise_min, request.form.get('exercise_type'), '')) ``` ### Technical Analysis The SQLite database contains diagnoses, symptoms, medications, ejection fraction, blood pressure, heart rate, and exercise history in plaintext. The applicatio ...[truncated 1642 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the database in a dedicated application-data directory that is not web-accessible. 2. Create the directory with owner-only permissions and enforce mode `0600` on the database and associated SQLite journal or WAL files. 3. Run the service under a dedicated, unprivileged operating-system account. 4. Use database or field-level encryption for sensitive medical fields when the deployment threat model includes local filesystem compromise. 5. Keep encryption keys outside the database and source tree, preferably in an operating-system secret store or managed key service. 6. Minimize collected data and define explicit retention and deletion controls. 7. Encrypt backups and restrict access to backup locations. 8. Document that SQLite data is sensitive medical information and provide secure deployment guidance. 9. Test database, journal, backup, and export permissions as part of deployment validation. ]]>

other

Error
Location
app.py:88
Finding
Emergency Symptom Detection Does Not Match the Documented User Input<![CDATA[ ## Vulnerability Details **File Location**: `app.py:88-98` **Vulnerability Type**: Clinical safety validation failure **Risk Level**: High ### Vulnerable Code ```python def check_symptom_alerts(logs): """基于症状的红色/橙色预警""" red_flags = [] orange_flags = [] for log in logs: sym = (log['symptoms'] or '').lower() if any(x in sym for x in ['胸痛', '胸闷压榨', '呼吸困难静息', '晕厥', '黑朦']): red_flags.append(log['date']) elif any(x in sym for x in ['心悸', '疲劳异常', '头晕活动时']): orange_flags.append(log['date']) return red_flags, orange_flags ``` The input form suggests a different symptom value: ```html <label>症状(可多选,用逗号分隔)</label><input name="symptoms" class="form-control" placeholder="胸痛,呼吸困难,心悸,疲劳,头晕,无"> ``` ### Technical Analysis The input form explicitly suggests the value `呼吸困难` (“shortness of breath”), but the red-alert detector only recognizes the more specific substring `呼吸困难静息` (“shortness of breath at rest”). Because the suggested value is not a substring of the detector's longer expected phrase, a user who follows the UI guidance can report shortness of breath without triggering the emergency path. Symptoms are accepted as unrestricted text and evaluated through exact substring matching. There is no canonicalization layer, structured symptom identifier, synonym mapping, or fallback review for unrecognized symptom text. This makes the safety behavior dependent on users entering the precise phrases embedded in the source code. This conflicts with the Skill's declared behavior that high-risk symptoms automatically block exercise. ### Attack Path 1. A patient experiences shortness of breath. 2. The patient enters `呼吸困难`, exactly as suggested by the form placeholder. 3. `check_symptom_alerts()` checks for `呼吸困难静息`, which does not match the submitted text. 4. No red flag is added for that symptom. 5. `ai_safe_cardiac_rehab()` does not activate its symptom-based contraindication branch. 6. If no ot ...[truncated 768 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace unrestricted symptom text with structured checkboxes or select controls backed by stable symptom identifiers. 2. Include `呼吸困难` in the emergency mapping and explicitly define whether all shortness of breath or only shortness of breath at rest should trigger urgent action. 3. Normalize punctuation, spacing, case, and common synonyms before evaluating any retained free-text input. 4. Treat unrecognized or ambiguous potentially serious symptoms conservatively and direct the patient to clinical review. 5. Ensure that the UI and backend use the same canonical symptom vocabulary. 6. Add automated tests for every symptom displayed by the UI and documented in the Skill description. 7. Include negative, synonym, combined-symptom, and punctuation-separated test cases. 8. Have qualified clinicians review the emergency and contraindication rules before deployment. 9. Clearly present the application as decision support rather than a substitute for emergency assessment. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Unbounded and Non-Reproducible Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1` **Vulnerability Type**: Unsafe dependency version constraints **Risk Level**: Low ### Vulnerable Code ```text flask>=2.0 ``` The same broad constraint is present in `package.json`: ```json "dependencies": { "flask": ">=2.0" } ``` The installation documentation also resolves packages without a lock file or hash verification: ```bash pip install flask python app.py ``` ```bash pip install gunicorn gunicorn -w 2 -b 127.0.0.1:5000 app:app ``` ### Technical Analysis The lower-bound-only constraint permits any future Flask version. The Gunicorn installation instructions do not specify a version at all. The project contains no lock file and no cryptographic hashes for downloaded Python distributions. The named packages and default package source are not inherently suspicious, and no dependency-confusion or typosquatting package was identified. The risk arises because installations are not reproducible: package behavior can change after the Skill has been reviewed, and a future compromised, incompatible, or maliciously modified allowed release could be installed automatically. ### Attack Path 1. A user follows the documented installation process. 2. The package manager resolves the newest release satisfying `flask>=2.0`, or the newest available Gunicorn release. 3. A future allowed release contains a security regression, incompatible behavior, or compromised code. 4. The affected package is downloaded and installed without lock-file or hash verification. 5. Package code executes during installation or when the application starts. 6. The compromised dependency receives the permissions of the user running the installation or service. This is a supply-chain exposure rather than evidence that the currently named dependencies are malicious. ### Impact Assessment The maximum privilege scope is the privilege of the account performing installation or running the web service. In a least- ...[truncated 306 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin Flask and Gunicorn to reviewed versions. 2. Generate and commit a dependency lock file. 3. Require cryptographic hashes for resolved distributions, for example through a hash-locked requirements file. 4. Install only from an explicitly configured trusted package index. 5. Use automated vulnerability and provenance scanning in the update process. 6. Review dependency release notes and test updates before changing pinned versions. 7. Keep `requirements.txt`, `package.json`, and installation documentation consistent. 8. Run installation and the application under a dedicated non-administrative account. 9. Define a regular process for updating pins so reproducibility does not prevent timely security patching. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (10)

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The API spec describes an AI system that ingests sensitive cardiac health data and produces automated risk stratification, exercise prescriptions, medication reminders, and safety warnings, but it provides no warning about the sensitivity of patient data, the limits of automated medical recommendations, or the need for clinician oversight. In a cardiac rehabilitation context, omitted cautions can lead users or integrators to over-trust the system, increasing the chance of unsafe exercise guidance, missed urgent care escalation, or privacy mishandling of health information.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The manifest description and the full user-facing SKILL.md content are written in Chinese, and the file does not indicate that the skill is region-specific or that users may choose another language. This can violate a language/locale policy when skills are expected to avoid forcing a specific language without user opt-in.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The entire user-facing guide is written only in Chinese, with no indication that language selection is optional or that the skill is intentionally limited to a Chinese-speaking audience. This creates a natural-language locale policy issue because it imposes a specific language without user opt-in.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The documented privacy assurance is materially inconsistent with actual behavior: external CDN-hosted assets cause outbound requests despite the statement that data is not uploaded to the cloud. In a healthcare-facing app, this mismatch can create privacy, compliance, and trust risks because users may rely on the promise of fully local operation when entering sensitive health information.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The file’s natural-language UI, warnings, and instructions are entirely in Chinese, beginning with the module description and continuing throughout the application. There is no indication that users may choose another language, nor is there a documented justification that this skill is intended only for a Chinese-language or region-specific audience.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The code claims that data stays local and is not uploaded to the cloud, but the HTML template loads Bootstrap and Chart.js from jsDelivr CDNs. Even if patient data is not explicitly sent in requests, browser visits to a third-party CDN disclose metadata such as client IP address, user agent, access timing, and referrer, which is especially sensitive in a medical context because it can reveal use of a cardiac rehab application.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The package description is written entirely in Chinese, which indicates a language-specific presentation in the skill metadata. There is no accompanying indication that the skill is region-specific or that users can opt into this locale, so it may conflict with a language-choice policy.

Unpinned Dependencies

Low
Category
Supply Chain
Content
flask>=2.0
Confidence
96% confidence
Finding
The dependency is specified as `flask>=2.0`, which allows any future major or minor release to be installed and makes builds non-reproducible. This increases supply-chain risk and can unexpectedly pull in vulnerable or breaking versions of Flask, which is especially concerning for a healthcare-oriented web application that may process sensitive patient data.

Unverifiable Dependency: flask has 10 known advisory(ies) (CVE-2025-47278 (Flask uses fallback key instead of current signing key); CVE-2018-1000656 (Flask is vulnerable to Denial of Service via incorrect encoding of JSON data); CVE-2019-1010083 (Pallets Project Flask is vulnerable to Denial of Service via Unexpected memory u) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
91% confidence
Finding
Because the manifest does not pin Flask to a specific version, it is impossible to verify whether the deployed package includes fixes for known Flask advisories. In practice, this means the application may install a release affected by denial-of-service or signing-key related issues, creating avoidable uncertainty in a medical web application's security posture.

Static analysis

No suspicious patterns detected.