Back to skill

Security audit

WHOOP Lab

Security checks for vulnerabilities and agentic risk

Overview

This WHOOP health-data skill is mostly coherent, but it needs review because it can persist health API credentials, write health data into notes, push an entire Obsidian vault to git, and open/share chart files that load remote code.

Install only if you are comfortable granting broad read access to WHOOP health data and storing refresh credentials locally. Before using Obsidian logging, put WHOOP notes in a dedicated vault or disable git sync, because the current script can commit and push unrelated vault files. Treat generated chart HTML as sensitive health data, avoid auto-sharing it, and prefer offline or locally bundled chart assets. Revoke WHOOP app access and delete ~/.config/whoop-skill/credentials.json if you stop using it.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/log_to_obsidian.py:149
Finding

Vault-Wide Git Staging and Push Can Upload Unrelated Sensitive Files

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/log_to_obsidian.py:126
Finding

Unvalidated Date Argument Enables Path Traversal Outside the Daily Notes Directory

Content
View full analysis
Remediation
View remediation

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/chart.py:174
Finding

Mutable Remote JavaScript Executes in HTML Containing Private Health Data

Content
View full analysis
' ``` ```python output = Path(args.output).expanduser() if args.output else Path(f"/tmp/whoop-{args.chart}.html") ``` ```python output.write_text(html, encoding="utf-8") print(f"Chart saved: {output}", file=sys.stderr) if not args.no_open: webbrowser.open(f"file://{output.resolve()}") print("Opened in browser.", file=sys.stderr) ``` The remote script is inserted into generated pages: ```python return f""" ... {CHARTJS_CDN} """ ``` ### Technical Analysis The generated HTML embeds recovery scores, HRV, resting heart rate, sleep-stage data, strain, and calorie information. When opened, it retrieves and executes JavaScript from an unversioned CDN URL. Because the URL does not pin an exact Chart.js release and does not use Subresource Integrity, the code that executes can change after the Skill has been audited. If the package, CDN, DNS path, or delivery infrastructure is compromised, the remote script executes in the same document context as the embedded health data and can read that data. This also contradicts the documentation's claim that the file is “self-contained.” The chart depends on a network request and remote executable content. ### Attack Path 1. The user or agent generates a WHOOP chart. 2. The script writes private health measurements into a local HTML document. 3. Unless `--no-open` is selected, the script automatically opens the document. 4. The browser requests `https://cdn.jsdelivr.net/npm/chart.js`. 5. A compromised or maliciously changed CDN response executes in the page. 6. The script reads chart datasets or document content. 7. The m ...[truncated 784 chars]
Remediation
View remediation
/dist/chart.umd.min.js" integrity="sha384-" crossorigin="anonymous"> ``` - Add a restrictive Content Security Policy that permits only the required local script and blocks arbitrary outbound connections. A fully bundled chart can use a policy such as `connect-src 'none'`. - Avoid automatically opening the chart unless the user explicitly requests it. - Correct the documentation to disclose any remaining external network dependency. - Treat chart attachments as sensitive health records and require confirmation before sharing them through messaging services. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/chart.py:465
Finding

Predictable Shared Temporary Chart Files Permit Local Disclosure and Symlink Overwrites

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding

Unpinned Python Dependency Makes Installation Non-Reproducible

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (44)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

This finding is valid to the extent that the skill documents local file modification and git synchronization behavior without clearly surfacing them in a permission model or high-visibility warning. Silent or insufficiently disclosed writes to an Obsidian vault and remote git push can affect user data integrity and confidentiality, especially because health data is involved.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

This finding is valid to the extent that the skill documents local file modification and git synchronization behavior without clearly surfacing them in a permission model or high-visibility warning. Silent or insufficiently disclosed writes to an Obsidian vault and remote git push can affect user data integrity and confidentiality, especially because health data is involved.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

This finding is valid to the extent that the skill documents local file modification and git synchronization behavior without clearly surfacing them in a permission model or high-visibility warning. Silent or insufficiently disclosed writes to an Obsidian vault and remote git push can affect user data integrity and confidentiality, especially because health data is involved.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

The skill stores OAuth tokens in a local credentials.json file, which is a sensitive secret store for access to personal health data. If file permissions are weak, the path is exposed, or the vault/config points somewhere unsafe, an attacker or another local process could steal tokens and access WHOOP account data.

