Back to skill

Security audit

Whoop

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims for Whoop data and charts, but it handles very sensitive health data and OAuth credentials with weak local protections and loads unpinned third-party JavaScript into generated reports.

Install only if you are comfortable granting broad read access to Whoop health, profile, and body-measurement data. Use it on a private machine, avoid passing secrets in shared shells, do not share logs or generated HTML reports, and revoke the Whoop app authorization if you stop using it. The publisher should ideally move secrets to an OS credential store, narrow scopes, remove token printing, and bundle or pin chart JavaScript before this is low risk.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/whoop_chart.py:41
Finding

Unpinned remote JavaScript executes with access to embedded health data

Content
View full analysis
Whoop - {title} ``` The generated page embeds the user's health data and then automatically opens in a browser: ```python html = HTML_TEMPLATE.format( title=title, subtitle=subtitle, stats_html=stats_html, charts_html=charts_html, chart_data=json.dumps(chart_data), chart_scripts=chart_scripts, timestamp=timestamp, ) with open(filepath, "w", encoding="utf-8") as f: f.write(html) print(f"Chart saved to: {filepath}") # Open in browser try: webbrowser.open(f"file://{filepath}") except Exception: pass ``` The corresponding template exposes those metrics to all scripts running in the page: ```html ``` ### Technical Analysis The URL `https://cdn.jsdelivr.net/npm/apexcharts` does not select an immutable ApexCharts version and does not provide a Subresource Integrity hash. Consequently, the JavaScript executed by the generated report can change after the Skill has been audited. The remote script executes in the same page as `chartData`, which can contain dates, sleep duration and stages, recovery scores, HRV, resting heart rate, strain, and other private health metrics. A malicious or compromised CDN response can read these values directly and transmit them to an attacker-controlled server using ordinary browser network APIs. Loading a charting library is related to the declared visualization functionality, but retrieving mutable executable code ...[truncated 1564 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/whoop_auth.py:44
Finding

OAuth client secret and bearer credentials are persistently stored in plaintext

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/whoop_chart.py:812
Finding

User-selected chart output can persist sensitive health data with permissive filesystem permissions

Content
View full analysis
0 for value in (chart_data or {}).values() ) if not has_data: stats_html = "" charts_html = '
No data available
' chart_data = {} chart_scripts = "" timestamp = datetime.now().strftime("%b %d, %Y at %I:%M %p") html = HTML_TEMPLATE.format( title=title, subtitle=subtitle, stats_html=stats_html, charts_html=charts_html, chart_data=json.dumps(chart_data), chart_scripts=chart_scripts, timestamp=timestamp, ) if output: filepath = os.path.abspath(output) output_dir = os.path.dirname(filepath) if output_dir: os.makedirs(output_dir, exist_ok=True) else: fd, filepath = tempfile.mkstemp(suffix=".html", prefix="whoop_") os.close(fd) with open(filepath, "w", encoding="utf-8") as f: f.write(html) print(f"Chart saved to: {filepath}") # Open in browser try: webbrowser.open(f"file://{filepath}") except Exception: pass ``` ### Technical Analysis The generated HTML contains plaintext arrays of private health measurements. When no output path is supplied, `tempfile.mkstemp` creates the initial temporary file securely. However, when `--output` is used, the subsequent `open(filepath, "w")` creates a new file using the process umask, commonly resulting in permissions such as `0644`. No explicit restrictive mode is applied, and existing files ...[truncated 1530 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
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill's stated scope focuses on wearable metrics and charts, but it also instructs retrieval of profile and body-measurement data, which expands collection of sensitive personal data beyond the core description. Undisclosed data-category expansion increases privacy risk because users may not realize additional health-related information is being accessed and processed.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill's stated scope focuses on wearable metrics and charts, but it also instructs retrieval of profile and body-measurement data, which expands collection of sensitive personal data beyond the core description. Undisclosed data-category expansion increases privacy risk because users may not realize additional health-related information is being accessed and processed.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
89% confidence
Finding

