Server-managed Settings:远端下发的策略抢占与“Fail-open”安全模型
本质
Server-managed settings(服务器托管设置)的核心并非将一份远端的 settings.json 同步到本地并替换用户配置,而是为 Claude Code 提供了一层动态下发的最高优先级策略(policySettings)。
这层策略的设计初衷是让企业或 Anthropic 官方能够跨设备、实时地管控 CLI 的行为,例如强制开启沙箱隔离、限制可调用的工具、或者统一下发安全 Hooks。在 Claude Code 的配置体系中,它具有三个核心特质:**缓存优先(Cache-first)**确保启动速度;**热更新(Hot-reload)**保证策略实时生效;**故障放行(Fail-open)**则确保在网络不可用时 CLI 不会因无法拉取策略而彻底瘫痪。
源码级拆解
实现这套机制的第一步是资格判定。在 claude-code-opensource/src/services/remoteManagedSettings/syncCache.ts 中,isRemoteManagedSettingsEligible() 规定了只有特定的身份(如 Anthropic 第一方 Provider、非自定义 Base URL 的 Team/Enterprise 账户)才会被纳入拉取范围。这避免了对个人 API Key 用户或本地开发代理造成不必要的网络干扰。
在配置层级上,Server-managed settings 被接进 policySettings 这一 Source。根据 claude-code-opensource/src/utils/settings/settings.ts 的实现,policySettings 内部遵循“首位胜出(First source wins)”原则,顺序如下:
- 远端服务器托管设置(Server-managed settings)
- 操作系统级管理工具(MDM / macOS plist / Windows HKLM)
- 本地文件系统中的托管配置
- Windows 注册表用户项(HKCU)
只要远端策略被拉取成功,它就会直接抢占最高优先级,压过本地 MDM 或文件系统中的所有策略设置。
在启动流程中,claude-code-opensource/src/main.tsx 中的 preAction 阶段会以非阻塞(Fire-and-forget)的方式调用 loadRemoteManagedSettings()。这意味着它不会拖慢 CLI 的首屏渲染。为了实现“缓存优先”,syncCacheState.ts 会优先同步读取 ~/.claude/remote-settings.json,让启动瞬间就有一份旧策略可用,随后的异步网络请求才会尝试拉取更新。
真正的远端交互逻辑封装在 claude-code-opensource/src/services/remoteManagedSettings/index.ts:
- 智能同步:客户端会计算本地缓存的 SHA-256 校验和(Checksum),并配合
If-None-Match请求头发送,服务端如果发现策略未变则返回304 Not Modified,节省流量。 - Fail-open 哲学:如果网络失败或超时,CLI 会继续使用旧缓存运行;如果连缓存都没有且网络失败,CLI 依然会启动,只是暂时没有最高层策略。
- 热更新逻辑:一旦成功拉取,系统会每小时轮询一次;当检测到策略变更时,会触发
settingsChangeDetector.notifyChange('policySettings'),进而驱动权限、沙箱、插件等子系统实时重载。
最后是安全防线。并非所有远端改动都会被静默接受。securityCheck.tsx 会监控如 Shell 执行权限、环境变量白名单、Hooks 等敏感字段的变化。在交互模式下,如果远端下发了可能造成风险的新策略,CLI 会弹出一个强制性的审批对话框,要求本地用户显式确认。这确保了即便是被托管的设置,也依然受用户最终知情的约束。
边界条件
首先,托管策略是“抢占”而非“融合”。一旦远端策略生效,它会完全屏蔽掉你本地通过 managed-settings.json 定义的所有其他策略。
其次,Fail-open 带来的时间窗口。虽然它保证了可用性,但也意味着在首次安装或清空缓存后的第一次启动时,如果网络拉取较慢,可能会存在几秒钟的“无策略运行”窗口。
第三,热更新的局限性。尽管配置可以热重载,但某些底层设置(如 OpenTelemetry 遥测初始化、部分进程级环境变量)在 CLI 启动那一刻就已经固化,这些改动往往需要重启会话才能真正生效。
第四,用户侧控制的本质。官方文档明确将其定义为“Client-side control”。这意味着这套方案旨在帮助合规管理和一致性配置,而非对抗性的硬防御。如果用户拥有本地机器的最高权限且具备逆向能力,这层逻辑理论上是可以被本地绕过的。
继续探索
如果你关注托管策略能控制的具体配置项,建议深入研究 Settings Precedence 中的 Source 列表。
如果你关心安全审核对话框的具体判定规则,可以查看 ManagedSettingsSecurityDialog 相关的源码实现。
如果你正在集成自定义遥测或环境集成,应重点关注 entrypoints/init.ts 中远端设置与初始化逻辑的先后顺序。
源码锚点
claude-code-opensource/src/services/remoteManagedSettings/syncCache.ts: 托管策略的拉取资格判定。
📄 src/services/remoteManagedSettings/syncCache.ts — 托管策略的拉取资格判定。
let cached: boolean | undefined
export function resetSyncCache(): void {
cached = undefined
resetLeafCache()
}claude-code-opensource/src/services/remoteManagedSettings/syncCacheState.ts:~/.claude/remote-settings.json缓存文件的读写逻辑。
📄 src/services/remoteManagedSettings/syncCacheState.ts — `~/.claude/remote-settings.json` 缓存文件的读写逻辑。
const SETTINGS_FILENAME = 'remote-settings.json'
let sessionCache: SettingsJson | null = null
let eligible: boolean | undefined
export function setSessionCache(value: SettingsJson | null): void {
sessionCache = value
}claude-code-opensource/src/services/remoteManagedSettings/index.ts: 核心同步流程、校验和计算与轮询机制。
📄 src/services/remoteManagedSettings/index.ts — 核心同步流程、校验和计算与轮询机制。
type SettingsJson,
SettingsSchema,
} from '../../utils/settings/types.js'
import { sleep } from '../../utils/sleep.js'claude-code-opensource/src/services/remoteManagedSettings/securityCheck.tsx: 远端敏感配置变更的安全审批守卫。
📄 src/services/remoteManagedSettings/securityCheck.tsx — 远端敏感配置变更的安全审批守卫。
export type SecurityCheckResult = 'approved' | 'rejected' | 'no_check_needed';
/**
* Check if new remote managed settings contain dangerous settings that require user approval.
* Shows a blocking dialog if dangerous settings have changed or been added.
*
* @param cachedSettings The current cached settings (may be null for first run)
* @param newSettings The new settings fetched from the API
* @returns 'approved' if user accepts, 'rejected' if user declines, 'no_check_needed' if no dangerous changes
*/
export async function checkManagedSettingsSecurity(cachedSettings: SettingsJson | null, newSettings: SettingsJson | null): Promise<SecurityCheckResult> {
// If new settings don't have dangerous settings, no check needed
if (!newSettings || !hasDangerousSettings(extractDangerousSettings(newSettings))) {
return 'no_check_needed';
}
// If dangerous settings haven't changed, no check needed
if (!hasDangerousSettingsChanged(cachedSettings, newSettings)) {
return 'no_check_needed';
}
// Skip dialog in non-interactive mode (consistent with trust dialog behavior)
if (!getIsInteractive()) {
return 'no_check_needed';
}
// Log that dialog is being shown
logEvent('tengu_managed_settings_security_dialog_shown', {});
// Show blocking dialogclaude-code-opensource/src/utils/settings/settings.ts: 托管设置在全局优先级链条中的抢占顺序实现。
📄 src/utils/settings/settings.ts — 托管设置在全局优先级链条中的抢占顺序实现。
type EditableSettingSource,
getEnabledSettingSources,
type SettingSource,
} from './constants.js'
import { markInternalWrite } from './internalWrites.js'