Files
claude-code/src/services/auth/hostGuard.ts
claude-code-best ee63c17697 feat: 添加登录认证增强(workspace key、host guard、auth status)
- hostGuard: workspace API key 仅限 api.anthropic.com,OAuth 限定 subscription plane
- saveWorkspaceKey: sk-ant-api03- 前缀校验,安全写入缓存
- AuthPlaneSummary/WorkspaceKeyInput: 登录 UI 组件
- getAuthStatus: 认证状态查询

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
2026-05-09 23:04:15 +08:00

96 lines
3.5 KiB
TypeScript

/**
* Host guard utilities for multi-auth routing.
*
* These guards enforce that workspace API key requests only go to Anthropic's
* API host and that subscription OAuth requests stay on the subscription plane.
* This prevents credential leakage to third-party hosts.
*
* Design: ~/.claude/rules/deep-debug/security.md §2 (read-only investigation first,
* then minimal guard at earliest detection point).
*/
import { logError } from '../../utils/log.js'
/** The canonical Anthropic API host for workspace (non-subscription) endpoints. */
const WORKSPACE_API_HOST = 'api.anthropic.com'
/**
* Asserts that `url` points to Anthropic's workspace API host.
*
* Called before every workspace API key request (agents, vaults, memory_stores,
* skills) to prevent the API key from being sent to a third-party host.
*
* @throws {Error} if the URL does not resolve to api.anthropic.com
*/
export function assertWorkspaceHost(url: string): void {
let hostname: string
try {
hostname = new URL(url).hostname
} catch {
throw new Error(
`assertWorkspaceHost: invalid URL "${url}". Workspace API key requests must target ${WORKSPACE_API_HOST}.`,
)
}
if (hostname !== WORKSPACE_API_HOST) {
throw new Error(
`assertWorkspaceHost: refusing to send workspace API key to non-Anthropic host "${hostname}". ` +
`Workspace API key requests must target ${WORKSPACE_API_HOST}. ` +
`If you are using a custom base URL, workspace endpoints are only available on the Anthropic API.`,
)
}
}
/**
* Asserts that `url` points to the Anthropic subscription base URL.
*
* Called before subscription-OAuth requests (schedule, ultrareview, teleport)
* to ensure they only target the expected host. Less strict than assertWorkspaceHost —
* it still allows the configured BASE_API_URL which may vary in test/staging.
*
* @throws {Error} if the URL does not resolve to api.anthropic.com
*/
export function assertSubscriptionBaseUrl(url: string): void {
let hostname: string
try {
hostname = new URL(url).hostname
} catch {
throw new Error(
`assertSubscriptionBaseUrl: invalid URL "${url}". Subscription OAuth requests must target ${WORKSPACE_API_HOST}.`,
)
}
if (hostname !== WORKSPACE_API_HOST) {
throw new Error(
`assertSubscriptionBaseUrl: refusing subscription OAuth request to non-Anthropic host "${hostname}". ` +
`Subscription OAuth requests must target ${WORKSPACE_API_HOST}.`,
)
}
}
/**
* Warns (but does not throw) when Anthropic API environment variables are set
* alongside OpenAI-compat configuration.
*
* This prevents silent credential confusion when a user has both
* ANTHROPIC_API_KEY and OPENAI_API_KEY / CLAUDE_CODE_USE_OPENAI set.
* The warning is informational — the calling code decides what to do.
*/
export function assertNoAnthropicEnvForOpenAI(): void {
const hasOpenAIMode =
process.env['CLAUDE_CODE_USE_OPENAI'] === '1' ||
Boolean(process.env['OPENAI_API_KEY'])
const hasAnthropicKey = Boolean(process.env['ANTHROPIC_API_KEY'])
if (hasOpenAIMode && hasAnthropicKey) {
logError(
new Error(
'assertNoAnthropicEnvForOpenAI: Both ANTHROPIC_API_KEY and OpenAI-compat mode are set. ' +
'ANTHROPIC_API_KEY is for Anthropic workspace endpoints (/v1/agents, /v1/vaults, /v1/memory_stores). ' +
'OpenAI-compat mode routes /v1/messages to a third-party provider. ' +
'These are separate credential planes and will not interfere, but verify this is intentional.',
),
)
}
}