T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/auth/oauth_auth.py:14
- Finding
- OAuth Tokens Are Stored Without Enforced Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth/oauth_auth.py:14-17`; `scripts/playback/playback_control.py:22-25` **Vulnerability Type**: Insecure plaintext credential storage and file permissions **Risk Level**: Medium ### Vulnerable Code `scripts/auth/oauth_auth.py:14-17`: ```python def save_tokens(tok): os.makedirs(os.path.dirname(TOK_PATH), exist_ok=True) with open(TOK_PATH, "w", encoding="utf-8") as f: json.dump(tok, f) ``` `scripts/playback/playback_control.py:22-25`: ```python def write_tokens(tok): os.makedirs(os.path.dirname(TOK_PATH), exist_ok=True) with open(TOK_PATH, "w", encoding="utf-8") as f: json.dump(tok, f) ``` ### Technical Analysis The Skill stores Spotify access and refresh tokens in plaintext at `data/tokens.json`. Both the initial token save and subsequent refresh-token rewrite use the process's default file-creation permissions. The code does not: - Create the file with an explicit owner-only mode such as `0600`. - Verify the file owner or current permissions. - Reject symbolic links. - Repair an existing file with unsafe permissions. - Use an operating-system credential store. The effective permissions therefore depend on the host's umask, ACLs, and any pre-existing file. This also conflicts with `references/config.md:22`, which states that the implementation should warn when token-file permissions are too broad. ### Attack Path 1. The Skill runs on a shared host or under an environment with a permissive umask or ACL. 2. OAuth authentication or token refresh writes `data/tokens.json` with permissions readable by another local account. 3. A local attacker reads the file and obtains the access token and refresh token. 4. The attacker uses the access token directly or exchanges the refresh token for new access tokens. 5. The attacker invokes Spotify APIs with the permissions granted during authorization. If the host already contains an attacker-controlled symbolic link at the ...[truncated 716 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create token files atomically with owner-only permissions: ```python import os import tempfile def save_tokens(tok): token_dir = os.path.dirname(TOK_PATH) os.makedirs(token_dir, mode=0o700, exist_ok=True) fd, temporary_path = tempfile.mkstemp(dir=token_dir) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as stream: json.dump(tok, stream) stream.flush() os.fsync(stream.fileno()) os.replace(temporary_path, TOK_PATH) os.chmod(TOK_PATH, 0o600) except Exception: try: os.unlink(temporary_path) except OSError: pass raise ``` 2. Check that the target and parent directory are owned by the current user. 3. Reject a token path that is a symbolic link or resolves outside the intended data directory. 4. On startup, warn or fail if the token file is readable or writable by group or other users. 5. Apply equivalent access-control checks on platforms that use ACLs rather than POSIX modes. 6. Prefer an operating-system credential manager or secret vault for refresh-token storage. ]]>
