🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇧🇦 bs · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇹🇼 zh-TW
所有 OmniRoute 管理 API 路由均被划分为三个保护层级之一。分类是静态的,定义于 src/server/authz/routeGuard.ts 中,并在运行任何其他身份验证分支之前进行评估。
执行方式: isLocalOnlyPath(path) → 回环主机检查
绕过方式: 默认无。当请求携带具有 manage 作用域的有效
API 密钥时,LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES 中的路径有一个狭窄的豁免
(参见 Manage 作用域豁免)。
这些路由会生成子进程或执行运行时代码。将它们暴露给 非回环流量,会让获取到有效 JWT 的攻击者(例如 通过 Cloudflared/Ngrok 隧道)触发进程生成——这是一类已知的 CVE (GHSA-fhh6-4qxv-rpqj)。
GHSA-fhh6-4qxv-rpqj 是什么(攻击类型): 管理/代理服务器
暴露了一个会启动子进程的端点(npm install、node、浏览器、
代理、git、tar 等)。如果该端点可从主机外访问——因为
运维人员将 OmniRoute 置于 nginx/Cloudflare/Tailscale 隧道之后且 JWT
泄露,或者身份验证配置错误——攻击者就能把"调用 API"变成"在
主机上运行命令"(远程代码执行)。OmniRoute 通过在每个
可生成进程的路由上、在任何身份验证检查之前无条件地强制执行
回环主机检查来封堵这一漏洞:即使令牌通过隧道泄露,也无法触及进程生成。
完整的 LOCAL_ONLY 集合。 权威来源是
src/server/authz/routeGuard.ts 中的
LOCAL_ONLY_API_PREFIXES / LOCAL_ONLY_API_PATTERNS;下表反映了当前状态。
check-route-guard-membership 门禁会枚举可生成进程前缀下的每个 route.ts,
如果有任何一个未被归类为 local-only,则 CI 失败。
| 前缀 / 模式 | 仅限本地的原因 |
|---|---|
/api/mcp/ |
MCP 服务器 — 生成 stdio 桥接 + SSE 处理器 |
/api/cli-tools/runtime/ |
CLI 工具运行时 — 执行任意插件代码 |
/api/cli-tools/{omp,letta,grok-build,forge,jcode,qwen}-settings |
各工具的设置写入器,可修改主机上的工具二进制文件/配置 |
/api/cli-tools/{claude,cline,codewhale,codex,crush,deepseek-tui,droid,kilo,openclaw,pi,smelt}-settings |
与上述六个同类端点相同的 getCliRuntimeStatus() 进程生成(GHSA-35fw-cv32-2373) |
/api/cli-tools/{all-statuses,status,detect} |
CLI 清单探测 — 对每个工具生成 command -v / --version 进程(GHSA-35fw-cv32-2373) |
/api/cli-tools/antigravity-mitm |
Antigravity MITM 代理控制(生成/指向系统代理) |
/api/modality-bridge/video/ |
严格的可信回环 Video Bridge 运行时探测与内部提取桥接 |
/api/services/ |
嵌入式服务(9Router / CLIProxy / Bifrost / Mux / Dario)— npm install + 生成进程 |
/dashboard/providers/services/ |
嵌入式服务 UI 的反向代理 |
/api/tunnels/cloudflared |
安装/生成 cloudflared 二进制文件 |
/api/tunnels/tailscale/{install,enable,disable,login,start-daemon} |
在主机上安装/控制 tailscaled |
/api/copilot/ |
未经身份验证的 LLM 驱动器 — 默认仅限 CLI |
/api/tools/agent-bridge/ |
AgentBridge — 生成 MITM 服务器 + DNS 修改 |
/api/tools/traffic-inspector/ |
Traffic Inspector — http-proxy 监听器 + 系统代理 |
/api/settings/mitm |
启用 MITM 拦截(系统级代理状态) |
/api/issue-agent/ |
Issue 代理 — 针对仓库生成本地工具 |
/api/plugins/, /api/plugins |
插件 — 通过 worker_threads + child_process 加载/执行 |
/api/middleware/ |
用户中间件 — 在进程内加载/执行操作者代码 |
/api/system/version |
自动更新(仅 POST;GET/HEAD/OPTIONS 除外)— 生成 git checkout + npm install |
/api/db-backups/exportAll |
为导出存档生成 tar |
/api/local/ |
一键本地启动器(目前为 Redis)— 生成 podman/docker |
/api/headroom/start, /api/headroom/stop |
Headroom 代理生命周期 — 生成 python CLI / 向 PID 发送信号 |
/api/jobs, /api/jobs/ |
任务运行器控制 — 执行计划的主机端工作 |
/api/oauth/cursor/auto-import |
导入凭据前执行 execFile("which", ["cursor"]) |
/api/oauth/kiro/auto-import |
从主机读取 Kiro CLI 凭据文件 |
/api/skills/collect/ |
技能收集 — 检测/安装本地工具 |
/api/skills/install, /api/skills/executions |
Skill 处理器注册 + 执行 —— 可触达沙箱容器生成(GHSA-jx89) |
/api/discovery/ |
本地网络/提供者发现探测 |
/api/vnc-session (VNC_ROUTE_PREFIX) |
启动有头浏览器 + VNC 会话,用于交互式登录 |
/api/acp/agents |
ACP —— 发现并启动本地 CLI 代理二进制文件 |
/api/resilience/connections |
按账户的弹性 JSON(冷却、熔断、锁定)。仪表板 HTML 并非仅限本地访问。 |
/api/providers/cursor/agent-availability |
仪表板安装提醒检查 —— 会启动 cursor-agent status --format json |
/api/providers/{id}/login (regex) |
启动有头 Playwright Chromium 以进行 Web Cookie 登录 |
/api/providers/volcengine-plan/connect (regex) |
手动有头流程 + 基于会话的手机/短信自动登录(会启动 Playwright) |
/api/providers/{id}/refresh-cursor (regex) |
手动 Cursor 会话续期 —— 触发 cursor-agent |
/api/providers/{id}/chatgpt-web-codex-doctor (regex) |
诊断本地 Codex CLI 安装(会启动该二进制文件) |
违规时的响应: 403 LOCAL_ONLY
当且仅当请求携带 Authorization: Bearer <api-key> 且其元数据包含
manage 范围(或 admin)时,一部分 LOCAL_ONLY 路径才允许从非回环
地址访问。该豁免通过 LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES 按路径
显式控制,因此任何新增 LOCAL_ONLY 路径的默认行为仍是严格仅限回环。未认证的
请求以及使用非 manage 密钥的请求仍会以 403 LOCAL_ONLY 被拒绝。
目前唯一可豁免的前缀是 /api/mcp/。/api/cli-tools/runtime/ 和
/api/services/ 被有意排除在外,因为它们可以生成任意
子进程(npm install、node),而这正是 LOCAL_ONLY 层级
旨在防范的 CVE 类型。
#7895 —— mcp:connect 窄范围: /api/mcp/ 豁免还接受
持有窄范围 mcp:connect 的 Bearer 密钥
(src/shared/constants/managementScopes.ts::MCP_CONNECT_SCOPE),
通过 src/server/authz/policies/management.ts 中的
hasMcpConnectOrManageScope() 进行检查。
该范围仅适用于 /api/mcp/ —— mcp:connect 在任何其他
管理路由上均不授予任何权限(包括未来可能新增的任何其他 LOCAL_ONLY
豁免前缀),并且被有意排除在
MANAGEMENT_API_KEY_SCOPES 之外。持有 manage/admin 的密钥仍与之前
一样通过豁免;mcp:connect 是一种低权限替代方案,
适用于不需要广泛管理访问权限的远程纯 MCP 调用方。
| 请求 | 路径 | 结果 |
|---|---|---|
| 非回环,无 Bearer | /api/mcp/* |
403 LOCAL_ONLY |
非回环,Bearer 具有 manage 范围 |
/api/mcp/* |
允许 |
非回环,Bearer 具有 mcp:connect 范围 |
/api/mcp/* |
允许 |
非回环,Bearer 不具有 manage/mcp:connect |
/api/mcp/* |
403 LOCAL_ONLY |
非回环,Bearer 具有 mcp:connect 范围 |
/api/cli-tools/runtime/* |
403 LOCAL_ONLY |
非回环,Bearer 具有 manage 范围 |
/api/cli-tools/runtime/* |
403 LOCAL_ONLY |
| 回环,任意/无 Bearer | 任意 LOCAL_ONLY | 允许(门禁通过) |
如果你在反向代理或隧道(nginx、Caddy、Cloudflare
Tunnel、Tailscale、Ngrok)之后运行 OmniRoute,回环检查仍然会保护
上述能够生成进程的路由 —— 客户端地址为非回环的请求会在认证
执行之前以 403 LOCAL_ONLY 被拒绝,因此泄露的 JWT 无法触达进程生成。运维人员
仍需承担两项责任:
- 不要通过将客户端 IP 伪造为 loopback 来"修复" 403。 设置
X-Forwarded-For: 127.0.0.1,或使用将源地址重写为 loopback 的代理, 会重新打开本层级所封堵的那一类 RCE 漏洞。通过代理暴露 dashboard/API —— 绝不要暴露具备 spawn 能力的路由。 - 将 manage 作用域的绕过保持在最小范围。 只有
/api/mcp/可被绕过, 且必须持有manage作用域的 API 密钥。SPAWN_CAPABLE_PREFIXES永远无法 被加入绕过列表 —— zod schema 会拒绝它们,并且isLocalOnlyBypassableByManageScope在运行时同样拒绝它们(纵深防御), 这正是 dashboard 中 "cannot be made bypassable"(无法设为可绕过)的含义。同一 拒绝列表还包含少数几个不执行 spawn、但同样绝不能被远程访问的 LOCAL_ONLY 路由 ——/api/oauth/kiro/auto-import(读取主机本地的 Kiro 凭据文件)、/api/discovery/和/api/issue-agent/。运行时 快照加载器也会从已存储的设置行中丢弃此类条目,而旧版 JSON 导入 (/api/settings/import-json)永远不会写入绕过键。 每个LOCAL_ONLY_API_PREFIXES条目必须要么位于拒绝列表中,要么经过审查 确认为具备绕过资格(tests/unit/authz/local-only-bypass-spawn-denylist.test.ts)。/api/providers/下带有动态段和静态路径的 spawn 能力路由 (例如/login、/refresh-cursor)由src/shared/constants/spawnCapablePrefixes.ts中基于正则的配套常量SPAWN_CAPABLE_PATTERNS/SPAWN_CAPABLE_PATTERN_ANCESTORS覆盖, 而不是由扁平的SPAWN_CAPABLE_PREFIXES数组覆盖 —— 若要捕获它们, 扁平数组将不得不覆盖整个/api/providers/前缀,这会过度扩大远程 dashboard 合法用于 provider CRUD 的路由树范围。
审计访问 —— 用于验证没有来自主机外部的请求到达这些路由:
- 打开
/dashboard/settings/security上的 Authorization Inventory:它会渲染 实时的 LOCAL_ONLY 前缀列表、哪些前缀可绕过,以及编译期确定的 spawn 能力("cannot be made bypassable")集合。 - 在你的反向代理/访问日志中 grep 上述前缀与非 loopback 客户端地址的组合。
任何返回了
200而非403 LOCAL_ONLY的此类命中都意味着代理正在掩盖真实的 客户端 IP —— 请修复代理。 - OmniRoute 日志中针对这些路径之一的
403 LOCAL_ONLY是防护 按预期工作的表现,而不是需要抑制的错误。
某些路由必须保持对远程 dashboard 可达,但会为本地调用方运行主机侧探测
(which <binary>、<binary> --version、tailscale status --json)。
它们对 GET 而言并非 LOCAL_ONLY;相反,处理器通过
getRequestPeerLocality()(src/shared/utils/apiAuth.ts)读取可信的对端判定,
并且只为 loopback / lan 调用方运行探测。remote 调用方会得到
由非 spawn 来源(持久化状态、设置、文件系统检查)构建的相同响应结构,并标记为 probed: false。
| 路由 | 为远程调用方跳过的探测 |
|---|---|
POST /api/providers/{id}/test、/api/providers/test-batch |
CLI 运行时探测(allowLocalRuntimeProbe)和 CLI 回退 spawn |
GET /api/headroom/status |
which headroom 以及 python --version 探测 |
GET /api/tunnels/tailscale、GET /api/tunnels/tailscale/check |
which tailscale、tailscale status/funnel --json、which brew |
GET /api/tunnels/cloudflared(GET 豁免 LOCAL_ONLY) |
which cloudflared PATH 查找 |
新增的、会探测主机的读取路由应遵循同样的模式,或者直接归类为 LOCAL_ONLY。
执行方式: isAlwaysProtectedPath(path) → 跳过 requireLogin=false 绕过
绕过方式: 当 requireLogin=false 时无绕过;始终需要 JWT
这些路由具有破坏性或者不可逆。在"免密码" 安装中放行它们,意味着同一 LAN 上的任何人都可以清空数据库或杀死 服务器进程。
| 路径 | 原因 |
|---|---|
/api/shutdown |
终止服务器进程 |
/api/settings/database |
数据库导出、导入和清空 |
/api/db-backups |
完整数据库备份归档的访问 |
/api/settings/export-json |
导出完整的设置数据块(含密钥) |
/api/settings/import-json |
替换完整的设置数据块 |
/api/providers/health-autopilot/actions |
执行 autopilot 修复操作 |
/api/settings/obsidian |
为任意 vault 根目录签发可复用的 WebDAV 凭据 |
/api/relay/tokens |
签发、列出、编辑和吊销 relay 令牌 |
/api/cli-tools/apply |
重写操作员主机的 CLI 配置 |
/api/cli-tools/guide-settings |
写入由向导驱动的 CLI 配置文件 |
/api/cli-tools/hermes-agent-settings |
写入 Hermes agent 配置 |
/api/cli-tools/backups |
恢复和删除 CLI 配置备份 |
/api/log-export |
将调用日志发送到配置的目标 |
/api/restart |
重启服务器进程 |
违规时的响应: 401 Authentication required
/api/settings/obsidian 覆盖其 /webdav 子路由:POST 将 WebDAV 文件服务——由 Next.js 之前的自定义 Node 层提供,不在此管道之内——指向调用方选择的根目录,并回显新生成的 Basic 凭据;DELETE 用于轮换这些凭据;父级 POST 则存储 Obsidian REST API 令牌。GHSA-62vw 只是掩盖了 GET 的密码泄露问题;其签发仍位于 fail-open 层级(GHSA-7pq4-8pvv-rx7r)。enableObsidianVaultSync() 还会拒绝等同于数据目录、位于数据目录内部或包含数据目录的 vault。
上述层级只决定是否需要_某种_凭据。下载已存储凭据的路由在处理器中增加了第二道检查:GET /api/settings/export-json(解密后的提供方连接和原始 API 密钥)、GET /api/db-backups/export(SQLite 文件)以及 GET /api/db-backups/exportAll(完整归档)都会调用 requireOperatorCredential()(src/lib/api/operatorCredential.ts)。它接受仪表盘会话、本地 CLI 主体或已禁用登录的实例,并对任何作用域的 API 密钥(包括 manage 和 admin)以及 oma_ 访问令牌返回 403。与之对应的破坏性操作也遵守同样的边界:POST /api/db-backups/import(上传 .sqlite,包括 dryRun=true)、POST /api/db-backups(按 id 恢复已存储的备份)以及 POST /api/settings/import-json(旧版 JSON 恢复)会替换线上数据库或整个设置/连接/API 密钥集合,因此机器凭据同样无法触发它们。销毁恢复点也遵守同样的边界:DELETE /api/db-backups(清理——保留最新 N 个/丢弃早于 D 天的备份)和 PATCH /api/db-backups(保留策略设置),因为持久化的保留策略正是 cleanupDbBackups() 在每次自动备份后所应用的规则(src/lib/db/backup.ts),所以一个能够保存 keepLatest: 1 的机器凭据将在下一次快照时清空所有备份。列出和创建备份(GET/PUT /api/db-backups)仍使用普通的管理认证。同步令牌签发(POST /api/sync/tokens)使用同一个辅助函数,并额外接受受信任的 loopback 内部服务令牌。
在未配置管理密码(且无 INITIAL_PASSWORD)时,src/shared/utils/apiAuth.ts 中的 isAuthRequired() 仅对 loopback 对端保持匿名引导开放。loopback 的判定依据受信任的对端信号,按顺序为:带有令牌戳记的真实 TCP 对端(PEER_IP_HEADER + VIA_PROXY_HEADER,即策略所看到的内容)、管道自身的 AUTHZ_HEADER_PEER_LOCALITY 判定(即路由处理器所看到的内容,仅在设置了 OMNIROUTE_PEER_STAMP_TOKEN 时才受信任),或面向直连调用方的真实 socket 对端。Host / nextUrl.hostname 从不参与判定,且首个密码的写入(POST /api/settings/require-login)也处于同样的约束之下,而非对每个网络对端开放(GHSA-7pq4-8pvv-rx7r)。managementPolicy 会显式向下传递自己的 peerContext 判定,因此原始(剥离前)请求的头部永远不会决定该判定。
所有其他管理路由。除非配置了 requireLogin=false,否则需要认证。CLI 令牌可以认证这些路由(loopback + 有效 HMAC)。
managementPolicy.evaluate(ctx)
1. isLocalOnlyPath(path)?
→ 回环地址 → 继续执行
→ 非回环地址、具有 manage 作用域的 Bearer
且 isLocalOnlyBypassableByManageScope → 允许 (management_key)
→ 否则 → 拒绝 403 LOCAL_ONLY
2. isInternalModelSyncRequest(ctx)?
→ 允许 (system)
3. hasValidCliToken(headers)?
→ 允许 (cli) [回环地址 + timingSafeEqual HMAC 检查]
4. isAlwaysProtectedPath(path) 或 requireLogin=true?
→ isDashboardSessionAuthenticated?
→ 允许 (dashboard_session)
→ 不可绕过路径上的具有 manage 作用域的 Bearer?
→ 允许 (management_key)
→ 拒绝 401/403
5. requireLogin=false?
→ 允许 (anonymous)
步骤 1 中的 manage 作用域分支是唯一能够满足 LOCAL_ONLY 路由要求的认证路径;认证后端故障时返回 503(而不是 403),因此数据库过期不会静默降级为“拒绝”。
- 将路径前缀添加到
src/server/authz/routeGuard.ts中的LOCAL_ONLY_API_PREFIXES - 在
tests/unit/authz/routeGuard.test.ts中添加测试,断言isLocalOnlyPath()对新前缀返回 true - 绝不要跳过此步骤 — 请参阅
CLAUDE.md中的硬性规则 #15 - 决定:此路由是否也应属于
LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES? 默认答案是否。仅当该路由可以安全地向 manage 作用域持有者公开时才选择加入(即不会启动任意用户控制的代码)。
- 确认该路由不会执行用户提供的代码或命令。如果会,请停止 — 此例外机制并不适用。
- 将前缀追加到
src/server/authz/routeGuard.ts中的LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES - 在
tests/unit/authz/management-policy.test.ts中为全部四种请求形式添加覆盖:无 Bearer(403)、manage Bearer(允许)、非 manage Bearer(403),以及针对每个前缀的回归测试,确保/api/cli-tools/runtime/*即使带有 manage Bearer 也始终严格限制为回环地址。
| 文件 | 用途 |
|---|---|
src/server/authz/routeGuard.ts |
常量和辅助函数 |
src/server/authz/policies/management.ts |
评估逻辑 |
tests/unit/authz/routeGuard.test.ts |
层级辅助函数的单元测试 |
tests/unit/authz/management-policy.test.ts |
evaluate() 的单元测试 |
向 docs/openapi.yaml 添加新路由时,如果该路由已由 routeGuard.ts 分类,请应用对应的供应商扩展:
| routeGuard.ts 分类 | YAML 注解 | 强制执行规则 |
|---|---|---|
LOCAL_ONLY_API_PREFIXES |
x-loopback-only: true |
无条件阻止来自非回环地址的访问 |
ALWAYS_PROTECTED_API_PATHS |
x-always-protected: true |
即使 requireLogin=false 也要求认证 |
| 内部管理/调试路由 | x-internal: true |
默认不在 /dashboard/api-endpoints 中显示 |
| 无(公开/标准认证) | (无需注解) | 由标准 requireLogin 控制的访问 |
以下两个脚本用于确保 YAML 注解与 routeGuard.ts 之间的一致性:
scripts/check/check-openapi-coverage.mjs— 覆盖率低于 99% 时失败scripts/check/check-openapi-security-tiers.mjs— 当x-loopback-only或x-always-protected注解与编译时常量不一致时失败
这两个脚本都会在预提交钩子和 CI 中运行。
如果某个路由带有 x-always-protected 或 x-loopback-only 注解,但并不在
routeGuard.ts 常量中,覆盖率脚本将失败。正确的修复方式始终是让 YAML 与
routeGuard.ts 实际强制执行的规则保持一致 — 而不是在尚未实现相应强制执行逻辑的情况下,将路由添加到 routeGuard.ts。
docs/security/CLI_TOKEN.md— CLI 机器 ID 令牌docs/architecture/AUTHZ_GUIDE.md— 完整的授权流程docs/frameworks/MCP-SERVER.md— MCP 服务器传输方式和作用域