T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/kallyai.py:108
- Finding
- OAuth Tokens Exposed Through URL Query Parameters and Non-Atomic Credential File Permissions## Vulnerability Details **File Location**: `scripts/kallyai.py:108-115` and `scripts/kallyai.py:145-148` **Vulnerability Type**: OAuth credential exposure and insecure local secret-file creation **Risk Level**: Medium ### Vulnerable Code ```python def save_token(access_token: str, refresh_token: str = None, expires_in: int = 3600): """Save token securely (0600 permissions).""" TOKEN_FILE.write_text(json.dumps({ "access_token": access_token, "refresh_token": refresh_token, "expires_at": time.time() + expires_in - 60, })) TOKEN_FILE.chmod(0o600) ``` ```python if "access_token" in params: auth_result["access_token"] = params["access_token"][0] auth_result["refresh_token"] = params.get("refresh_token", [None])[0] auth_result["state"] = params.get("state", [None])[0] ``` The corresponding OAuth flow is documented in `references/api-reference.md:47-57`: ```text GET /v1/auth/cli?redirect_uri=http://localhost:PORT&state=OPTIONAL_STATE After authentication, redirects to: http://localhost:PORT?access_token=TOKEN&refresh_token=TOKEN&expires_in=3600&state=STATE ``` ### Technical Analysis The OAuth callback transports both the access token and refresh token in the URL query string. Query parameters can be retained in browser history and may be visible to browser extensions, local browser telemetry, diagnostic tooling, or other software that records navigated URLs. A refresh token is particularly sensitive because it can potentially be exchanged for new access tokens after the original access token expires. The credential file is also written before restrictive permissions are applied. `Path.write_text()` creates or truncates the file using permissions derived from the process umask, and `chmod(0o600)` is only called after the sensitive contents have been written. This produces a race window during which another local account may be able to ...[truncated 2075 chars]
- Remediation
- ## Remediation Suggestions 1. Replace query-parameter token delivery with OAuth 2.0 Authorization Code Flow with PKCE. - Return only a short-lived, single-use authorization code to the localhost callback. - Exchange the code and PKCE verifier for tokens through a TLS-protected back-channel request. - Do not place access tokens or refresh tokens in URLs. 2. Make the OAuth state parameter mandatory at both the client and server. 3. Return browser responses with restrictive headers, including `Cache-Control: no-store`, `Pragma: no-cache`, and an appropriate `Referrer-Policy`. 4. Create credential files with restrictive permissions before writing secrets. Use `os.open()` with mode `0o600`, then wrap the descriptor for writing. 5. Prefer an atomic update strategy: - Create a temporary file in the same directory with mode `0o600`. - Write and flush the credential data. - Optionally call `fsync()`. - Atomically replace the destination with `os.replace()`. 6. Before reading an existing credential file, verify that it is owned by the current user, is a regular file rather than a symbolic link, and has no group or world permissions. 7. Where available, store refresh tokens in the operating system's credential manager instead of a plaintext JSON file. 8. Revoke existing tokens after migrating the authentication flow.
