ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

openGym AI教练容器隔离机制:非特权用户与fail-closed降权设计

openGym AI教练容器隔离机制:非特权用户与fail-closed降权设计 openGym AI教练容器隔离机制非特权用户与fail-closed降权设计【免费下载链接】openGymSelf-hosted gym body-weight tracker — plan routines, log workouts (supersets, warm-ups, cardio), see which muscles are trained, fatigued or detrained, import from FitNotes/Strong/Hevy, passkey login. Your data, your server.项目地址: https://gitcode.com/GitHub_Trending/op/openGymopenGym 是一个自托管的健身房与体重的追踪工具——你可以规划训练计划、记录训练超级组、热身、有氧并查看哪些肌群被训练、疲劳或退步。除了这些它还内置了一个可选的AI 教练能根据你的记录设计并修订训练计划。这个 AI 教练默认关闭只有管理员在应用中打开才会出现。而当它真正跑起来时最关键的工程设计不是它有多聪明而是它有多安全openGym 通过非特权用户和fail-closed失败即关闭降权把 AI 运行时牢牢关在一个读不到任何秘密的笼子里。这篇文章拆解这套隔离机制帮助你理解为什么它既能让 AI 教练工作又不会让你的数据冒险。为什么自托管的 AI 教练必须降权自托管的核心承诺是你的数据你的服务器。./data目录里存着用户档案、passkey、每个用户的状态文件与会话密钥。一旦引入一个能执行外部运行时的 AI 教练风险就来了如果这个 AI 进程能以高权限运行它就能读到./data里的一切。所以 openGym 的设计原则是——让 AI 运行时在启动的那一刻就失去访问秘密的能力。这套原则落在两处一个是操作系统层面的非特权用户另一个是进程启动前的fail-closed 检查。第一道隔离HTTP 直连 vs 容器内运行时并非所有 AI 提供商都需要这套复杂的隔离。openGym 区分了两类提供商见 adapters/index.js提供商运行方式是否需要降权Anthropic / OpenAI / Gemini / 兼容端点一条 HTTPS 请求无子进程否spawns: falseClaude (Agent SDK) / Codex CLI在容器内启动子进程运行时是spawns: true对于纯 HTTP 的提供商一个任务就是 api 进程发出的一次fetch——没有子进程也就没有降权的对象。隔离机制非特权coach用户、./coach-auth挂载只为容器内运行时的两种提供商而存在。第二道隔离非特权 coach 用户读不到 ./data在 api/Dockerfile 里openGym 用一行命令创建了一个非特权、无登录 shell的coach用户RUN adduser -D -H -s /sbin/nologin coach这个用户的 uid无法读取/data。这就是那道把提供商运行时挡在用户、passkey 和会话密钥之外的控制线。关键设计在于AI 任务不是以 api 进程的高权限运行的而是降权到coach用户再启动。无论 Claude Agent SDK 还是 Codex CLI子进程被 spawn 时都会带上coach的 uid/gid见 spawn.js。即使某个 CLI 运行时起了歹心想翻文件它也找不到任何可读的秘密。fail-closed降权失败就拒绝一切任务这是整套设计里最值得称道的部分。openGym 的降权逻辑失败即关闭fail-closed而不是失败即放行fail-open。在 spawn.js 的canDropPrivileges()中只有当服务器以 root 运行且coach用户存在时才能把任务降权到该用户。如果降权无法执行比如镜像里少了coach用户或服务器不是 root在 Linux 上任何任务都会被拒绝管理员卡片会明确显示原因。为什么要这么严格源码注释说得很直白降权曾经会静默关闭自己——它只在服务器以 root 运行时才生效。如果哪天有人在 Dockerfile 里为了别的加固目的加了一行普通的USER node运行时就会悄无声息地继承服务器的高权限。一个在别人重构时自己消失的控制是你事后才会发现的隐患。在任务入队的地方jobs.js这道检查是硬性闸门if (adapterFor(cfgStore.load().provider)?.spawns ! false) { const priv canDropPrivileges(); if (!priv.ok) throw new CoachError(unprivileged, Coach jobs are disabled: ${priv.why}); }注意spawns ! false的判断——它问的是适配器自己而不是去猜测。这意味着即使在一台没有coach用户的主机上API key 直连的提供商照常工作只有运行时类的提供商才会被拒绝。环境从零构建不继承任何秘密光有非特权用户还不够。openGym 更进一步子进程的环境变量是从零构建的而不是从父进程过滤的见 config.js 的jobEnv()。const env { PATH: .../bin, HOME: jobDir, TMPDIR: jobDir };也就是说子进程不会继承RP_ID、ADMIN_UIDS、VAPID 密钥或服务器持有的任何别的东西。凭据通过唯一的、明确命名的环境变量注入别无他途。Claude Agent SDK 的env选项是替换而非扩展子进程环境正好契合这个契约——RP_ID、ADMIN_UIDS、VAPID 密钥连意外继承的机会都没有。模型被锁在只进文本、只出 JSON的通道里对于运行在容器内的 Claude Agent SDKopenGym 用四个文档化的开关把它锁死见 claude.js 的LOCKDOWN选项效果tools: []禁用所有内置工具——没有 Read、没有 Bash、没有 Grep模型无论如何被提示都没有文件系统工具settingSources: []不加载任何用户/项目/本地设置文件主机上的东西无法事后放宽上面的限制skills: []同理技能也不加载strictMcpConfig不允许任何 MCP 服务器从环境配置中混进来这些选项被导出为一个冻结对象Object.freeze并在 adapters.test.js 中按值断言。换句话说——如果有一天有人不小心重新启用了某个工具CI 会直接变红而不是悄悄给模型多一分能力。Codex CLI 走的是类似思路见 codex.js 的argvFor()每个任务都带着三个加固参数运行--ephemeral不写会话文件、--ignore-user-config不读管理员看不见的配置、--skip-git-repo-check。凭据的存放与隔离最后一层隔离关乎凭据本身。Codex 会保存一个可刷新的登录缓存openGym 把它放在./coach-auth——故意是./data的兄弟目录而不是它内部的文件夹。原因写在 Dockerfile 的注释里官方文档教用户用tar czf … data/做备份。任何放在./data下的东西都会进入每一个备份归档——而一个活的刷新令牌一旦进了归档拷到笔记本或网盘后依然有效。把缓存放在./data之外才能让备份说明保持诚实。这个目录由coach用户所有、权限0700因为写入它的任务已经降权到了该用户。如何启用这套 AI 教练如果你想体验只需构建带运行时的那个镜像目标默认目标是不带任何 AI 运行时的API_TARGETcoach docker compose up -d --build api然后进入设置 → 管理后台 → AI 教练卡片连接你的提供商并测试教练。管理员卡片会如实告诉你任务是否真的在无权限状态下运行、以及花的是哪个账户的钱。小结openGym 的 AI 教练隔离机制是自托管 AI这个矛盾组合下的一次克制的工程实践非特权用户让 AI 运行时在操作系统层面就读不到./datafail-closed 降权让隔离失效这件事永远不会静默发生——降不了权任务直接不跑环境从零构建让秘密无法被继承LOCKDOWN 与按值断言让能力回退在 CI 里变成一次显眼的红色构建。对新手来说你不需要理解每一行代码也能放心用这套设计的每一环都在回答同一个问题——如果 AI 出问题了它能碰到我的东西吗答案是不能。更多设计细节见 docs/AI_COACH.md 与 api/coach/ 源码目录。【免费下载链接】openGymSelf-hosted gym body-weight tracker — plan routines, log workouts (supersets, warm-ups, cardio), see which muscles are trained, fatigued or detrained, import from FitNotes/Strong/Hevy, passkey login. Your data, your server.项目地址: https://gitcode.com/GitHub_Trending/op/openGym创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表