The usage pattern requires passing the client secret on the command line (--client-secret SECRET). On many systems, command-line arguments can be exposed to other local users via process listings, shell history, logging, or job-control tooling, which can leak OAuth client credentials.

Content

Scanner excerpt · scripts/whoop_auth.py (reported line 9)May include surrounding context.

python
# First-time login (opens browser)
    python3 whoop_auth.py login --client-id ID --client-secret SECRET

    # Get current access token (auto-refreshes if expired)
    python3 whoop_auth.py token

    # Check auth status

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

The refresh flow depends on storing and retrieving long-lived secrets, including the refresh token and client secret, from a local JSON file. Compromise of that file enables unauthorized token refresh and continued access to the user's sensitive health data, increasing the blast radius beyond a single short-lived access token.

Content

Scanner excerpt · scripts/whoop_auth.py (reported line 88)May include surrounding context.

python
def refresh_token(tokens):
    """Refresh the access token."""
    refresh = tokens.get("refresh_token")
    client_id = tokens.get("client_id")
    client_secret = tokens.get("client_secret")

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

The helper is designed to retrieve a valid bearer token for later use, which is sensitive by nature because possession of the token grants API access to private wellness data. In the context of this script, token access is legitimate, but centralizing retrieval increases risk if other local components or users can invoke the script or read its outputs.

Content

Scanner excerpt · scripts/whoop_auth.py (reported line 123)May include surrounding context.

python
def get_valid_token():
    """Get a valid access token, refreshing if needed."""
    tokens = load_tokens()
    if not tokens:
        print("Not authenticated. Run: whoop_auth.py login", file=sys.stderr)

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

get_valid_token() returns the raw access token and the CLI token command prints it to stdout, making credential leakage likely through terminal scrollback, shell capture, logs, copied transcripts, or calling-process output collection. Because the token authorizes access to sensitive health information, exposure can directly enable unauthorized API access until expiry or refresh.

Content

Scanner excerpt · scripts/whoop_auth.py (reported line 137)May include surrounding context.

python
access_token = tokens.get("access_token")
    if not access_token:
        print("No access token found. Run: whoop_auth.py login", file=sys.stderr)
        sys.exit(1)
    return access_token

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

The CLI explicitly advertises token as 'Print current access token', encouraging direct exposure of a bearer credential. This creates a practical credential-disclosure path in normal use and is especially risky given the scope includes offline access and private health-related API data.

Content

Scanner excerpt · scripts/whoop_auth.py (reported line 284)May include surrounding context.

python
login_p.add_argument("--client-id", required=True)
    login_p.add_argument("--client-secret", required=True)

    sub.add_parser("token", help="Print current access token")
    sub.add_parser("status", help="Show auth status")
    sub.add_parser("logout", help="Delete stored tokens")

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill instructs use of shell, network access, local file reads/writes, and token storage, but it does not declare any explicit tool scope or permissions boundaries. That makes the agent's operational authority ambiguous and can lead to over-broad execution against sensitive local files, browser-based OAuth flows, and external services without clear least-privilege constraints.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 12)May include surrounding context.

md
## Setup (first time only)

### 1. Create a Whoop Developer App

