T09 · Insecure Skill Coding Practices
- Location
scripts/kallyai.py:108- Finding
OAuth Tokens Exposed Through URL Query Parameters and Non-Atomic Credential File Permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/kallyai.py:108-115andscripts/kallyai.py:145-148
Vulnerability Type: OAuth credential exposure and insecure local secret-file creation
Risk Level: MediumVulnerable 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=STATETechnical 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, andchmod(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
View remediation
Remediation Suggestions
- 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.
- Make the OAuth state parameter mandatory at both the client and server.
- Return browser responses with restrictive headers, including
Cache-Control: no-store,Pragma: no-cache, and an appropriateReferrer-Policy. - Create credential files with restrictive permissions before writing secrets. Use
os.open()with mode0o600, then wrap the descriptor for writing. - 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().
- Create a temporary file in the same directory with mode
- 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.
- Where available, store refresh tokens in the operating system's credential manager instead of a plaintext JSON file.
- Revoke existing tokens after migrating the authentication flow.
- Replace query-parameter token delivery with OAuth 2.0 Authorization Code Flow with PKCE.
