Back to skill

Security audit

apple-health-skills

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a local health-data query helper, but it handles sensitive health data with weak live-heart-rate scoping and broad .env loading that warrant review before use.

Review this before installing if your health database may contain multiple users or shared data. Use it only with a trusted local database, avoid storing unrelated secrets in the same .env file, and do not run live-heart-rate queries against shared tables unless a user-level filter is added.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/query_live_hr.py:81
Finding

Live heart-rate queries do not enforce user-level data isolation

Content
View full analysis
= ?"] params: list[object] = [since] if args.session_id: clauses.append("session_id = ?") params.append(args.session_id) if args.device_id: clauses.append("device_id = ?") params.append(args.device_id) where = " AND ".join(clauses) sql = f""" SELECT session_id, seq, ts, value, unit, source_kind, source_vendor, device_id, source_device_name, received_at FROM live_events WHERE {where} ORDER BY ts DESC LIMIT ? """ params.append(args.limit) rows = conn.execute(sql, params).fetchall() ``` ### Technical Analysis The query applies time, session, and device filters but does not require a user identifier or include a `user_id = ?` ownership condition. This differs from `query_health.py` and `query_whoop.py`, which require `AHB_USER_ID` or `--user-id` and scope their SQL queries accordingly. If `live_events` contains records for multiple users, the documented default invocation returns recent records belonging to every user. Optional `session_id` and `device_id` parameters narrow the result but do not establish authorization. The selected fields include heart-rate values, precise event timestamps, session identifiers, and device metadata. The dynamically assembled SQL is not itself an SQL-injection issue because the clause strings are fixed and user-controlled values are parameterized. The vulnerability is the absent ownership boundary. ### Attack Path 1. Multiple users' live heart-rate events are stored in the same SQLite `live_events` table. 2. A caller obtains permission to invoke the Skill or directly execute `scripts/query_live_hr.py`. 3. The caller runs the documented command without an ownership filter: ```bash python scripts/query_live_hr ...[truncated 967 chars]
Remediation
View remediation
= ?"] params = [args.user_id, since] ``` 3. Reject execution when no user identifier is available. 4. Ensure the user identifier is derived from an authenticated context rather than accepted solely as an arbitrary caller-controlled value. 5. Verify that the database schema associates every `live_events` row with a non-null owner. 6. Add tests with at least two users to confirm that one user's query never returns another user's events. 7. Omit `device_id`, `source_device_name`, and similar metadata by default. Return them only when required by the user's request. 8. Where feasible, use separate databases or database views per user to provide defense in depth. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/query_health.py:14
Finding

Query scripts load all repository secrets from the .env file without need

Content
View full analysis
None: """Parse a simple KEY=VALUE .env file and set missing env vars.""" if not path.exists(): return for line in path.read_text().splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, value = line.partition("=") key = key.strip() value = value.strip() if key and key not in os.environ: os.environ[key] = value # Load .env from repo root so the script works without manually exporting vars. _repo_root = Path(__file__).resolve().parent.parent _load_dotenv(_repo_root / ".env") ``` In `scripts/query_live_hr.py`: ```python def _load_dotenv(path: Path) -> None: if not path.exists(): return for line in path.read_text().splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, value = line.partition("=") key = key.strip() value = value.strip() if key and key not in os.environ: os.environ[key] = value _repo_root = Path(__file__).resolve().parent.parent _load_dotenv(_repo_root / ".env") ``` In `scripts/query_whoop.py`: ```python def _load_dotenv(path: Path) -> None: if not path.exists(): return for line in path.read_text().splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, value = line.partition("=") key = key.strip() value = value.strip() if key and k ...[truncated 2502 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The code’s core function is querying a local SQLite database and calculating health features. That partially aligns with 'Apple Health summaries' and 'trends,' but the declared description specifically says the skill should validate collector connectivity over HTTP and fetch fresh data with shell commands before responding. None of that is present: there are no network calls, no subprocess or shell execution, and no collector freshness validation. The code also does not generate alerts, check-ins, or coaching guidance; it only emits structured JSON summaries. Therefore the declared description materially overstates and misrepresents the actual behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description says this skill should validate a collector over HTTP and fetch fresh Apple Health data via shell commands before producing coaching-oriented summaries or alerts. The actual code only queries an SQLite database file, computes heart-rate statistics, groups events by session, and prints JSON. This is a materially different implementation and capability set: it uses a different data source/interface, omits the declared HTTP and shell-fetch behaviors, and does not provide the promised coaching/check-in functionality. While the code is related to health data, the primary behavior is much narrower and inconsistent with the declared operational method.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description and the actual code differ materially in both data source and behavior. The description centers on Apple Health data from a local collector with HTTP connectivity validation and shell-based refresh steps. The code instead queries local SQLite tables named for Whoop data (whoop_recoveries, whoop_sleeps, whoop_cycles, whoop_workouts), computes statistical summaries, and emits JSON. There is no HTTP access, no shell command execution, and no Apple Health-specific handling. These are core functional differences, not minor implementation details.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/query_health.py (reported line 16)May include surrounding context.

python
def _load_dotenv(path: Path) -> None:
    """Parse a simple KEY=VALUE .env file and set missing env vars."""
    if not path.exists():
        return
    for line in path.read_text().splitlines():

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/query_health.py (reported line 30)May include surrounding context.

python
def _load_dotenv(path: Path) -> None:
    """Parse a simple KEY=VALUE .env file and set missing env vars."""
    if not path.exists():
        return
    for line in path.read_text().splitlines():

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/query_health.py (reported line 183)May include surrounding context.

python
def _load_dotenv(path: Path) -> None:
    """Parse a simple KEY=VALUE .env file and set missing env vars."""
    if not path.exists():
        return
    for line in path.read_text().splitlines():

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/query_whoop.py (reported line 79)May include surrounding context.

python
def _load_dotenv(path: Path) -> None:
    """Parse a simple KEY=VALUE .env file and set missing env vars."""
    if not path.exists():
        return
    for line in path.read_text().splitlines():

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/query_health.py (reported line 32)May include surrounding context.

python
# Load .env from repo root so the script works without manually exporting vars.
_repo_root = Path(__file__).resolve().parent.parent
_load_dotenv(_repo_root / ".env")


def parse_args() -> argparse.Namespace:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/query_live_hr.py (reported line 36)May include surrounding context.

python
# Load .env from repo root so the script works without manually exporting vars.
_repo_root = Path(__file__).resolve().parent.parent
_load_dotenv(_repo_root / ".env")


def parse_args() -> argparse.Namespace:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/query_whoop.py (reported line 37)May include surrounding context.

python
# Load .env from repo root so the script works without manually exporting vars.
_repo_root = Path(__file__).resolve().parent.parent
_load_dotenv(_repo_root / ".env")


def parse_args() -> argparse.Namespace:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest describes a skill that validates local collector connectivity over HTTP, fetches fresh data with shell commands, and supports Apple Health summaries, trends, alerts, and check-ins. This file instead only queries an existing local SQLite table for live heart-rate events and prints raw events plus simple statistics, with no HTTP connectivity checks, shell-based refresh, broader Apple Health data handling, alerts, or coaching guidance.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill manifest says this skill is for Apple Health summaries from a local collector, with HTTP connectivity validation and shell-based fresh-data fetching before guidance. This script instead queries Whoop-specific tables from a local SQLite database and emits Whoop data JSON, which is a materially different data source and behavior than the stated Apple Health collector workflow.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill encourages HTTP(S) health-check requests to a collector endpoint without any privacy or network-boundary warning. In a health-data context, even a simple /healthz probe can disclose internal hostnames, ports, service presence, and operational details, and may trigger network access to infrastructure the user did not intend the agent to contact.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The manifest describes validating a local collector over HTTP and fetching health data for summaries and coaching-style guidance. This script additionally loads a repository-root .env file and populates process environment variables, including the user ID and database path, which is a broader configuration-secrets access pattern not clearly required by the stated purpose.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

Reading a repo-level .env file and populating process environment variables is an additional capability not clearly justified by the manifest's stated purpose of querying Apple Health summaries from a local collector. While used here to source a database path, this environment-harvesting behavior expands the skill's access surface beyond the plainly stated health-query function.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The file documentation states that it queries Whoop data and that its JSON output is consumed by an OpenClaw whoop-query skill. Within the context of the apple-health-query skill, that documentation directly indicates a different intended skill and data domain than the one declared in the manifest.

Content

No source excerpt is available for this finding.