T09 · Insecure Skill Coding Practices
- Location
glancely/core/auth/google_oauth.py:36- Finding
Google OAuth Refresh Tokens Are Stored Without Explicitly Restrictive Permissions
- Content
View full analysis
Credentials: CONFIG_DIR.mkdir(parents=True, exist_ok=True) creds: Credentials | None = None if TOKEN_PATH.is_file(): creds = Credentials.from_authorized_user_file(str(TOKEN_PATH), SCOPES) if creds and creds.valid: return creds if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) TOKEN_PATH.write_text(creds.to_json(), encoding="utf-8") return creds if not CREDENTIALS_PATH.is_file(): raise AuthSetupError( f"Missing OAuth client at {CREDENTIALS_PATH}.\n" f" 1. Go to https://console.cloud.google.com/apis/credentials\n" f" 2. Create OAuth client ID → Desktop app\n" f" 3. Download the JSON, save as {CREDENTIALS_PATH}\n" f" 4. Re-run install.sh (or this command)." ) flow = InstalledAppFlow.from_client_secrets_file(str(CREDENTIALS_PATH), SCOPES) creds = flow.run_local_server(port=0, open_browser=True, prompt="consent") TOKEN_PATH.write_text(creds.to_json(), encoding="utf-8") return creds ``` ### Technical Analysis The OAuth token, including its reusable refresh token, is written with `Path.write_text()` without explicitly setting the file mode. The resulting permissions depend on the process umask. The containing directory is also created without an explicit restrictive mode. On systems with an unusually permissive umask, or where the configuration directory already has permissive permissions, another local account may be able to read `~/.glancely/token.json`. The same concern applies to the user-supplied OAuth client-secret file, whose permissions are not validated before use. The token uses a Google Calendar authorization scope and can be r ...[truncated 1043 chars]- Remediation
View remediation
