Background Agents(Open-Inspect)的部署安全模型建立在一个明确前提上:
这是一个单租户系统,适合部署在公司或团队内部,所有登录用户都被视为同一组织中的可信成员。
它不是面向 SaaS 多租户场景设计的。也就是说,系统默认:
这个模块主要解释三件事:
Open-Inspect 的 GitHub 权限模型分成两条线:
| 凭据 | 主要用途 | 权限边界 |
|---|---|---|
| GitHub App Token | git clone、fetch、push 等 Git 操作 |
GitHub App 被安装到的仓库 |
| User OAuth Token | 创建 PR、获取用户信息 | 当前 GitHub 用户有权限访问的仓库 |
| Sandbox Auth Token | Sandbox 调用控制面接口 | 单个 session |
| WebSocket Token | 实时会话鉴权 | 单个 session |
其中最重要的是前两类。
系统会安装一个 GitHub App 到组织仓库中。控制面在服务端生成短期的 GitHub App installation token,然后通过 git credential helper 按需提供给 sandbox。
这意味着:
也意味着一个很关键的边界:
系统不会在创建 session 时逐个检查“当前用户是否有这个仓库的访问权限”。
只要用户已经进入系统,并且目标仓库在 GitHub App 的安装范围内,Agent 就可能访问该仓库。
用户通过 GitHub 登录时,系统会拿到该用户的 OAuth Token。这个 token 主要用于:
因此,PR 创建时通常有这样的行为:
这个设计保证了“PR 归属”尽量准确,但它并不等价于“session 创建前做了仓库级权限校验”。
在这个项目的安全模型里,session 创建更接近“任务启动入口”,而不是强访问控制边界。
典型流程如下:
text用户登录系统 ↓ 创建后台编码任务 / session ↓ 控制面启动 sandbox ↓ sandbox 需要访问 Git 仓库 ↓ 控制面为 GitHub App 生成短期 installation token ↓ git credential helper 将 token 提供给 git clone/fetch/push ↓ Agent 修改代码、提交分支 ↓ 创建 PR 时优先使用用户 OAuth Token
需要特别注意:
text用户能否创建某个仓库的 session ≠ 用户本人在 GitHub 上是否有该仓库权限
真正决定 Git 操作能否成功的是:
textGitHub App 是否安装到了该仓库
真正决定用户身份 PR 能否成功的是:
text用户 OAuth Token 是否存在 + 该用户在 GitHub 上是否有目标仓库的写权限
由于系统假设所有用户可信,推荐的部署方式是把 Open-Inspect 放在组织内部边界之后。
常见做法:
ALLOWED_GITHUB_ORGS 限制活跃 GitHub 组织成员。如果系统暴露在公网,必须至少做登录限制。否则任何能登录的人,都可能间接访问 GitHub App 覆盖的仓库。
GitHub App 安装时,不建议选择:
textAll repositories
更推荐选择:
textOnly select repositories
也就是说,只把 App 安装到确实允许 Agent 访问的仓库。
例如,公司里有这些仓库:
textorg/web-app org/backend-api org/internal-payments org/security-research
如果当前只希望 Agent 处理产品代码,不希望它接触安全研究仓库,那么 GitHub App 应只安装到:
textorg/web-app org/backend-api
不要安装到:
textorg/security-research
因为一旦 App 安装到了某个仓库,系统内用户就可能通过 Agent session 间接访问它。
Git 操作不是直接把 token 写死在 sandbox 中,而是通过 credential helper 按需获取。
简化后的职责是:
textgit clone / fetch / push ↓ git credential helper 请求凭据 ↓ sandbox 使用 Sandbox Auth Token 调用控制面 ↓ 控制面确认 session ↓ 控制面生成短期 GitHub App installation token ↓ 返回给 git credential helper ↓ git 使用 token 完成操作
这个机制的好处是:
但它不提供:
PR 创建路径和 Git clone/push 不完全一样。
推荐理解成:
text如果用户有 GitHub OAuth Token: 用用户身份创建 PR GitHub 校验用户是否有写权限 否则: 使用 GitHub App bot 身份创建 PR
这带来两个实际结果:
如果组织对代码归属有严格要求,建议强制使用 GitHub 登录,或者在任务入口处限制非 GitHub 登录用户创建 PR。
Sandbox Auth Token 的边界是单个 session。它的职责是让 sandbox 能够调用控制面提供的 session 相关接口,例如请求 Git 凭据、回传状态等。
它不应该被设计成:
如果需要排查问题,应从控制面日志定位 session,而不是把 sandbox token 扩大成全局调试凭据。
WebSocket Token 用于浏览器、协作界面或其他客户端连接实时 session。
它的边界也是单个 session,主要用于:
它不应该被复用为 GitHub 凭据,也不应该被用来访问其他 session。
下面这个脚本模拟 Open-Inspect 的权限模型:
保存为 security-model-demo.mjs:
jsconst config = {
allowedEmailDomains: ["example.com"],
allowedGithubOrgs: ["acme"],
githubAppInstalledRepos: [
"acme/web-app",
"acme/backend-api",
],
};
const users = [
{
name: "alice",
loginType: "github",
email: "alice@example.com",
githubOrgs: ["acme"],
hasGithubOAuthToken: true,
githubWriteRepos: ["acme/web-app"],
},
{
name: "bob",
loginType: "google",
email: "bob@example.com",
githubOrgs: [],
hasGithubOAuthToken: false,
githubWriteRepos: [],
},
{
name: "mallory",
loginType: "github",
email: "mallory@external.test",
githubOrgs: ["other-org"],
hasGithubOAuthToken: true,
githubWriteRepos: ["acme/web-app"],
},
];
function emailDomain(email) {
return email.split("@")[1];
}
function canSignIn(user) {
const domainAllowed = config.allowedEmailDomains.includes(emailDomain(user.email));
const orgAllowed = user.githubOrgs.some((org) => config.allowedGithubOrgs.includes(org));
// 实际部署可以二选一或同时要求,这里演示“邮箱域名或 GitHub 组织满足其一即可”
return domainAllowed || orgAllowed;
}
function isRepoInGithubAppScope(repo) {
return config.githubAppInstalledRepos.includes(repo);
}
function canCreateSession(user, repo) {
if (!canSignIn(user)) {
return {
allowed: false,
reason: "用户不满足登录限制",
};
}
if (!isRepoInGithubAppScope(repo)) {
return {
allowed: false,
reason: "GitHub App 未安装到该仓库",
};
}
return {
allowed: true,
reason: "单租户模型下允许创建 session;未做用户级仓库权限校验",
};
}
function resolvePullRequestActor(user, repo) {
if (user.hasGithubOAuthToken) {
const canWrite = user.githubWriteRepos.includes(repo);
if (canWrite) {
return {
actor: user.name,
method: "user-oauth-token",
result: "PR 将以用户身份创建",
};
}
return {
actor: user.name,
method: "user-oauth-token",
result: "GitHub 会拒绝:用户没有该仓库写权限",
};
}
return {
actor: "github-app[bot]",
method: "github-app-token",
result: "用户没有 GitHub OAuth Token,回退到 GitHub App bot",
};
}
const scenarios = [
["alice", "acme/web-app"],
["alice", "acme/backend-api"],
["bob", "acme/web-app"],
["mallory", "acme/web-app"],
["alice", "acme/security-research"],
];
for (const [userName, repo] of scenarios) {
const user = users.find((u) => u.name === userName);
const session = canCreateSession(user, repo);
console.log(`\n用户: ${userName}`);
console.log(`仓库: ${repo}`);
console.log(`创建 session: ${session.allowed ? "允许" : "拒绝"}`);
console.log(`原因: ${session.reason}`);
if (session.allowed) {
const pr = resolvePullRequestActor(user, repo);
console.log(`PR 身份: ${pr.actor}`);
console.log(`凭据: ${pr.method}`);
console.log(`结果: ${pr.result}`);
}
}运行:
bashnode security-model-demo.mjs
示例输出:
text用户: alice 仓库: acme/web-app 创建 session: 允许 原因: 单租户模型下允许创建 session;未做用户级仓库权限校验 PR 身份: alice 凭据: user-oauth-token 结果: PR 将以用户身份创建 用户: alice 仓库: acme/backend-api 创建 session: 允许 原因: 单租户模型下允许创建 session;未做用户级仓库权限校验 PR 身份: alice 凭据: user-oauth-token 结果: GitHub 会拒绝:用户没有该仓库写权限 用户: bob 仓库: acme/web-app 创建 session: 允许 原因: 单租户模型下允许创建 session;未做用户级仓库权限校验 PR 身份: github-app[bot] 凭据: github-app-token 结果: 用户没有 GitHub OAuth Token,回退到 GitHub App bot 用户: mallory 仓库: acme/web-app 创建 session: 拒绝 原因: 用户不满足登录限制 用户: alice 仓库: acme/security-research 创建 session: 拒绝 原因: GitHub App 未安装到该仓库
这个例子里的第二个场景很重要:
textalice 可以创建 acme/backend-api 的 session 但 alice 没有该仓库写权限 所以使用用户 OAuth Token 创建 PR 会失败
这正是 Open-Inspect 单租户模型的核心边界:Git 操作依赖 GitHub App 的安装范围,而 PR 用户归属依赖用户 OAuth Token。
至少选择一种强入口限制:
ALLOWED_GITHUB_ORGS,只允许指定 GitHub Organization 成员。不要把未加限制的 Web UI 暴露到公网。
安装 GitHub App 时:
建议把 App 的安装范围当成系统的核心权限边界。
如果你希望 PR 都能准确归属到人,推荐:
如果团队使用 Google、SSO 等非 GitHub 登录,需要提前接受这个结果:
text用户可以启动任务,但 PR 可能由 bot 创建。
对于不同敏感级别的仓库,建议分组处理:
text普通业务仓库:可以安装 GitHub App 核心基础设施仓库:单独评估 安全、密钥、合规仓库:默认不安装 客户隔离仓库:不适合共享单个 GitHub App
如果一个仓库不希望所有系统用户都有可能通过 Agent 访问,就不要把 GitHub App 安装到该仓库。
当前模型不适合这样的场景:
text多个客户共用同一个 Open-Inspect 部署 每个客户只能访问自己的 GitHub 仓库 客户之间需要强数据隔离 不同客户使用不同计费、审计和权限策略
要支持多租户,至少需要补齐:
否则,一个 tenant 的用户可能通过共享 GitHub App 访问另一个 tenant 的仓库,这是不可接受的。
在单租户模型下,登录限制只控制谁能进入系统。进入系统之后,仓库访问能力主要取决于 GitHub App 安装范围。
Git clone、fetch、push 使用的是 GitHub App installation token。用户 OAuth Token 主要用于 PR 创建和用户信息。
短期 token 可以降低泄露后的影响时间,但 token 的仓库范围仍然来自 GitHub App installation scope。
如果 App 安装到了很多仓库,短期 token 仍然可能访问这些仓库。
Sandbox 仍然会运行代码、执行命令、访问仓库内容。不要把不可信用户放进同一个部署,也不要让不可信任务共享组织内部 GitHub App。
上线前建议确认:
ALLOWED_GITHUB_ORGS;只要这些条件不成立,就应该先收紧部署边界,而不是把它当成多租户权限系统使用。