| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- // 把合并后的文件访问策略映射为 Codex 的权限配置(permission profile),随 thread/start 的 config 传入。
- // 已验证(2026-10-23,codex 0.164):":root" = "deny" 可以做默认拒绝;Codex 的沙箱辅助程序是指向 codex 二进制的
- // 链接,位于 CODEX_HOME/tmp/arg0-linux,这两处必须可读,否则沙箱无法启动(docs/design/03-providers.md §6)
- import path from 'node:path'
- import type { AppSettings, PermissionMode } from '@agent-server/shared'
- import type { ResolvedAccess } from '../../services/access-policy'
- import { hasGlob, realpathLoose } from '../../lib/paths'
- export const PROFILE_NAME = 'agent_server'
- /**
- * 服务场景下关闭的 Codex 功能(每次对话的 config 覆盖)。已验证(2026-10-23,codex 0.164):服务账号登录 ChatGPT 后,
- * Codex 会自动安装 openai-curated-remote 中的插件(Google Drive、Gmail、Pages、Sites 等),把插件技能、应用连接器
- * 和插件安装建议带进对话,调用方可能借此访问账号绑定的个人数据。对话只使用 AgentServer 提供的能力(技能、MCP Gateway)
- */
- export const DISABLED_FEATURES = {
- plugins: false,
- remote_plugin: false,
- apps: false,
- tool_suggest: false,
- skill_mcp_dependency_install: false,
- // 子代理的事件与审批不经过 AgentServer
- multi_agent: false,
- browser_use: false,
- computer_use: false,
- } as const
- type Access = 'read' | 'write' | 'deny'
- type FsTable = Record<string, Access | Record<string, Access> | number>
- export interface CodexThreadConfig {
- default_permissions: string
- permissions: Record<string, { filesystem: FsTable; network: { enabled: boolean } }>
- shell_environment_policy: { inherit: 'core'; set: Record<string, string> }
- features: Record<string, boolean>
- [key: string]: unknown
- }
- export function buildThreadConfig(opts: {
- access: ResolvedAccess
- permissionMode: PermissionMode
- settings: AppSettings
- codexBin: string
- codexHome: string
- /** Playground 中开放远程插件与应用连接器(插件安装建议、子代理等仍然关闭) */
- allowPlugins?: boolean
- }): CodexThreadConfig {
- const { access } = opts
- const fs: FsTable = {
- ':root': access.mode === 'broad' ? 'read' : 'deny',
- ':minimal': 'read',
- // Codex 沙箱辅助程序及其指向的二进制
- [realpathLoose(path.join(opts.codexHome, 'tmp', 'arg0-linux'))]: 'read',
- [path.dirname(realpathLoose(opts.codexBin))]: 'read',
- // 已安装的 Codex 技能(脚本与资源需要在沙箱中可读)
- [realpathLoose(path.join(opts.codexHome, 'skills'))]: 'read',
- }
- const workspace = realpathLoose(access.workspaceDir)
- for (const p of access.readable) if (p !== '/' && p !== workspace) fs[p] = 'read'
- for (const p of access.writable) if (p !== workspace) fs[p] = opts.permissionMode === 'readonly' ? 'read' : 'write'
- // 宽松模式下屏蔽其他会话的工作区与服务数据目录(当前工作区更具体,仍然可写)
- if (access.mode === 'broad') for (const p of access.softDeny) fs[p] = 'deny'
- const workspaceRules: Record<string, Access> = { '.': opts.permissionMode === 'readonly' ? 'read' : 'write' }
- let hasGlobRule = false
- for (const entry of access.hardDeny) {
- if (path.isAbsolute(entry) && !hasGlob(entry)) {
- fs[entry] = 'deny'
- } else if (!path.isAbsolute(entry)) {
- // 相对通配(如 **/*.env)作用于工作区
- workspaceRules[entry] = 'deny'
- hasGlobRule = true
- } else {
- fs[entry] = 'deny'
- hasGlobRule = true
- }
- }
- fs[':workspace_roots'] = workspaceRules
- if (hasGlobRule) fs.glob_scan_max_depth = 6
- const py = opts.settings.paths.pythonRuntime
- const agentDir = path.join(access.workspaceDir, '.agent')
- return {
- features: opts.allowPlugins ? { ...DISABLED_FEATURES, plugins: true, remote_plugin: true, apps: true } : { ...DISABLED_FEATURES },
- // 未开放插件时,即使有应用被启用也不暴露连接器工具
- apps: opts.allowPlugins ? { _default: { enabled: true } } : { _default: { enabled: false, destructive_enabled: false, open_world_enabled: false } },
- default_permissions: PROFILE_NAME,
- permissions: { [PROFILE_NAME]: { filesystem: fs, network: { enabled: false } } },
- // 只继承基础环境变量(PATH、HOME 等),不把服务进程中的密钥带进命令
- shell_environment_policy: {
- inherit: 'core',
- set: {
- PATH: `${path.join(py, 'bin')}:/usr/local/bin:/usr/bin:/bin`,
- VIRTUAL_ENV: py,
- MATPLOTLIBRC: opts.settings.paths.matplotlibrc,
- MPLCONFIGDIR: path.join(agentDir, 'mpl'),
- NODE_PATH: opts.settings.paths.nodeModules,
- TMPDIR: path.join(agentDir, 'tmp'),
- },
- },
- }
- }
|