T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/frameo_client.py:87
- Finding
- OAuth Access and Refresh Tokens Stored Without Explicit Owner-Only Permissions## Vulnerability Details **File Location**: `scripts/frameo_client.py:87-93`, `scripts/frameo_client.py:99-111`; related insecure setup guidance at `SKILL.md:27-31` **Vulnerability Type**: Sensitive credential storage with insufficient permission enforcement **Risk Level**: Medium ### Vulnerable Code ```python token_data = r.json() with open(TOKEN_FILE, "w") as f: json.dump(token_data, f) print("Login successful! Token cached.") return token_data["access_token"] ``` ```python def refresh_token(refresh_tok): data = { "client_id": "frameo-app", "grant_type": "refresh_token", "refresh_token": refresh_tok, } r = requests.post(AUTH_URL, data=data) if r.status_code != 200: return None token_data = r.json() with open(TOKEN_FILE, "w") as f: json.dump(token_data, f) return token_data["access_token"] ``` The associated setup documentation also creates the credential file without explicitly setting restrictive permissions: ```bash echo '{"access_token": "YOUR_TOKEN"}' > ~/.frameo_token ``` ### Technical Analysis The login and token-refresh flows write the complete OAuth response to `~/.frameo_token`. This response can include both a short-lived access token and a longer-lived refresh token. The file is opened using the process's current umask, without explicitly enforcing mode `0600`. On systems with a permissive or misconfigured umask, the resulting file may be readable by other local users. An access token permits authenticated Frameo API requests until expiration. A refresh token is more sensitive because it can be exchanged for new access tokens and may extend unauthorized access significantly. The network destinations used by the authentication flow are fixed HTTPS endpoints under the official `frameo.net` domain. No transmission to an unknown or attacker-controlled host was found. The vulnerability concerns local token s ...[truncated 1260 chars]
- Remediation
- ## Remediation Suggestions 1. Create the token file atomically with owner-only permissions: ```python import os flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC fd = os.open(TOKEN_FILE, flags, 0o600) with os.fdopen(fd, "w") as f: json.dump(token_data, f) os.chmod(TOKEN_FILE, 0o600) ``` 2. Prefer an operating-system credential store, such as macOS Keychain, Windows Credential Manager, or the Linux Secret Service, instead of a plaintext JSON file. 3. Before reading an existing token file, verify that it is a regular file, owned by the current user, and not accessible by group or other users. 4. Avoid following symbolic links when creating or replacing the token file, and use atomic replacement to prevent race conditions. 5. Replace the documentation's shell redirection with an owner-only creation procedure, for example: ```bash install -m 600 /dev/null ~/.frameo_token printf '%s\n' '{"access_token": "YOUR_TOKEN"}' > ~/.frameo_token chmod 600 ~/.frameo_token ``` 6. Document token revocation and deletion procedures for lost or compromised systems.
