T09 · Insecure Skill Coding Practices
- Location
scripts/frameo_client.py:87- Finding
OAuth Access and Refresh Tokens Stored Without Explicit Owner-Only Permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/frameo_client.py:87-93,scripts/frameo_client.py:99-111; related insecure setup guidance atSKILL.md:27-31
Vulnerability Type: Sensitive credential storage with insufficient permission enforcement
Risk Level: MediumVulnerable 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_tokenTechnical 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 mode0600.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.netdomain. No transmission to an unknown or attacker-controlled host was found. The vulnerability concerns local token s ...[truncated 1260 chars]- Remediation
View remediation
Remediation Suggestions
-
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) -
Prefer an operating-system credential store, such as macOS Keychain, Windows Credential Manager, or the Linux Secret Service, instead of a plaintext JSON file.
-
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.
-
Avoid following symbolic links when creating or replacing the token file, and use atomic replacement to prevent race conditions.
-
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 -
Document token revocation and deletion procedures for lost or compromised systems.
-