1. Go to [developer-dashboard.whoop.com](https://developer-dashboard.whoop.com)
2. Sign in with your Whoop account credentials

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill handles highly sensitive health data and stores OAuth tokens locally, yet it provides no prominent warning about privacy, local machine exposure, token sensitivity, or secure handling expectations. If the host is shared, compromised, or backed up insecurely, attackers could use the stored tokens to access intimate health information and related personal data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill is designed to access highly sensitive health and biometric data, including sleep, recovery, HRV, resting heart rate, SpO2, body measurements, and profile information, but the reference provides no privacy warning, data-minimization guidance, or user-consent expectations. In a health-data context, this omission is dangerous because downstream implementations may over-collect or display/export sensitive data without making users aware of the privacy implications.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The usage trigger is broad enough to activate on common health-related questions, which can cause the skill to engage in situations where the user did not clearly request Whoop-specific analysis. In a health context, over-broad invocation increases the chance of unsolicited interpretation of sensitive biometric data and delivery of quasi-medical guidance outside the intended scope.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/api.md (reported line 3)May include surrounding context.

md
import webbrowser

TOKEN_FILE = os.path.expanduser("~/.clawdbot/whoop-tokens.json")
AUTH_URL = "https://api.prod.whoop.com/oauth/oauth2/auth"
TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
USER_AGENT = "Clawdbot/1.0 (Whoop Integration)"
SCOPES = "offline read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/api.md (reported line 10)May include surrounding context.

md
import webbrowser

TOKEN_FILE = os.path.expanduser("~/.clawdbot/whoop-tokens.json")
AUTH_URL = "https://api.prod.whoop.com/oauth/oauth2/auth"
TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
USER_AGENT = "Clawdbot/1.0 (Whoop Integration)"
SCOPES = "offline read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/api.md (reported line 11)May include surrounding context.

md
import webbrowser

TOKEN_FILE = os.path.expanduser("~/.clawdbot/whoop-tokens.json")
AUTH_URL = "https://api.prod.whoop.com/oauth/oauth2/auth"
TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
USER_AGENT = "Clawdbot/1.0 (Whoop Integration)"
SCOPES = "offline read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

python
import webbrowser

TOKEN_FILE = os.path.expanduser("~/.clawdbot/whoop-tokens.json")
AUTH_URL = "https://api.prod.whoop.com/oauth/oauth2/auth"
TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
USER_AGENT = "Clawdbot/1.0 (Whoop Integration)"
SCOPES = "offline read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

python
import webbrowser

TOKEN_FILE = os.path.expanduser("~/.clawdbot/whoop-tokens.json")
AUTH_URL = "https://api.prod.whoop.com/oauth/oauth2/auth"
TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
USER_AGENT = "Clawdbot/1.0 (Whoop Integration)"
SCOPES = "offline read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/whoop_data.py (reported line 43)May include surrounding context.

python
import webbrowser

TOKEN_FILE = os.path.expanduser("~/.clawdbot/whoop-tokens.json")
AUTH_URL = "https://api.prod.whoop.com/oauth/oauth2/auth"
TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
USER_AGENT = "Clawdbot/1.0 (Whoop Integration)"
SCOPES = "offline read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The refresh_token function performs an HTTP request containing the refresh token, client ID, and client secret, but there is no explicit warning or disclosure to the user that stored credentials and tokens will be transmitted during automatic refresh. The existing docstrings describe functionality but do not clearly communicate this network behavior to users.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
82% confidence
Finding

This code sends the client ID and client secret to the remote Whoop token endpoint as part of the authorization_code exchange. Although the script documents login usage and token storage, it does not explicitly warn the user at the point of operation that their supplied secret will be transmitted over the network.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The generated HTML loads ApexCharts from a third-party CDN, which causes remote JavaScript to execute in the user's browser every time the report is opened. In a health-data skill, that expands trust to an external provider and creates supply-chain, privacy, and integrity risk: compromised CDN content or unexpected requests could expose sensitive wearable data rendered into the page.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes a skill for accessing Whoop health data and generating interactive charts, but this script also performs local side effects by creating files and launching a browser. While chart generation implies producing output, automatically persisting files and opening external applications is a broader behavior than the manifest states.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script retrieves sensitive health and profile data from the Whoop API and prints the full JSON responses to stdout, with no privacy gating, redaction, or user-facing warning in the implementation. In agent environments, stdout is often logged, persisted, or forwarded, so this can unintentionally disclose highly sensitive biometric and profile information.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill exposes commands to fetch user profile and body measurement data even though the skill description focuses on sleep, recovery, strain, HRV, workouts, and visualizations. This expands collection into more sensitive personal data than users would reasonably expect, creating a data-minimization and consent problem rather than a code-execution flaw.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.