T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/gmncode_usage.py:56
- Finding
- Bearer Token Cache Is Not Created with Atomic Restrictive Permissions## Vulnerability Details **File Location**: `scripts/gmncode_usage.py:56-72` **Related Token Storage Logic**: `scripts/gmncode_usage.py:127-132` **Vulnerability Type**: Insecure sensitive-file creation and silent permission failure **Risk Level**: Medium ### Vulnerable Code ```python def ensure_file_mode(path: pathlib.Path, mode: int) -> None: try: path.chmod(mode) except OSError: pass def secure_write_json(path: pathlib.Path, payload: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8") ensure_file_mode(path, stat.S_IRUSR | stat.S_IWUSR) ``` The affected function stores the access token through the following call: ```python def _save_cached_token(self, access_token: str, expires_in: int) -> None: expires_at = int(dt.datetime.now(dt.timezone.utc).timestamp()) + int(expires_in) secure_write_json(TOKEN_CACHE, { "access_token": access_token, "expires_at": expires_at, "base_url": self.base_url, }) ``` ### Technical Analysis The cache file is created or truncated by `Path.write_text()` before mode `0600` is applied. Its initial permissions therefore depend on the process umask. With a common umask of `022`, a newly created file may initially have mode `0644`, creating a time-of-check/time-of-use exposure window in which another local user can read the bearer token. The implementation also suppresses every `OSError` raised by `chmod()`. If permission hardening fails because of filesystem behavior, ownership, access-control rules, or another operating-system error, execution continues while the sensitive file may remain readable by unintended users. Additionally, `Path.write_text()` follows symbolic links. The fixed cache location reduces practical exploitability, but an attacker who can manipulate the cache path or its parent directories could potentially redirect the token write. The code do ...[truncated 1626 chars]
- Remediation
- ## Remediation Suggestions 1. Create the cache directory with mode `0700` and verify that it is owned by the current user. 2. Create the token file with mode `0600` at creation time rather than applying permissions after writing. 3. Use `os.open()` with restrictive and defensive flags where supported: ```python flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(path, flags, 0o600) try: with os.fdopen(fd, "w", encoding="utf-8") as handle: json.dump(payload, handle, ensure_ascii=False) handle.flush() os.fsync(handle.fileno()) except Exception: try: path.unlink() except OSError: pass raise ``` 4. Prefer writing to a securely created mode-`0600` temporary file in the same directory and then replacing the destination atomically with `os.replace()`. 5. Reject symbolic links and validate that both the cache directory and destination are regular files owned by the current user. 6. Do not suppress permission-setting failures. Abort token caching, remove any insecurely created file, and report a clear error. 7. Consider continuing without persistent caching when secure storage cannot be guaranteed. 8. Add tests using permissive umasks and simulated `chmod()` failures to verify that the token is never observable with group or world permissions.
