Back to skill

Security audit

Strava Python

Security checks for vulnerabilities and agentic risk

Overview

This Strava skill is mostly purpose-aligned, but it persists broad Strava OAuth credentials in a plaintext home-directory file without enough scoping or safeguards.

Review carefully before installing. Use a dedicated Strava API app, grant only scopes you are comfortable with, protect or replace ~/.strava_credentials.json with secure secret storage or owner-only permissions, and revoke the Strava authorization when done. Prefer an isolated Python environment and a pinned stravalib version.

Vulnerability Patterns
  • 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
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
setup.py:31
Finding
Excessive Strava OAuth Scopes Violate Least Privilege<![CDATA[ ## Vulnerability Details **File Location**: `setup.py`, lines 31-35 **Vulnerability Type**: Excessive OAuth permissions **Risk Level**: Medium ### Vulnerable Code ```python authorize_url = client.authorization_url( client_id=int(client_id), redirect_uri='http://localhost:8282/authorized', scope=['read', 'read_all', 'activity:read_all', 'profile:read_all'] ) ``` ### Technical Analysis The setup process requests `read`, `read_all`, `activity:read_all`, and `profile:read_all` simultaneously. These scopes grant access to private profile and activity information, while the implemented commands only display recent activities, basic athlete information, and aggregate statistics. Requesting broad private-data scopes without demonstrating that each scope is necessary violates the principle of least privilege. The exposure is amplified because the resulting access and refresh tokens are stored locally. ### Attack Path 1. The user runs `setup.py`. 2. The generated authorization request asks the user to approve all listed scopes. 3. Strava issues access and refresh tokens with the approved private-data permissions. 4. An attacker obtains the saved token through local credential-file disclosure, malware, or account compromise. 5. The attacker uses the token against the Strava API to access private profile or activity data within the granted scopes, including data beyond what the Skill normally displays. ### Impact Assessment A compromised token may permit unauthorized access to private Strava activities and profile information. The issue does not grant operating-system privileges, but it increases the volume and sensitivity of account data exposed following token compromise. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Determine the minimum scopes required by each Strava API operation. - Remove private-data scopes that are not strictly necessary. - Make access to private activities or profile fields an explicit, optional setup choice. - Explain each requested scope before redirecting the user to authorization. - Consider separate authorization profiles for basic statistics and private-data access. - Add tests that verify the generated authorization URL does not silently acquire additional scopes. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
setup.py:22
Finding
Client Secret Is Collected Through an Echoing Terminal Prompt<![CDATA[ ## Vulnerability Details **File Location**: `setup.py`, lines 22-23 **Vulnerability Type**: Sensitive input exposure **Risk Level**: Low ### Vulnerable Code ```python client_id = input("Enter your Client ID: ").strip() client_secret = input("Enter your Client Secret: ").strip() ``` ### Technical Analysis Python's `input()` function echoes entered text to the terminal. It is therefore unsuitable for collecting the Strava client secret. The secret can remain visible on screen and may be captured by terminal recording, screen sharing, shoulder surfing, or monitoring software. This is a confidentiality weakness in the interactive setup process. It does not independently transmit the secret to another system. ### Attack Path 1. The user runs the setup wizard in a visible, shared, monitored, or recorded terminal session. 2. The user types the Strava client secret. 3. `input()` displays the secret as it is entered. 4. An observer or terminal-recording mechanism captures the displayed value. 5. The captured client secret may subsequently be combined with other OAuth material to impersonate the registered application or attack its OAuth workflow. ### Impact Assessment The exposed value is the user's Strava application client secret. Exposure does not by itself provide direct access to the operating system or necessarily grant access to the Strava account, but it weakens the OAuth application's credentials and may facilitate abuse when combined with authorization codes, refresh tokens, or other account information. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Collect the secret with `getpass.getpass()` so terminal echo is disabled: ```python from getpass import getpass client_id = input("Enter your Client ID: ").strip() client_secret = getpass("Enter your Client Secret: ").strip() ``` - Avoid printing the secret in success or error messages. - Clear references to the plaintext secret as soon as practical after storage or token exchange. - Warn users not to run credential setup in recorded or publicly visible terminal sessions. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
setup.py:64
Finding
OAuth Credentials Are Stored in Plaintext Without Enforced Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `setup.py`, lines 64-75 **Vulnerability Type**: Insecure sensitive-data storage **Risk Level**: High ### Vulnerable Code ```python # Save credentials credentials = { 'access_token': token_response['access_token'], 'refresh_token': token_response['refresh_token'], 'expires_at': token_response['expires_at'], 'client_id': int(client_id), 'client_secret': client_secret } config_path = os.path.expanduser('~/.strava_credentials.json') with open(config_path, 'w') as f: json.dump(credentials, f, indent=2) ``` ### Technical Analysis The application writes the access token, refresh token, and client secret to a plaintext JSON file. The call to `open(config_path, 'w')` does not explicitly enforce owner-only permissions. For a newly created file, effective permissions depend on the process umask. Under a common `022` umask, the file may be created as readable by other local users. If the file already exists with permissive permissions, opening it for writing does not correct those permissions. The refresh token is particularly sensitive because it may permit continued account access after the short-lived access token expires. ### Attack Path 1. The user runs `setup.py` with a permissive umask, or `~/.strava_credentials.json` already exists with permissive permissions. 2. The setup process writes the client secret, access token, and refresh token into that file. 3. Another local user or process with file-read access reads the JSON document. 4. The attacker extracts the OAuth tokens. 5. The attacker uses the access token, or exchanges the refresh token using the stored client credentials, to access Strava API data within the authorized scopes. Exploitation requires local filesystem access sufficient to read the credential file. ### Impact Assessment Successful exploitation can disclose private Strava profile and activity information covered by the authorized OAuth scopes. Possession of ...[truncated 284 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer an operating-system credential store or keyring instead of a plaintext JSON file. - If a file must be used, create it atomically with owner-only mode `0600`. - Explicitly correct permissions after writing, including for pre-existing files. - Prevent unintended symbolic-link traversal where supported. - Avoid storing the client secret unless it is required after initial authorization. - Validate ownership and permissions whenever credentials are loaded. - Provide a command to revoke authorization and securely remove local credentials. An example hardened Unix-oriented approach is: ```python config_path = os.path.expanduser("~/.strava_credentials.json") flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(config_path, flags, 0o600) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w") as f: fd = None json.dump(credentials, f, indent=2) finally: if fd is not None: os.close(fd) ``` Atomic replacement with a securely created temporary file in the same directory should also be considered to avoid partial writes. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:9
Finding
Unpinned Third-Party Dependency Creates Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 9-13 **Vulnerability Type**: Unpinned package dependency **Risk Level**: Medium ### Vulnerable Code ```yaml install: - id: pip kind: pip package: stravalib label: Install stravalib (pip) ``` The same unconstrained installation is also documented in `SKILL.md`, lines 32-36, and `README.md`, lines 21-26: ```bash pip install stravalib ``` ### Technical Analysis The dependency is specified only by package name. No audited version, hash, lock file, trusted index configuration, or integrity constraint is supplied. Installation therefore resolves whichever release the configured Python package index considers current at installation time. This does not establish that `stravalib` is malicious. It means the reviewed Skill does not provide reproducible dependency resolution and cannot guarantee that future installations execute the same dependency code that was present at audit time. ### Attack Path 1. A user or OpenClaw installation process follows the declared installation instructions. 2. `pip` resolves `stravalib` from the configured package index without a version or hash constraint. 3. A future dependency release, compromised package account, compromised package index, or maliciously configured index supplies altered package content. 4. The package is installed into the Skill's Python environment. 5. Its code executes when imported by `setup.py` or `strava_control.py`, gaining the privileges of the user running those scripts. 6. Such code could access the plaintext Strava credential file and other data available to that user. This path is conditional on compromise or unsafe configuration of the dependency supply chain; no such compromise was identified in the audited project files. ### Impact Assessment A compromised dependency could execute arbitrary Python code with the invoking user's privileges. It could access Strava tokens, local user files, environment variables ...[truncated 168 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `stravalib` to a reviewed, compatible version. - Use a requirements or lock file with cryptographic hashes. - Install packages only from an explicitly trusted package index. - Review transitive dependencies and update them through a controlled process. - Run dependency vulnerability and provenance checks in continuous integration. - Use an isolated virtual environment with only the permissions required by the Skill. For example: ```text stravalib==<reviewed-version> --hash=sha256:<verified-distribution-hash> ``` The Skill metadata and all installation documentation should reference the same pinned dependency policy. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The code substantially matches the core declared purpose of querying Strava data via Python/stravalib. It reads local credentials, connects to Strava, and retrieves recent activities, athlete stats, and the last activity. However, the description claims 'interactive setup,' which is not implemented here; the code only expects a preexisting credentials file and tells the user to run a separate setup script. The description also mentions 'workout data' broadly, while this chunk only exposes limited read-only queries. So this is a partial but material description-behavior mismatch.

Credential Access

High
Category
Privilege Escalation
Content
This will:
   - Guide you through creating a Strava API app
   - Handle OAuth authentication
   - Save credentials to `~/.strava_credentials.json`

## Commands
Confidence
94% confidence
Finding
The skill explicitly states that OAuth credentials will be saved to ~/.strava_credentials.json, which introduces a sensitive local secret file. In agent or shared environments, predictable plaintext credential storage in the user's home directory increases the risk of accidental disclosure, unintended reuse by other tools, or theft if filesystem access is broader than expected.

Ae1

High
Category
analysis-evasion
Content
- `SKILL.md` - This file
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
- `strava_control.py` - Main controller script
- `setup.py` - Interactive setup wizard
- `SKILL.md` - This file
- `~/.strava_credentials.json` - Credentials (auto-generated)

## Notes
Confidence
93% confidence
Finding
Documenting an auto-generated credentials file at ~/.strava_credentials.json confirms the presence of a predictable secret-bearing artifact on disk. In the context of a skill that may have file read/write capabilities, this makes credential targeting easier and raises the danger of unauthorized access or exfiltration from a user's home directory.

Credential Access

High
Category
Privilege Escalation
Content
'client_secret': client_secret
    }

    config_path = os.path.expanduser('~/.strava_credentials.json')
    with open(config_path, 'w') as f:
        json.dump(credentials, f, indent=2)
Confidence
98% confidence
Finding
This code writes a credential bundle containing access_token, refresh_token, and client_secret to ~/.strava_credentials.json. Those values enable ongoing authenticated access, and inclusion of the refresh token and client secret makes compromise more severe because an attacker can refresh access and potentially maintain persistence.

Credential Access

High
Category
Privilege Escalation
Content
def load_credentials():
    """Load Strava credentials from config file"""
    config_path = os.path.expanduser('~/.strava_credentials.json')
    if not os.path.exists(config_path):
        print(f"❌ Credentials not found at {config_path}")
        print("Run the setup script first: python3 setup.py")
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def load_credentials():
    """Load Strava credentials from config file"""
    config_path = os.path.expanduser('~/.strava_credentials.json')
    if not os.path.exists(config_path):
        print(f"❌ Credentials not found at {config_path}")
        print("Run the setup script first: python3 setup.py")
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Session Persistence

Medium
Category
Rogue Agent
Content
pip install stravalib
```

### 2. Create Strava API App

1. Go to https://www.strava.com/settings/api
2. Click **"Create App"**
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
82% confidence
Finding
The skill advertises behavior that involves reading and writing local files, including storing OAuth credentials, but it does not declare any explicit tool scope or permissions. In an agent setting, undeclared filesystem capability reduces transparency and can allow broader file access than users or reviewers expect, especially when credentials are written to the home directory.

Session Persistence

Medium
Category
Rogue Agent
Content
print()

# Get credentials from user
print("First, create a Strava API app:")
print("1. Go to: https://www.strava.com/settings/api")
print("2. Click 'Create App'")
print("3. Fill in:")
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script stores highly sensitive OAuth material and the Strava client secret in a plaintext file under the user's home directory. This creates unnecessary exposure because any local process, backup system, or other user with access to that file can reuse the tokens or secret to access the user's Strava data and maintain access via the refresh token.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script reads an OAuth access token from a local credentials file and uses it without any runtime disclosure or consent prompt. In an agent-skill context, this can cause a user to unknowingly expose or use sensitive account credentials, especially if the surrounding platform invokes the skill on the user's behalf.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill makes authenticated network requests to Strava APIs as soon as commands are run, but it does not clearly inform the user at execution time that external data will be transmitted. In an agent environment, undisclosed outbound access can surprise users and increase privacy risk by querying personal fitness data from a third-party service.

Static analysis

No suspicious patterns detected.