T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/tapd_client.py:59
- Finding
- OAuth Bearer Token Cached Without Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tapd_client.py:59-68` **Vulnerability Type**: Plaintext credential storage with insufficient access controls **Risk Level**: Medium ### Vulnerable Code ```python def _save_token_cache(self): """保存 OAuth token 到缓存""" cache = { "access_token": self._access_token, "expires_at": self._token_expires_at, "updated_at": time.time() } with open(self.TOKEN_CACHE_FILE, "w") as f: json.dump(cache, f) ``` ### Technical Analysis The client stores a live OAuth bearer token in plaintext at the predictable path `~/.tapd_token_cache.json`. The file is opened using the standard `open(..., "w")` operation without explicitly enforcing owner-only permissions. For a newly created file, its permissions depend on the process umask. With a permissive umask, the cache may be readable by other local users. If the file already exists with overly broad permissions, opening it for writing does not correct those permissions. The implementation also does not validate whether the destination is a regular file owned by the current user or an unexpected symbolic link. A bearer token is sufficient to authenticate requests without knowledge of the OAuth client secret. Consequently, anyone who obtains the cached token can replay it until it expires. The network transmission itself is necessary for the declared TAPD integration and is restricted to `https://api.tapd.cn`; the vulnerability is the insecure local storage of the resulting credential. ### Attack Path 1. A legitimate user runs the TAPD client with valid OAuth credentials. 2. The client requests an access token from TAPD. 3. `_save_token_cache()` writes the bearer token to `~/.tapd_token_cache.json`. 4. The file is created under a permissive umask or retains previously insecure permissions. 5. Another local user or compromised process reads the cache file. 6. The attacker extracts the `access_token` value. 7. The attacker ...[truncated 886 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Atomically create the token cache with owner-only mode `0600`, rather than relying on the process umask. - Correct the permissions of an existing cache before reading or writing it. - Validate that the target is a regular file owned by the current user and reject unexpected symbolic links. - Write through a securely created temporary file in the same directory, flush it, and atomically replace the destination. - Consider making persistent token caching opt-in or allowing users to disable it entirely. - Delete expired tokens and provide an explicit command to clear the cache. - Avoid storing the token if a platform credential manager or operating-system keyring is available. Example hardening approach: ```python import os import stat flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(self.TOKEN_CACHE_FILE, flags, 0o600) try: os.fchmod(fd, stat.S_IRUSR | stat.S_IWUSR) with os.fdopen(fd, "w") as f: fd = -1 json.dump(cache, f) finally: if fd != -1: os.close(fd) ``` Additional ownership and regular-file checks should be applied before trusting an existing cache. ]]>
