T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/setup_auth.py:46
- Finding
- OAuth credentials and PKCE state are stored without restrictive file permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup_auth.py:46-47, 65-75`; `scripts/auth.py:72-74` **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: High ### Vulnerable Code ```python with open('.auth_state.json', 'w') as f: json.dump({'code_verifier': code_verifier}, f) ``` ```python if os.path.exists('.auth_state.json'): with open('.auth_state.json') as f: state_data = json.load(f) code_verifier = state_data.get('code_verifier') os.remove('.auth_state.json') try: flow.fetch_token(code=code, code_verifier=code_verifier) token_path = os.path.join(CREDENTIALS_DIR, 'token.json') os.makedirs(CREDENTIALS_DIR, exist_ok=True) with open(token_path, 'w') as f: f.write(flow.credentials.to_json()) ``` The same default-permission write pattern is used when a refreshed token is saved: ```python creds.refresh(Request()) with open(token_path, 'w') as token: token.write(creds.to_json()) ``` ### Technical Analysis The Skill stores a PKCE code verifier and serialized Google OAuth credentials using ordinary `open(..., 'w')` calls. It does not explicitly set the credentials directory to mode `0700` or sensitive files to mode `0600`. The effective permissions therefore depend on the process umask and existing directory permissions. In a permissively configured or shared environment, another local user or process could read: - The Google OAuth access token - The long-lived refresh token - The OAuth client information - The temporary PKCE verifier The refresh token is particularly sensitive because it can be used to obtain new access tokens until the authorization is revoked. The granted scopes are limited to Gmail read-only and Google Calendar event access, but those scopes still expose private correspondence and permit Calendar modification. ### Attack Path 1. The user completes OAuth authorization. 2. `setup_auth.py` writes `token.json` with permissions inherited from the r ...[truncated 942 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create the credential directory with owner-only permissions: ```python os.makedirs(CREDENTIALS_DIR, mode=0o700, exist_ok=True) os.chmod(CREDENTIALS_DIR, 0o700) ``` 2. Create sensitive files using an explicit owner-only mode: ```python fd = os.open(token_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, 'w') as f: f.write(flow.credentials.to_json()) ``` 3. Apply the same protection to `.auth_state.json`, client-secret files, token refresh writes, and any backup or temporary files. 4. Use atomic replacement: write to a protected temporary file, flush and `fsync`, then use `os.replace`. 5. Store OAuth state in the protected credentials directory rather than the current working directory. 6. Delete temporary OAuth state in a `finally` block after completion or failure. 7. Document token revocation procedures and advise users to revoke authorization if credential exposure is suspected. ]]>
