Back to skill

Security audit

Skill Health

Security checks for vulnerabilities and agentic risk

Overview

This skill locally analyzes user-selected wearable health exports and writes optional JSON reports, with no evidence of hidden network access, persistence, or unrelated authority.

Install this only if you are comfortable letting it process sensitive local health exports. Use trusted, reasonably sized CSV or ZIP inputs, choose output folders deliberately, and treat any illness, apnea, bradycardia, arrhythmia-like, burnout, or overtraining alerts as non-diagnostic signals to discuss with a qualified professional when relevant.

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

T09 · Insecure Skill Coding Practices

Warning
Location
pkg/skill_health/load.py:49
Finding
Unbounded CSV and Compressed ZIP Processing Can Cause Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `pkg/skill_health/load.py:33-57`, with the ZIP loading path invoked at `pkg/skill_health/load.py:362-383` **Vulnerability Type**: Uncontrolled resource consumption through unbounded input processing **Risk Level**: Medium ### Vulnerable Code ```python def _load_csv(path: Path) -> pd.DataFrame | None: """Read a CSV file from disk. Returns None if missing or empty.""" if not path.exists(): return None try: raw_df = pd.read_csv(path) except Exception as e: logger.warning("Could not read %s: %s", path, e) return None if raw_df.empty: return None return raw_df def _load_csv_from_zip( zip_file: zipfile.ZipFile, member_name: str ) -> pd.DataFrame | None: """Read a CSV member from an open ZIP. Returns None if missing or empty.""" if member_name not in zip_file.namelist(): return None try: with zip_file.open(member_name) as f: raw_df = pd.read_csv(f) except Exception as e: logger.warning("Could not read %s from ZIP: %s", member_name, e) return None if raw_df.empty: return None return raw_df ``` The affected ZIP-loading path is: ```python if data_path.suffix.lower() == ".zip": with zipfile.ZipFile(data_path, "r") as zip_file: steps_df = _normalize_and_dedupe_steps( _load_csv_from_zip(zip_file, "steps.csv") ) heart_rate_df = _normalize_and_dedupe_heart_rate( _load_csv_from_zip(zip_file, "heart_rate.csv") ) calories_df = _normalize_and_dedupe_calories( _load_csv_from_zip(zip_file, "calories.csv") ) sleep_sessions_df = _normalize_sleep( _load_csv_from_zip(zip_file, "sleep_sessions.csv") ) exercise_sessions_df = _normalize_exercise( _load_csv_from_zip(zip_file, "exercise_sessions.csv") ) oxygen_saturation_df = _normalize_oxy ...[truncated 2862 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Enforce ZIP metadata limits before opening members** - Retrieve each expected member through `ZipFile.getinfo()`. - Reject members whose `file_size` exceeds a defined expanded-size limit. - Reject archives whose total expanded size exceeds an aggregate limit. - Reject suspicious compression ratios calculated from `file_size` and `compress_size`. - Handle zero-byte compressed sizes safely when calculating ratios. 2. **Restrict accepted archive contents** - Permit only the expected CSV member names. - Reject duplicate member names, encrypted members, and unsupported entry types. - Continue streaming members rather than extracting them to disk. 3. **Apply equivalent controls to ordinary CSV files** - Check `Path.stat().st_size` before calling `pandas.read_csv()`. - Reject files exceeding a documented maximum size. - Ensure that symbolic links and caller-selected input paths are handled according to the deployment trust model. 4. **Use bounded CSV parsing** - Parse large inputs with `pandas.read_csv(..., chunksize=...)`. - Track cumulative rows and bytes and abort when configured limits are reached. - Validate required columns early so irrelevant or malformed data is not processed unnecessarily. - Select only required columns and specify appropriate data types where practical to reduce memory use. 5. **Add runtime containment** - Run analysis with process-level memory, CPU, and execution-time limits. - Return a clear validation error when an input exceeds limits rather than attempting to process it. - Avoid exposing detailed host resource information in error messages. 6. **Add adversarial tests** - Test oversized plain CSV files. - Test highly compressed ZIP members. - Test archives containing multiple large expected members. - Verify that rejected inputs fail before substantial decompression or DataFrame allocation occurs. ]]>
Vulnerability Patterns
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The supplied code is a data ingestion and normalization layer, not the described analysis/reporting component. Its behavior is limited to reading CSVs or ZIP members, parsing timestamps, filtering invalid values, deduplicating some records, and packaging normalized DataFrames. While this supports a health analysis skill, it does not itself perform the declared primary functions of analysis and compact JSON report production across multiple temporal views, nor does it implement alerting or explanation features. No undeclared dangerous capability is evident, but the declared description materially overstates what this code chunk does.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill documentation describes running local scripts that read input data, may access environment-dependent runtime behavior, and can write JSON output files, but it does not declare any explicit tool scope or permissions. This creates an authorization gap: an agent or reviewer cannot easily tell what filesystem access is intended, which increases the risk of over-broad file reads/writes or accidental exposure of sensitive health data.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This code generates health-related alerts such as 'Pre-illness signature' and 'Possible sleep apnea' with predictive or diagnostic wording, but it contains no built-in disclosure that the output is informational, non-medical, and potentially affected by missing or low-quality wearable data. In a health-analysis skill, users may reasonably treat these alerts as medical guidance, increasing the risk of inappropriate self-diagnosis, delayed care, or undue alarm.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code generates medically suggestive outputs such as 'arrhythmia-like', 'high rest HR', and 'possible bradycardia' based on simple thresholds, but the metric definitions do not consistently include a strong non-diagnostic warning at the point of output. In a health-analysis skill, users or downstream agents may over-trust these flags as clinical conclusions, creating risk of inappropriate self-triage or delayed care.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The inactivity logic can set alerts from absent or sparse step data, including treating an empty window during active hours as prolonged inactivity, without attaching an explicit uncertainty warning or minimum data coverage requirement. This can produce misleading behavioral or health inferences from missing telemetry, which is especially risky in a wearable-health context where sync gaps and sensor dropouts are common.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code ingests detailed health records including heart rate, sleep, exercise, oxygen saturation, and calories, which are highly sensitive personal data. While the docstrings describe the mechanics of loading files, there is no user-facing warning, confirmation, or explicit disclosure in this code about handling sensitive health information.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The top-level docstring states 'Run sleep analysis for the last 24 hours', but the CLI parser defines '--window-hours' with a default of 30 and the report is built using that value. This is a direct contradiction between documentation and actual behavior rather than a mere omission.

Intent-Code Divergence

Low
Confidence
97% confidence
Finding
The argparse description advertises 'Sleep analysis for the last 24h', yet the script defaults '--window-hours' to 30 and passes that into 'build_sleep_report'. Users relying on the CLI help would be misled about the actual analysis scope.

Static analysis

No suspicious patterns detected.