Content

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

text
~/.config/whoop-skill/
  credentials.json   — OAuth tokens (created by auth.py on first setup)
  experiments.json   — experiment tracking data (created on first `plan` command)
  config.json        — optional path/timezone overrides (copy from config.example.json)

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

Automatically creating and managing a credentials file for OAuth tokens creates persistent secret material on disk. Persistent token storage raises the risk of credential theft and long-term account access if the machine, backup system, or synced home directory is compromised.

Content

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

config.json — optional path/timezone overrides (copy from config.example.json)

text

The directory and `credentials.json` are created automatically when you run `scripts/auth.py`. You never need to create them manually.

## Setup

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

The setup flow collects a Client ID and Client Secret and saves resulting credentials locally, which is sensitive account-linked material. If mishandled, these secrets can enable unauthorized token minting or API access, particularly because the skill requests offline access.

Content

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

md
1. Prompt you for your Client ID and Client Secret
2. Ask which callback method you chose in Step 1 (local server or manual)
3. Walk you through the authorization flow
4. Save credentials to `~/.config/whoop-skill/credentials.json`

**Customize paths (optional):**
Copy `config.example.json` from the skill root to `~/.config/whoop-skill/config.json` and edit to override defaults:

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

Allowing override of creds_path via config increases flexibility but can also redirect token storage to insecure or synced locations. That makes accidental disclosure more likely, especially if users point it at broadly readable directories or cloud-synced folders.

Content

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

Copy config.example.json from the skill root to ~/.config/whoop-skill/config.json and edit to override defaults:

