Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.8 kB · 210 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211import axios from 'axios'import memoize from 'lodash-es/memoize.js'import { hostname } from 'os'import { getOauthConfig } from '../constants/oauth.js'import { checkGate_CACHED_OR_BLOCKING, getFeatureValue_CACHED_MAY_BE_STALE,} from '../services/analytics/growthbook.js'import { logForDebugging } from '../utils/debug.js'import { errorMessage } from '../utils/errors.js'import { isEssentialTrafficOnly } from '../utils/privacyLevel.js'import { getSecureStorage } from '../utils/secureStorage/index.js'import { jsonStringify } from '../utils/slowOperations.js'
/** * Trusted device token source for bridge (remote-control) sessions. * * Bridge sessions have SecurityTier=ELEVATED on the server (CCR v2). * The server gates ConnectBridgeWorker on its own flag * (sessions_elevated_auth_enforcement in Anthropic Main); this CLI-side * flag controls whether the CLI sends X-Trusted-Device-Token at all. * Two flags so rollout can be staged: flip CLI-side first (headers * start flowing, server still no-ops), then flip server-side. * * Enrollment (POST /auth/trusted_devices) is gated server-side by * account_session.created_at < 10min, so it must happen during /login. * Token is persistent (90d rolling expiry) and stored in keychain. * * See anthropics/anthropic#274559 (spec), #310375 (B1b tenant RPCs), * #295987 (B2 Python routes), #307150 (C1' CCR v2 gate). */
const TRUSTED_DEVICE_GATE = 'tengu_sessions_elevated_auth_enforcement'
function isGateEnabled(): boolean { return getFeatureValue_CACHED_MAY_BE_STALE(TRUSTED_DEVICE_GATE, false)}
// Memoized — secureStorage.read() spawns a macOS `security` subprocess (~40ms).// bridgeApi.ts calls this from getHeaders() on every poll/heartbeat/ack.// Cache cleared after enrollment (below) and on logout (clearAuthRelatedCaches).//// Only the storage read is memoized — the GrowthBook gate is checked live so// that a gate flip after GrowthBook refresh takes effect without a restart.const readStoredToken = memoize((): string | undefined => { // Env var takes precedence for testing/canary. const envToken = process.env.CLAUDE_TRUSTED_DEVICE_TOKEN if (envToken) { return envToken } return getSecureStorage().read()?.trustedDeviceToken})
export function getTrustedDeviceToken(): string | undefined { if (!isGateEnabled()) { return undefined } return readStoredToken()}
export function clearTrustedDeviceTokenCache(): void { readStoredToken.cache?.clear?.()}
/** * Clear the stored trusted device token from secure storage and the memo cache. * Called before enrollTrustedDevice() during /login so a stale token from the * previous account isn't sent as X-Trusted-Device-Token while enrollment is * in-flight (enrollTrustedDevice is async — bridge API calls between login and * enrollment completion would otherwise still read the old cached token). */export function clearTrustedDeviceToken(): void { if (!isGateEnabled()) { return } const secureStorage = getSecureStorage() try { const data = secureStorage.read() if (data?.trustedDeviceToken) { delete data.trustedDeviceToken secureStorage.update(data) } } catch { // Best-effort — don't block login if storage is inaccessible } readStoredToken.cache?.clear?.()}
/** * Enroll this device via POST /auth/trusted_devices and persist the token * to keychain. Best-effort — logs and returns on failure so callers * (post-login hooks) don't block the login flow. * * The server gates enrollment on account_session.created_at < 10min, so * this must be called immediately after a fresh /login. Calling it later * (e.g. lazy enrollment on /bridge 403) will fail with 403 stale_session. */export async function enrollTrustedDevice(): Promise<void> { try { // checkGate_CACHED_OR_BLOCKING awaits any in-flight GrowthBook re-init // (triggered by refreshGrowthBookAfterAuthChange in login.tsx) before // reading the gate, so we get the post-refresh value. if (!(await checkGate_CACHED_OR_BLOCKING(TRUSTED_DEVICE_GATE))) { logForDebugging( `[trusted-device] Gate ${TRUSTED_DEVICE_GATE} is off, skipping enrollment`, ) return } // If CLAUDE_TRUSTED_DEVICE_TOKEN is set (e.g. by an enterprise wrapper), // skip enrollment — the env var takes precedence in readStoredToken() so // any enrolled token would be shadowed and never used. if (process.env.CLAUDE_TRUSTED_DEVICE_TOKEN) { logForDebugging( '[trusted-device] CLAUDE_TRUSTED_DEVICE_TOKEN env var is set, skipping enrollment (env var takes precedence)', ) return } // Lazy require — utils/auth.ts transitively pulls ~1300 modules // (config → file → permissions → sessionStorage → commands). Daemon callers // of getTrustedDeviceToken() don't need this; only /login does. /* eslint-disable @typescript-eslint/no-require-imports */ const { getClaudeAIOAuthTokens } = require('../utils/auth.js') as typeof import('../utils/auth.js') /* eslint-enable @typescript-eslint/no-require-imports */ const accessToken = getClaudeAIOAuthTokens()?.accessToken if (!accessToken) { logForDebugging('[trusted-device] No OAuth token, skipping enrollment') return } // Always re-enroll on /login — the existing token may belong to a // different account (account-switch without /logout). Skipping enrollment // would send the old account's token on the new account's bridge calls. const secureStorage = getSecureStorage()
if (isEssentialTrafficOnly()) { logForDebugging( '[trusted-device] Essential traffic only, skipping enrollment', ) return }
const baseUrl = getOauthConfig().BASE_API_URL let response try { response = await axios.post<{ device_token?: string device_id?: string }>( `${baseUrl}/api/auth/trusted_devices`, { display_name: `Claude Code on ${hostname()} · ${process.platform}` }, { headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, timeout: 10_000, validateStatus: s => s < 500, }, ) } catch (err: unknown) { logForDebugging( `[trusted-device] Enrollment request failed: ${errorMessage(err)}`, ) return }
if (response.status !== 200 && response.status !== 201) { logForDebugging( `[trusted-device] Enrollment failed ${response.status}: ${jsonStringify(response.data).slice(0, 200)}`, ) return }
const token = response.data?.device_token if (!token || typeof token !== 'string') { logForDebugging( '[trusted-device] Enrollment response missing device_token field', ) return }
try { const storageData = secureStorage.read() if (!storageData) { logForDebugging( '[trusted-device] Cannot read storage, skipping token persist', ) return } storageData.trustedDeviceToken = token const result = secureStorage.update(storageData) if (!result.success) { logForDebugging( `[trusted-device] Failed to persist token: ${result.warning ?? 'unknown'}`, ) return } readStoredToken.cache?.clear?.() logForDebugging( `[trusted-device] Enrolled device_id=${response.data.device_id ?? 'unknown'}`, ) } catch (err: unknown) { logForDebugging( `[trusted-device] Storage write failed: ${errorMessage(err)}`, ) } } catch (err: unknown) { logForDebugging(`[trusted-device] Enrollment error: ${errorMessage(err)}`) }}