Back to skill

Security audit

Strava Api

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims for Strava, but it handles OAuth tokens and private fitness data with weak local-file protections that deserve review before installation.

Review before installing if you use Strava data you consider private. Use a private token path, restrict file permissions, avoid writing activity exports to shared /tmp paths, and revoke/rotate Strava credentials if token files are exposed.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/strava_oauth_login.py:224
Finding
OAuth Authorization Flow Does Not Validate the State Parameter<![CDATA[ ## Vulnerability Details **File Location**: `scripts/strava_oauth_login.py`, lines 164-169 and 224-250 **Vulnerability Type**: OAuth login CSRF and authorization-session confusion **Risk Level**: Medium ### Vulnerable Code ```python q = urllib.parse.parse_qs(parsed.query) code = (q.get("code") or [None])[0] if code: got["code"] = code ``` ```python params = { "client_id": client_id, "redirect_uri": redirect_uri, "response_type": "code", "approval_prompt": "auto", "scope": scopes, } auth_url = AUTH_URL + "?" + urllib.parse.urlencode(params) print("Open this URL in a browser and approve access:\n") print(auth_url) if args.loopback: print("\nWaiting for redirect on:") print(redirect_uri) code = listen_for_code(redirect_uri) else: print("\nAfter approval, paste either the full redirect URL or just the code:") code = parse_code(input("> ")) tok = exchange_code_for_token( code=code, client_id=client_id, client_secret=client_secret ) ``` ### Technical Analysis The OAuth authorization request does not contain a cryptographically random `state` parameter. Correspondingly, neither the copy-and-paste flow nor the loopback callback verifies that an incoming authorization response belongs to the authorization transaction initiated by the script. OAuth `state` is used to bind the authorization response to the initiating client session and prevent login CSRF or authorization-response substitution. The script accepts any authorization code supplied through standard input or received as the first valid loopback callback. The token exchange still occurs against Strava's fixed HTTPS token endpoint, so this is not arbitrary credential exfiltration. The weakness instead concerns the identity and authorization transaction associated with the accepted code. ### Attack Path 1. An attacker initiates or influences a separate Strava authorization flow using the same registered client and redirect URI. 2. The attacker o ...[truncated 972 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate a fresh, unpredictable state value before constructing each authorization URL: ```python import secrets expected_state = secrets.token_urlsafe(32) params["state"] = expected_state ``` 2. Require the callback or pasted redirect URL to contain a `state` value exactly matching `expected_state`. 3. Reject responses with a missing, malformed, or mismatched state before exchanging the authorization code. 4. Explicitly process OAuth error responses such as `error` and `error_description`. 5. In loopback mode, accept only the configured callback path and terminate the listener after the first valid state-matched response. 6. Prefer a loopback address of `127.0.0.1` over hostname-based resolution where practical. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/strava_oauth_login.py:44
Finding
OAuth Tokens Are Written Before Restrictive File Permissions Are Applied<![CDATA[ ## Vulnerability Details **File Location**: `scripts/strava_oauth_login.py`, lines 44-55 **Vulnerability Type**: Insecure temporary-file creation and transient credential exposure **Risk Level**: Medium ### Vulnerable Code ```python def save_token(tok: Dict[str, Any], path: Optional[str] = None) -> str: p = path or token_path() _mkdirp(p) tmp = p + ".tmp" with open(tmp, "w", encoding="utf-8") as f: json.dump(tok, f, indent=2, sort_keys=True) f.write("\n") os.replace(tmp, p) try: os.chmod(p, 0o600) except PermissionError: pass return p ``` ### Technical Analysis The temporary token file is created using Python's ordinary `open(..., "w")`. Its initial permissions therefore depend on the process umask. Under a common `022` umask, a newly created file can initially be mode `0644`. The code applies mode `0600` only after the sensitive token contents have been written and the temporary file has been moved into place. The temporary filename is also predictable because it is always the configured token path followed by `.tmp`. Ordinary `open()` follows symbolic links and does not request exclusive creation. If the configured parent directory is writable by another local user, an attacker may pre-create the temporary path as a symbolic link or monitor it while credentials are being written. The stored JSON can include an access token and a long-lived refresh token. The post-write `chmod` reduces continuing exposure but does not eliminate the initial permission window, symlink risk, or the possibility that `chmod` fails and is silently ignored. ### Attack Path 1. The Skill runs with a permissive umask or uses a token directory accessible or writable by another local user. 2. A local attacker monitors the predictable `<token-path>.tmp` filename or pre-creates it as a symbolic link. 3. The script opens the path and writes the OAuth token response before applying restrictive permissions. 4. Th ...[truncated 669 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Ensure the token directory is private and owned by the current user, preferably mode `0700`. 2. Create the temporary file atomically with mode `0600`, exclusive creation, and protection against symbolic-link traversal. For example, use `os.open()` with `O_CREAT | O_EXCL | O_WRONLY` and `O_NOFOLLOW` where supported. 3. Alternatively, use `tempfile.NamedTemporaryFile` in the destination directory, explicitly apply mode `0600`, and atomically replace the destination. 4. Flush and `fsync()` the file before replacement to improve durability. 5. Validate that the destination and parent directory are not symbolic links and have the expected ownership. 6. Do not silently ignore permission-setting failures; remove the insecure file and report an error if secure permissions cannot be established. 7. Consider setting the restrictive mode before writing any token bytes rather than correcting permissions afterward. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/strava_fetch_activities.py:188
Finding
Normalized Health Data Is Written to an Insecure Caller-Supplied Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/strava_fetch_activities.py`, lines 188-190 **Vulnerability Type**: Insecure sensitive-data file creation and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python with open(args.out, "w", encoding="utf-8") as file: json.dump(bundle, file, indent=2, sort_keys=True) file.write("\n") ``` ### Technical Analysis The output file is created with ordinary `open(..., "w")`. Its permissions inherit the process umask, and no restrictive mode is subsequently applied. Under a typical `022` umask, the resulting file may be mode `0644`. The generated JSON includes workout timestamps, activity type, duration, distance, calories, and average and maximum heart rate. This is sensitive health and activity information. The documented invocation writes to the predictable shared path `/tmp/strava_today.json`, where permissive permissions can expose the data to other local users. The operation also follows existing symbolic links. If another user can pre-create the selected path, particularly in a shared temporary directory, the write may truncate and overwrite another file that is writable by the account running the Skill. ### Attack Path 1. The user follows the documented example and selects a predictable shared path such as `/tmp/strava_today.json`. 2. A local attacker monitors that path or creates a symbolic link at that location before execution. 3. The script opens the path with truncation and follows any existing symbolic link. 4. The activity bundle is written with permissions derived from the ambient umask. 5. The attacker reads the resulting health data or causes a file writable by the executing user to be overwritten. ### Impact Assessment The primary impact is local disclosure of private workout and health information, including heart-rate measurements and activity timing. Activity timing may also reveal behavioral patterns. In a successful symlink scenario, the script m ...[truncated 161 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create output files with mode `0600` from the outset rather than relying on the caller's umask. 2. Use exclusive, no-follow file creation where supported to reject pre-existing files and symbolic links. 3. Write to a securely created temporary file in the destination directory and atomically replace the final path after flushing. 4. Validate that the destination directory is owned by the current user and is not writable by untrusted users. 5. Avoid recommending predictable filenames in shared `/tmp`; use a private application data directory or securely generated temporary filename. 6. Warn users that the output contains sensitive health information and should receive access controls equivalent to other private medical or fitness data. 7. If overwriting is required, verify the existing target is a regular file owned by the current user before replacement. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill invokes scripts that use environment variables, local file storage, network access, and shell execution, but it does not declare any explicit tool or permission scope. In an agent environment, missing scope declarations can lead to overbroad execution privileges and make it harder to enforce least-privilege controls around OAuth flows, token handling, and file writes.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The skill documents OAuth client secrets, redirect URI configuration, and token persistence behavior without any guidance on secure secret handling or protection of stored tokens. Access and refresh tokens for a fitness account can enable long-lived unauthorized access to personal activity data if exposed through misconfigured files, shell history, screenshots, or shared environments.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill instructs users to export Strava activity data to `/tmp/strava_today.json` without warning that workout history can contain sensitive personal information such as timestamps, routes, and behavioral patterns. Writing this data to a shared or weakly protected local path increases the risk of unintended disclosure to other local users, processes, backups, or logs.

Missing User Warnings

Low
Confidence
77% confidence
Finding
This code writes structured Strava activity data, including workout timing and health-related metrics, to the path supplied by --out. Although the module docstring mentions writing raw JSON, there is no runtime disclosure, confirmation, or visible warning at the point of file creation that user activity data will be stored locally.

Static analysis

No suspicious patterns detected.