- Fix X-Forwarded-For header spoofing in requireSensitiveLocalAccess (use socket-level address only)
- Add model ID length validation (max 256 chars) in PUT /models/:provider
- Add provider name validation (alphanumeric, max 64 chars) to prevent path traversal
- Add stale entry eviction for unbounded quotaRateLimits Map (>1000 entries)
- Fix concurrent refresh race in usage aggregator (wait for in-flight before forced refresh)
- Sanitize internal URLs/paths from OAuth failure diagnostics
- Add ValidationError class for denylist violations (distinct from system errors)
- Make CLIProxy sync interval configurable via CCS_CLIPROXY_SYNC_INTERVAL env var
- Improve legacy continuity config error logging (unconditional warn)
- Add CACHE_VERSION history comments
- export buildProviderMap from provider capabilities and reuse in auth-types
- add provider descriptions to backend capabilities and sync display names
- remove ui CLIPROXY_PORT alias and use CLIPROXY_DEFAULT_PORT directly
- return stable string from getProviderDescription
- extend backend/ui parity tests for display name and description values
- make refresh ownership handling exhaustive in provider refresher
- extend backend/ui sync test to provider IDs and device-code providers
- remove DEFAULT_CURSOR_PORT re-export from use-cursor hook
- import cursor default port directly from shared defaults module
- move callback/auth-url names, token type values, and prefixes
- derive auth maps from capabilities instead of hardcoded records
- route refresh delegation through capability ownership metadata
- add 'kimi' to CLIProxyProvider union type
- register Kimi capabilities (device_code flow, moonshot alias)
- add OAuth config, auth prefixes, type values, channel maps
- add CLIProxy-delegated token refresh for Kimi
- add Kimi model catalog (k2.5, k2-thinking, k2)
- switch base-kimi.settings.json to CLIProxy mode (127.0.0.1:8317)
- update comments removing kimi from settings-based profile mentions
Closes#574
* fix(cliproxy): migrate deprecated gemini-claude-* model names to upstream claude-* names (#515)
* fix(cliproxy): migrate deprecated gemini-claude-* model names to upstream claude-* names
CLIProxyAPI registry no longer recognizes the gemini-claude-* prefix convention.
Model names in catalog, base config, and user settings are migrated to upstream
claude-* names. Auto-migration in env-builder rewrites existing user settings on
load and persists the change.
Closes#513
* fix: address code review feedback — sync UI layer and add migration tests
- Sync UI isNativeGeminiModel() with backend (remove gemini-claude- exclusion)
- Update UI model catalog agy entries from gemini-claude-* to claude-*
- Update CI/CD workflow and code-reviewer default model names
- Add unit tests for migrateDeprecatedModelNames() logic
* fix(hooks): isolate image type check before error-prone processing (#514)
* fix(hooks): isolate image type check before error-prone processing
Restructure processHook() into two phases so non-image Read calls
never see hook error messages. Phase 1 defensively checks tool name
and file extension, exiting 0 silently on any failure. Phase 2 only
runs for confirmed image/PDF files where errors are relevant.
Closes#511
* fix(hooks): sync image analyzer hook file on every profile launch
Add installImageAnalyzerHook() call to cliproxy executor, matching
the existing installWebSearchHook() pattern. This ensures the .cjs
file in ~/.ccs/hooks/ gets refreshed from the npm package on every
launch, so users receive hook updates after npm update.
* chore(release): 7.41.0-dev.1 [skip ci]
* fix(cliproxy): add fork:true for Claude model aliases in config generator (#523)
Config generator now outputs fork:true for Claude model alias entries,
ensuring both upstream (claude-*) and aliased (gemini-claude-*) model
names appear in /v1/models listings. Also preserves fork flag when
parsing user-added aliases during config regeneration.
Bumps config version to v7 to trigger regeneration on next ccs doctor.
Closes#522
* chore(release): 7.41.0-dev.2 [skip ci]
* feat(cliproxy): add account safety guards to prevent Google account bans (#516)
* feat(cliproxy): add account safety guards to prevent Google account bans
Implements cross-provider isolation to prevent Google from flagging
concurrent OAuth usage across different client IDs (ref: #509, #512).
Three pillars:
1. Auto-pause enforcement at session launch — conflicting accounts in
other Google OAuth providers are paused so CLIProxyAPI can't use them,
restored on session exit with crash recovery via auto-paused.json
2. Ban/disable detection — error responses matching Google ban patterns
auto-pause the affected account to prevent further damage
3. Cross-provider conflict warnings during OAuth registration
Key design decisions:
- PID-based session tracking for crash recovery (dead PID = restore)
- Timestamp comparison prevents restoring ban-paused accounts on exit
- Schema validation on auto-paused.json prevents corrupted state
- Falls back to warn-only when another session is managing isolation
* fix(cliproxy): address code review feedback (attempt 1/5)
- Re-read auto-paused.json before write in enforceProviderIsolation to
reduce concurrent write race window
- Use actual email from registry for display instead of raw accountId
- Export maskEmail for testability
- Add 27 unit tests covering ban detection, email masking,
cross-provider duplicate detection, enforcement lifecycle,
crash recovery, and timestamp-guarded restore
* fix(cliproxy): address remaining review feedback (attempt 2/5)
- Add handleBanDetection test verifying account pause on ban error
- Add warnCrossProviderDuplicates tests (true/false/non-Google)
- Document PID reuse limitation in isPidAlive JSDoc comment
* chore(release): 7.41.0-dev.3 [skip ci]
* feat(cliproxy): runtime quota monitoring during active sessions (#529)
* feat(cliproxy): add runtime quota monitoring during active sessions
Adds adaptive background quota polling to detect and respond to quota
exhaustion during active CLIProxy sessions. Prevents rate-limit-driven
account bans by auto-cooling exhausted accounts and switching defaults.
- Adaptive polling: 300s normal, 60s at 20% threshold, stops at 0%
- Stderr warnings at 20%, boxed exhaustion alerts at 0%
- Cooldown + default switch on exhaustion (existing patterns)
- Configurable via quota_management.runtime_monitor in config.yaml
- Timer.unref() prevents blocking process exit
- monitorStopped guard for in-flight poll safety
Closes#524
* fix: address code review feedback (attempt 1/5)
- M1: Round quotaPercent display with Math.round() to avoid ugly floats
- M2: Rename exhaust_threshold -> exhaustion_threshold for consistency
with existing auto.exhaustion_threshold config field
- M3: Replace async not.toThrow() with direct await assertion pattern
* fix: address code review feedback (attempt 2/5)
- Remove .claude/agent-memory/ from tracking and add to .gitignore
- Unify cooldown_minutes default to 5 (was 10 in runtime_monitor, 5 in auto)
- Add threshold validation in startQuotaMonitor (warn > exhaustion)
- Document intentional post-switch monitoring gap in code comment
* chore(release): 7.41.0-dev.4 [skip ci]
* fix(cliproxy): mask email in ban detection and fix JSDoc default
- Use maskEmail() in handleBanDetection output for consistency
- Fix cooldown_minutes JSDoc: default is 5, not 10
* chore(release): 7.41.0-dev.5 [skip ci]
* fix(cliproxy): address all review feedback (Low + informational)
- Add sync constraint comment on process.exit handler (executor)
- Add TOCTOU race acceptability comment (account-safety)
- Mask email in handleQuotaExhaustion reason string
- Use realistic exhaustion_threshold (5) in test configs
* chore(release): 7.41.0-dev.6 [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Move killWithEscalation to src/utils/process-utils.ts (DRY)
- Fix handleCancel to use exitCode === null and killWithEscalation
- Fix import ordering in both oauth-process.ts and headless-executor.ts
- Escalate SIGTERM to SIGKILL after 3s if Go binary is stuck in blocking HTTP call
- Extend timeout to 300s for device code flows matching CLIProxy binary polling window
- Apply SIGKILL escalation to both SIGINT cleanup and timeout handlers
Closes#314
CLIProxyAPIPlus already handles background token refresh for kiro (every
1 min via background_refresh.go) and other providers. CCS was incorrectly
returning "not implemented" errors for these providers, causing noisy
worker logs and preventing proper token expiry tracking.
Changes:
- Classify providers into CCS-managed (gemini), CLIProxy-delegated
(codex, agy, kiro, ghcp, qwen, iflow), and not-implemented (claude)
- Delegated providers return success with delegated flag instead of error
- Skip refresh_token requirement for delegated providers in expiry checker
- Filter delegated providers from background worker refresh loop
Closes#487
- Add promptOAuthModeChoice() for headless OAuth mode selection
- Add --port-forward flag to force port-forwarding mode
- Add conflict detection for --paste-callback + --port-forward
- Handle edge cases: Ctrl+C, invalid input, non-TTY stdin
- Update help docs with new flag
Closes#461
Fixes token mismatch where Gemini auth succeeds but token immediately
expires. The issue was that isAuthenticated() checked CLIProxy auth dir
(~/.ccs/cliproxy/auth/) but ensureGeminiTokenValid() only read from
~/.gemini/oauth_creds.json.
Changes:
- Add readCliproxyGeminiCreds() to read from CLIProxy auth dir first
- Add mapCliproxyToGeminiCreds() to convert CLIProxyAPI format to internal
- Track token source path for correct write-back on refresh
- Add writeCliproxyGeminiCreds() to preserve email/project_id fields
- Fall back to ~/.gemini/oauth_creds.json for backward compatibility
Closes#368
Fixes remaining edge cases from #382:
- Add anthropic/claude mapping to statsProviderMap in cliproxy-auth-routes
- Add 'claude' case to provider-refreshers switch statement
- Add 'claude' to 4 UI type definitions in api-client.ts
- Add 'claude' to cliproxy-dialog providers array and options
- Add 'claude' to use-cliproxy-auth-flow VALID_PROVIDERS
- Fix wizard index.tsx type cast to include all 8 providers
Claude was missing from:
- getAllAuthStatus() providers array in token-manager.ts
- PROVIDER_ASSETS, PROVIDER_COLORS, PROVIDER_NAMES in provider-config.ts
- PROVIDERS array in setup wizard constants.ts
- PLUS_ONLY_PROVIDERS in proxy settings
Add Claude as a first-class OAuth provider in CCS CLI and dashboard.
Backend support already exists in CLIProxyAPIPlus (port 54545).
Changes:
- Add 'claude' to CLIProxyProvider type and profile detector
- Add Claude OAuth config (port 54545, --anthropic-login flag)
- Add Claude to oauth-port-diagnostics flow types
- Add Claude token discovery prefixes (claude-, anthropic-)
- Add Claude model catalog (Opus 4.5, Sonnet 4.5/4, Haiku 4.5)
- Add Claude logo and provider display name
- Update variant config types and adapters
Closes#380
- Use checkCliproxyUpdate() in checkLatestVersion() for correct repo
- Dynamic error messages based on backend in service-manager
- Dynamic error messages based on backend in cliproxy-executor
- Generic "CLIProxy" message in oauth-process (no backend context)
- Fix connection tracking test: wait for 'close' event instead of only
checking socket.destroyed (more reliable in CI environments)
- Increase timeout from 500ms to 1000ms for CI latency
- Add proper socket cleanup after test
- Merge dev changes: add projectId parameter to registerAccount()
Display GCP project_id for Antigravity (agy) accounts in the Dashboard:
- Add projectId field to AccountInfo interface (account-manager.ts)
- Read project_id from auth files when discovering/registering accounts
- Pass projectId through token-manager when registering new accounts
- Add projectId to OAuthAccount type (api-client.ts)
- Add projectId to AccountRow type (auth-monitor/types.ts)
- Display project_id in account-item.tsx with FolderCode icon
- Show N/A warning with amber tooltip if project_id is missing
suggesting user remove and re-add account to fetch it
Note: project_id is read-only and respects privacy mode blur.
- Fix race condition in start() using Promise instead of boolean flag
- Document that tunnel intentionally doesn't limit response sizes (streaming)
- Improve token upload failure visibility with actionable user message
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* feat(cliproxy): add HTTPS tunnel for remote proxy mode
Claude Code doesn't support HTTPS in ANTHROPIC_BASE_URL directly (undici
limitation). This adds an HTTP→HTTPS tunnel proxy for remote CLIProxyAPI
connections.
Changes:
- Add HttpsTunnelProxy: local HTTP server tunneling to remote HTTPS
- Add CodexReasoningProxy HTTPS support and path prefix stripping
- Remote mode now uses root paths (/v1/messages) not provider-prefixed
- Add remote token uploader for syncing OAuth tokens to remote server
- Auto-upload tokens after OAuth auth when remote mode is enabled
Flow for remote HTTPS:
Claude CLI → CodexReasoningProxy → HttpsTunnel → Remote HTTPS
Built with [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: address PR review issues for HTTPS tunnel proxy
- Add connection tracking with activeConnections Set for proper cleanup
- Add port validation after start() to reject port 0
- Add Authorization header fallback injection in buildForwardHeaders()
- Handle client disconnect (premature close) and request errors
- Improve error handling in uploadTokenToRemoteAsync (log instead of silent catch)
- Add comprehensive tests for HttpsTunnelProxy (97% coverage)
- Add integration tests for remote-token-uploader
- Add stripPathPrefix unit tests for remote mode
Built with [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: bump Node.js engine requirement to >=18.0.0
FormData and Blob APIs used in remote-token-uploader require Node.js 18+.
Addresses coderabbit review comment about Node.js engine compatibility.
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: address remaining PR suggestions
- Add verbose parameter to registerAccountFromToken for proper propagation
- Improve stripPathPrefix with path normalization (double slashes, leading slash)
- Add edge case tests for path normalization
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: add timeout to flaky npm CLI tests
Tests 'handles empty arguments gracefully' and 'handles very long argument'
were missing timeout option in execSync, causing occasional timeouts when
bun test's 5000ms limit was reached before CLI completed.
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
* fix: address new PR review feedback
- Add path segment boundary check to prevent partial matches (/codex vs /codextra)
- Add hostname validation in HttpsTunnelProxy constructor
- Add race condition protection with 'starting' flag in start()
- Sanitize error messages (detailed only in verbose mode)
- Update comments for clarity (regex behavior)
- Add comprehensive tests for all edge cases
Built [OnSteroids](https://onsteroids.ai)
Co-Authored-By: OnSteroids <built@onsteroids.ai>
---------
Co-authored-by: OnSteroids <built@onsteroids.ai>
Kiro/GHCP OAuth tokens have empty email field, causing all accounts to
use accountId='default' and overwrite each other. This fix:
- Add PROVIDERS_WITHOUT_EMAIL constant for kiro/ghcp identification
- Require nickname for kiro/ghcp during registration (CLI + web UI)
- Use nickname as accountId instead of email for these providers
- Enforce nickname uniqueness to prevent collisions
- Update discoverExistingAccounts() to generate unique IDs (kiro-1, etc.)
Closes#258, #267
- Clear stdinKeepalive interval on SIGINT/SIGTERM signal handlers
- Add cancelProjectSelection() to timeout/exit/error handlers
- Close server on error path in testLocalhostBinding to prevent fd leak
- Add TTL-based cleanup for stale auth sessions (10 min expiry)
- Use DEVICE_CODE_TIMEOUT_MS constant instead of hardcoded value (DRY)
CLIProxyAPIPlus has a 15-second timer that prompts for manual URL paste.
If user completes browser auth after this timer fires but before the
non-blocking check, the prompt blocks forever on stdin since CCS pipes
stdin but doesn't write to it.
Workaround: Send newline every 16s for authorization code flows to skip
the manual prompt and continue polling for callback.
Port cleanup before OAuth was skipped for providers whose ports were
only in comments. This caused hanging when stale processes blocked
the callback port from previous auth attempts.
Changes:
- auth-types.ts: Add codex (1455), agy (51121), iflow (11451) to map
- oauth-port-diagnostics.ts: Update iflow from device_code to auth_code flow
- Add Claude (54545) to doc comments for future reference
When Gemini OAuth tokens expire, CLIProxyAPI attempts synchronous
refresh during API calls. If this fails or times out, the socket
connection closes abruptly causing Claude CLI's undici to report
UND_ERR_SOCKET errors.
This fix adds proactive token validation before spawning Claude CLI:
- New gemini-token-refresh.ts module handles Gemini OAuth refresh
- ensureTokenValid() in token-manager.ts checks expiry with 5min lead
- cliproxy-executor.ts calls ensureTokenValid() after auth check
The token is now refreshed BEFORE Claude CLI connects, eliminating
the race condition that caused socket errors.
Fixes#256
* fix(cliproxy): pass variant port to executor for isolation
Variants configured with dedicated ports (8318-8417) were not using
their assigned port. The executor always defaulted to 8317.
Changes:
- Add port field to ProfileDetectionResult interface
- Pass variant.port from profile-detector to ccs.ts
- Forward port to execClaudeWithCLIProxy options
- Update executor priority: CLI flags > variant port > config.yaml > default
Closes#228
* fix(cliproxy): propagate port in unified config and UI preset handlers
Edge case fixes identified in codebase review:
- Unified config variant detection: add settingsPath and port fields
- Provider editor: use variant port in handleApplyPreset/handleCustomPresetApply
- preset-utils: add optional port parameter to applyDefaultPreset()
* chore(release): 7.11.1-dev.1 [skip ci]
* fix(cliproxy): use correct default port (8317) for remote HTTP connections
Root cause: Inconsistent default port logic across code paths.
- Test Connection used 8317 (correct)
- Actual API calls used 80 (wrong)
Changes:
- Add centralized getRemoteDefaultPort() helper in config-generator.ts
- Fix proxy-target-resolver.ts to use shared helper
- Fix rewriteLocalhostUrls() and getRemoteEnvVars() in config-generator.ts
- Update remote-proxy-client.ts to use shared helper (DRY)
Fixes all 3 reported issues:
1. Port empty → now correctly uses :8317 instead of :80
2. BASEURL construction → now includes correct port
3. CLIProxy Plus auth → now fetches from remote on correct port
* chore(release): 7.11.1-dev.2 [skip ci]
* feat(delegation): add Claude Code CLI flag passthrough
Add explicit passthrough support for key Claude Code CLI flags:
- --max-turns: Limit agentic turns (prevents infinite loops)
- --fallback-model: Auto-fallback when model overloaded
- --agents: Dynamic subagent JSON injection
- --betas: Enable experimental features
Maintain extraArgs catch-all for future Claude Code flags.
Update help command with new "Delegation Flags" section.
Closes#89
* test(delegation): add comprehensive CLI flag passthrough tests
Add 45 test cases covering all edge cases for CLI flag passthrough:
- DelegationHandler: timeout/max-turns/fallback-model/agents/betas validation
- HeadlessExecutor: duplicate flag filtering, undefined vs truthy checks
* chore(release): 7.11.1-dev.3 [skip ci]
* fix(ui): enable cancel button during OAuth authentication
Resolves#234 - Cancel button was disabled during authentication flow,
preventing users from canceling the OAuth process.
Changes:
- Add auth-session-manager.ts for tracking active OAuth sessions
- Add POST /cliproxy/auth/:provider/cancel endpoint to abort sessions
- Kill spawned CLIProxy auth process when cancel is triggered
- Enable Cancel button in AddAccountDialog during authentication
- Add cancel support to QuickSetupWizard auth step
- Update useCancelAuth hook to call backend cancel endpoint
* chore(release): 7.11.1-dev.4 [skip ci]
* fix(prompt): add stdin.pause() to prevent process hang after password input
Fixes#236. The password() method called resume() on stdin but never
paused it in cleanup, keeping the event loop alive indefinitely.
* chore(release): 7.11.1-dev.5 [skip ci]
* feat(cliproxy): add --allow-self-signed flag for HTTPS connections (#227)
Previously, allowSelfSigned was hardcoded to true for all HTTPS protocol
connections, forcing use of the native https module which has issues with
Cloudflare-proxied connections causing timeouts.
This change:
- Adds --allow-self-signed CLI flag (default: false)
- Adds CCS_ALLOW_SELF_SIGNED environment variable
- Uses standard fetch API by default for HTTPS (works with valid certs)
- Only uses native https module when --allow-self-signed is specified
Usage:
- For production HTTPS proxies with valid certs: no flag needed
- For dev proxies with self-signed certs: use --allow-self-signed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* chore(release): 7.11.1-dev.6 [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Shun Kakinoki <39187513+shunkakinoki@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When Kiro OAuth callback redirects to Kiro IDE instead of CLI, this fix:
- Auto-attempts token import from Kiro IDE storage
- Adds --import flag for manual import (ccs kiro --import)
- Shows clear instructions if import fails
Fixes#212
- Change default to noIncognito=true (normal browser) for reliability
- Always print OAuth URL to terminal for VS Code popup detection
- Move incognito toggle from Proxy settings to Kiro provider page
- Add --incognito flag to opt into incognito mode (was --no-incognito)
- Update help text to reflect new defaults
- Add "Kiro: Use normal browser" toggle in config dashboard (Proxy section)
- Add --no-incognito documentation to ccs --help output
- Show hint after successful Kiro auth about saving AWS credentials
Kiro OAuth always opens in incognito browser mode, which doesn't save
login credentials. Users need to re-enter email each authentication.
Changes:
- Add noIncognito option to OAuthOptions interface
- Add kiro_no_incognito config option to CLIProxyConfig
- Pass --no-incognito flag to CLIProxyAPI binary when enabled
- Support both CLI flag (--no-incognito) and config.yaml setting
Usage:
- CLI: ccs kiro --auth --no-incognito
- Config: cliproxy.kiro_no_incognito: true in config.yaml
Device Code flows (Qwen, GHCP) may require interactive terminal input
before generating the device code. For example, Qwen prompts for email.
Previously, all auth processes used piped stdin which blocked user input.
Now Device Code flows use 'inherit' for stdin, allowing users to respond
to prompts directly in the terminal.
Authorization Code flows (Gemini, Codex) still use piped stdin for
programmatic project selection.
Closes#188
When authenticating with GitHub Copilot using device code flow,
the user code was not being displayed. This fix:
- Parses device codes from CLIProxy output in oauth-process.ts
- Displays code prominently in CLI terminal with box formatting
- Broadcasts device code events via WebSocket for UI display
- Creates DeviceCodeDialog component for web UI (ccs config)
- Follows same pattern as Gemini project selection dialog
Closes#189