Kim uses a tiered token storage approach to maximize security:
-
Refresh Token (Long-lived, sensitive)
- Where: Stored exclusively in OS secure storage
- macOS: Keychain
- Windows: Windows Credential Manager
- Linux: Secret Service / libsecret (if available)
- Access: Only Rust code can read it
- Python: Never receives refresh token
- Why: Refresh tokens grant indefinite access; they must be protected as highly as passwords
- Where: Stored exclusively in OS secure storage
-
Access Token (Short-lived, less sensitive)
- Where: Passed only via environment variables to Python subprocess
- Lifetime: Typically 1 hour; may be shorter
- Python: Receives via
KIM_GOOGLE_ACCESS_TOKEN; used only for current request - After Request: Environment is cleaned; token is never persisted
- Why: Short lifetime limits exposure window
-
Project ID (Non-secret metadata)
- Where: Local configuration file, not secure storage
- Example:
kim-gemini-abc123 - Risk Level: Low — project IDs are not secret
- Why: Needed for
x-goog-user-projectheader; no secrecy required
Kim explicitly avoids logging tokens:
# ✓ SAFE: HTTP error message does not include bearer token
logger.error("Gemini OAuth API error: HTTP %s: %s", exc.code, _truncate(raw, 2000))
# ✗ UNSAFE (never done in Kim):
# logger.error("Request headers: %s", headers) # Would include Authorization headerUser-facing errors never include token values:
# ✓ SAFE: User sees clear error without secrets
raise RuntimeError("Google access token is expired. Please reconnect Google for Kim.")
# ✗ UNSAFE (never done in Kim):
# raise RuntimeError(f"Token refresh failed: {token}")Kim uses PKCE for desktop OAuth flow:
- Why: PKCE is the OAuth 2.0 standard for public clients (desktop apps) and doesn't require a client secret
- How: Rust generates a random code verifier, hashes it, and includes both in the OAuth flow
- Benefit: Client secret never leaves Kim; even if intercepted, the auth code is useless
Kim's deployment:
-
Client ID (
KIM_GOOGLE_OAUTH_CLIENT_ID)- Shipped with Kim binary
- Not secret; safe to expose
- Identifies Kim as the OAuth application
-
Client Secret (
KIM_GOOGLE_OAUTH_CLIENT_SECRET)- Optional (not required with PKCE)
- If provided at build time, used only in Rust (never passed to Python)
- Example: For server-to-server flows or internal CI/CD
- Never committed to source control
Kim requests only the minimum scopes needed:
-
Base Identity Scopes (always)
openid— User identityemail— User email addressprofile— User profile information
-
Gemini API Scope (for API access)
https://www.googleapis.com/auth/generative-language.retriever- Grants access only to Gemini models, not other Google APIs
-
Cloud Platform Scope (for project creation only, if needed)
https://www.googleapis.com/auth/cloud-platform- Requested only when user chooses "Use Google free tier"
- Allows Kim to create a project and enable Gemini API
Principle: Scopes are incremental and explicit. Kim never requests broad access.
When Kim creates a Google Cloud project in your account:
-
Project Creation
- Kim uses your OAuth token and Google Cloud APIs
- Kim generates a project ID:
kim-gemini-<random-suffix> - Google Cloud creates the project in your Google account
- Kim stores only the project ID (not secret)
-
API Enablement
- Kim enables Generative Language API on your project
- No data is transferred to Kim's account
-
Billing
- Usage is charged to your project, not Kim's
- Your free-tier quota (if applicable) applies
- Kim never charges your account directly
-
Project Access
- Kim can only access Gemini API (scoped to that specific API)
- Kim cannot access other services (Cloud Storage, Cloud SQL, etc.)
- You can revoke access anytime via Google Cloud Console
If you use an API key:
-
API Key Ownership
- You own and manage the key
- The key is tied to your Google Cloud project
-
Scope of Access
- The key grants access to Gemini API
- No access to other resources
-
Revocation
- You can revoke the key anytime in Google Cloud Console
- No Antigravity access
- No Gemini CLI endpoints
- No internal Google client spoofing
- No reverse-engineered Google code
- Does not fetch user's Gemini API keys
- Does not read user's existing Google Cloud projects without permission
- Does not access user's Gmail, Drive, or other Google services
- Does not use Kim's shared project as fallback quota
- Does not hide costs by using company credentials
- If your free tier is exceeded, usage is blocked (no silent upgrade)
- No API keys in config files
- No refresh tokens in unencrypted storage
- No secrets in logs or error messages
Kim shows consent screens for:
-
Google Sign-In
- Standard Google OAuth consent
- Shows scopes: identity, email, profile, Gemini API access
-
Google Cloud Project Creation (free-tier mode only)
- Additional scope: Cloud Platform
- Clearly explains: "Kim needs permission to create a Google Cloud project and enable the Gemini API for you."
- Kim discloses all scopes upfront
- Users can review project access in Google Cloud Console
- Users can revoke access anytime
If your refresh token is compromised (e.g., keychain is stolen):
- Immediate: Disconnect Google for Kim in Settings
- Google Account: Visit myaccount.google.com/security
- Confirm: Check "Security event" notifications for unauthorized Kim access
- Reset: Sign back in to Kim to generate a new refresh token
If your free-tier project is used suspiciously:
- Google Cloud Console: Visit console.cloud.google.com
- Audit: Check "Cloud Audit Logs" for unexpected API calls
- Revoke: Go to Settings, disconnect Gemini, or delete the project
- Report: Contact Google Cloud support if needed
Kim includes test mocks in tests/test_gemini_oauth_provider.py:
def test_requires_exactly_one_auth_path():
"""Ensure user cannot accidentally enable multiple auth modes."""
config_both = {"api_key": "key", "oauth_access_token": "token"}
with pytest.raises(EnvironmentError, match="ambiguous"):
GeminiProvider(config_both)For development, use the API key mode:
export GOOGLE_API_KEY=<your-key>Never commit API keys. Use .env or .envrc locally.
For GitHub Actions or other CI systems:
- Store secrets as GitHub Secrets (never in code)
- Use
export KIM_GOOGLE_OAUTH_CLIENT_SECRET=...at build time - PKCE flow ensures desktop OAuth still works in CI
- No tokens in logs
- No secrets in source code
- Refresh token stored in OS secure storage only
- Access token passed via environment only
- Project ID stored as plaintext (not secret)
- PKCE enabled for desktop OAuth
- Scopes are minimal
- User-owned project mode does not fall back to Kim's quota
- Error messages do not expose token values
- Token expiry is checked before use