json
{
  "creds_path": "~/.config/whoop-skill/credentials.json",
  "vault_path": "~/my-obsidian-vault",
  "daily_notes_subdir": "Daily Notes",
  "timezone": "America/New_York",

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

The documented workflow requires loading stored credentials and updating them during refresh, meaning the skill routinely accesses sensitive tokens in plaintext-like local storage. Regular automated credential handling increases exposure surface to logs, crashes, backups, or other local software.

Content

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

md
## Workflow

1. Load credentials from `~/.config/whoop-skill/credentials.json`
2. If `expires_at` is in the past (or within 60s), call `scripts/refresh_token.py` to get a new access token and update the file
3. Call the appropriate endpoint (see `references/api.md`)
4. Parse and present the data in plain language

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

Use of an access token to call the API is expected, but in this context it is security-sensitive because the token authorizes retrieval of personal health data. If exposed through logs, files, subprocess environments, or crash output, it enables unauthorized data access until expiry and potentially beyond if paired with refresh credentials.

Content

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

md
## Workflow

1. Load credentials from `~/.config/whoop-skill/credentials.json`
2. If `expires_at` is in the past (or within 60s), call `scripts/refresh_token.py` to get a new access token and update the file
3. Call the appropriate endpoint (see `references/api.md`)
4. Parse and present the data in plain language

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

Automatic token refresh extends the lifetime of API access and therefore raises the consequences of local secret compromise. Because the account data includes sensitive wellness and biometrics information, unauthorized persistence materially increases privacy risk.

Content

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

md
## Token Refresh

Run `scripts/refresh_token.py` when the access token is expired. It reads/writes `~/.config/whoop-skill/credentials.json` automatically.

To re-auth from scratch, run `scripts/auth.py` again.

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

Automatic token refresh extends the lifetime of API access and therefore raises the consequences of local secret compromise. Because the account data includes sensitive wellness and biometrics information, unauthorized persistence materially increases privacy risk.

Content

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

md
## Token Refresh

Run `scripts/refresh_token.py` when the access token is expired. It reads/writes `~/.config/whoop-skill/credentials.json` automatically.

To re-auth from scratch, run `scripts/auth.py` again.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · config.example.json (reported line 2)May include surrounding context.

json
{
  "creds_path": "~/.config/whoop-skill/credentials.json",
  "vault_path": "~/path/to/your/obsidian-vault",
  "daily_notes_subdir": "Daily Notes",
  "timezone": "America/New_York",

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/config.py (reported line 8)May include surrounding context.

python
{
  "creds_path": "~/.config/whoop-skill/credentials.json",
  "vault_path": "~/path/to/your/obsidian-vault",
  "daily_notes_subdir": "Daily Notes",
  "timezone": "America/New_York",

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/config.py (reported line 24)May include surrounding context.

python
{
  "creds_path": "~/.config/whoop-skill/credentials.json",
  "vault_path": "~/path/to/your/obsidian-vault",
  "daily_notes_subdir": "Daily Notes",
  "timezone": "America/New_York",

Credential Access

High
Category
Privilege Escalation
Confidence
82% confidence
Finding

The script stores long-lived OAuth material, including client secret and refresh token, in a local JSON file under the user's home directory. Even with chmod 600, plaintext at-rest credential storage increases risk of token theft from local compromise, backups, logs, or multi-process access, especially because the skill handles health data and offline access.

Content

Scanner excerpt · scripts/auth.py (reported line 8)May include surrounding context.

python
Guides you through connecting your WHOOP account:
  1. Prompts for your WHOOP Developer App client ID and secret
  2. Opens a browser to authorize access (local server or manual code paste)
  3. Saves tokens to ~/.config/whoop-skill/credentials.json

Run this once to get set up. Tokens are refreshed automatically by other scripts.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/auth.py (reported line 228)May include surrounding context.

python
print()
    print(f"✓ Setup complete! Credentials saved to {creds_path}")
    print(f"  Access token expires: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(creds['expires_at']))}")
    print()
    print("You're all set. Ask your agent about your WHOOP data!")

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/refresh_token.py (reported line 2)May include surrounding context.

python
#!/usr/bin/env python3
"""Refresh the WHOOP access token using the stored refresh token."""

import json
import time

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/refresh_token.py (reported line 53)May include surrounding context.

python
#!/usr/bin/env python3
"""Refresh the WHOOP access token using the stored refresh token."""

import json
import time

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill explicitly describes capabilities that require shell, file read/write, network access, and credential handling, yet it declares no tool scope or permission boundaries. This is dangerous because an agent may invoke powerful operations without transparent least-privilege constraints, increasing the chance of unintended file modification, credential exposure, or network actions.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The 'Use when' language is overly broad for a skill that can access health data, local files, credentials, shell, and network resources. Overbroad activation criteria increase the chance the agent will invoke this skill in loosely related health conversations, causing unnecessary access to sensitive data or triggering side effects outside user intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill handles sensitive health information, stores OAuth tokens locally, and may append health data into an Obsidian vault and push it to a git remote, but the documentation lacks a concise warning about privacy, persistence, and external disclosure risks. Users may not realize their biometric data and tokens are being stored and potentially synchronized beyond the local machine.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
84% confidence
Finding

The skill is designed to create and keep credential state across sessions, including offline-capable tokens. Session persistence is not inherently wrong for OAuth, but it becomes security-relevant here because persisted auth for a health-data service enables background or future access without renewed user review.

Content

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

config.json — optional path/timezone overrides (copy from config.example.json)

text

The directory and `credentials.json` are created automatically when you run `scripts/auth.py`. You never need to create them manually.

## Setup

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The documentation explicitly instructs requesting all available WHOOP scopes, including profile, body measurements, and offline refresh-token access, regardless of whether a given workflow needs them. This violates least-privilege principles and increases privacy and persistence risk because the skill handles sensitive health data plus long-lived authorization, with no warning or guidance to minimize requested access.

Content

No source excerpt is available for this finding.

Tainted flow: 'manual_code' from input (line 169, user input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/auth.py (reported line 174)May include surrounding context.

python
print("ERROR: No code entered.")
            sys.exit(1)
        print("Exchanging code for tokens...")
        resp = requests.post(TOKEN_URL, data={
            "grant_type": "authorization_code",
            "code": manual_code,
            "client_id": client_id,

Tainted flow: 'EXPERIMENTS_FILE' from os.environ.get (line 35, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
88% confidence
Finding

The output file path is taken directly from the WHOOP_EXPERIMENTS_FILE environment variable and then opened for writing without validation. If an attacker can influence the environment in which this skill runs, they can redirect writes to arbitrary filesystem locations accessible to the process, causing clobbering of files, data corruption, or overwriting sensitive user configuration.

Content

Scanner excerpt · scripts/experiment.py (reported line 82)May include surrounding context.

python
def save_experiments(experiments):
    EXPERIMENTS_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(EXPERIMENTS_FILE, "w") as f:
        json.dump(experiments, f, indent=2)

Static analysis

No suspicious patterns detected.