概述
沙箱 SDK 内置了一组 Git 操作方法(通过 sandbox.git 调用),覆盖仓库克隆、分支管理、提交、推送拉取等常见工作流,无需自行拼接 git 命令。
除了沙箱内运行时的 Git 操作,平台还提供两种与 Git 协同的安全能力(详见 GitHub 密钥注入与仓库资源):
GithubInjection密钥注入:Token 仅由平台在网关侧注入,不会以明文进入沙箱,沙箱内的git命令对github.com/api.github.com的 HTTPS 请求自动鉴权GitRepositoryResource资源挂载:沙箱启动前由平台拉取仓库快照并挂载到指定路径,适合”开箱即用”的代码消费场景
适用场景包括:
- AI Agent 自动完成代码改造并提交回远程仓库
- 在沙箱内拉取项目代码进行构建、测试或代码审查
- 自动化的代码迁移、批量改造任务
沙箱内 Git 操作
以下章节描述沙箱内通过 SDK 直接驱动的 Git 操作。所有示例使用 @e2b/code-interpreter,Python 等其他 SDK 接口一致,仅命名风格不同(snake_case)。
认证与身份
Git 仓库的读写通常需要凭证。沙箱提供以下几种方式管理认证信息,推荐优先选择 GitHub 密钥注入,因为它能让 token 完全不进入沙箱。
方式 1:GitHub 密钥注入(推荐)
通过创建沙箱时配置 GithubInjection,Token 仅由平台在网关侧持有并注入到出站请求,沙箱内进程无法读取 token 内容。沙箱内直接执行 git push / git pull 即可,不需要传递任何凭证。
完整示例见下文 GitHub 密钥注入与仓库资源。
方式 2:内联凭证
在调用 push 或 pull 时直接传入 username 和 password (Token)。Token 在请求结束后不会持久化到沙箱磁盘。使用 Token 认证时 username 仍然是必填项。
import { Sandbox } from '@e2b/code-interpreter'
async function main() {
const sandbox = await Sandbox.create()
await sandbox.git.clone('https://github.com/owner/repo.git', { path: '/home/user/repo' })
// 后续 push / pull 显式传递凭证
await sandbox.git.push('/home/user/repo', {
username: 'my-bot',
password: process.env.GIT_TOKEN,
})
await sandbox.kill()
}
main()
方式 3:凭证助手(一次认证,全程复用)
通过 dangerouslyAuthenticate() 将凭证写入沙箱内的 git credential helper,之后所有基于 HTTPS 的 Git 操作都会自动使用该凭证。
await sandbox.git.dangerouslyAuthenticate({
username: 'my-bot',
password: process.env.GIT_TOKEN,
// 可选: 针对自托管 Git 服务指定 host 和 protocol
// host: 'git.example.com',
// protocol: 'https',
})
⚠️ 安全提醒:
dangerouslyAuthenticate会将凭证以明文形式存储在沙箱磁盘上。任何有权访问该沙箱的进程或 Agent 都能读取这些凭证。仅在受信任的沙箱中使用,并确保沙箱在使用后及时销毁。如需运行 AI Agent 等不可信代码,请改用 GitHub 密钥注入。
方式 4:在远端 URL 中保留凭证
默认情况下,即便在克隆时把 Token 拼到 URL 中,沙箱也会在克隆完成后从 .git/config 中剥离凭证,避免泄露。如果确实需要保留,可通过 dangerouslyStoreCredentials: true 显式开启。
await sandbox.git.clone('https://my-bot:TOKEN@github.com/owner/repo.git', {
path: '/home/user/repo',
dangerouslyStoreCredentials: true,
})
⚠️ 安全提醒: 启用该选项后,凭证将持久化在仓库的
.git/config中。如果该仓库随后被复制、打包或上传,凭证会一同泄露。
配置 Git 身份
提交前需要配置作者姓名和邮箱。可以设置为全局或仅作用于特定仓库。
import { Sandbox } from '@e2b/code-interpreter'
async function main() {
const sandbox = await Sandbox.create()
// 全局设置
await sandbox.git.configureUser('AI Bot', 'bot@example.com')
// 仅作用于本仓库
await sandbox.git.configureUser('AI Bot', 'bot@example.com', {
scope: 'local',
path: '/home/user/repo',
})
await sandbox.kill()
}
main()
克隆仓库
clone 支持指定本地路径、分支以及浅克隆深度。
import { Sandbox } from '@e2b/code-interpreter'
async function main() {
const sandbox = await Sandbox.create()
// 克隆默认分支
await sandbox.git.clone('https://github.com/owner/repo.git', { path: '/home/user/repo' })
// 克隆指定分支
await sandbox.git.clone('https://github.com/owner/repo.git', {
path: '/home/user/repo',
branch: 'main',
})
// 浅克隆,只拉取最近 1 次提交
await sandbox.git.clone('https://github.com/owner/repo.git', {
path: '/home/user/repo',
depth: 1,
})
await sandbox.kill()
}
main()
仓库状态与分支信息
status 返回结构化的仓库状态,包括当前分支、相对上游的 ahead/behind 数量以及文件变更列表。branches 返回所有本地分支及当前所在分支。
const status = await sandbox.git.status('/home/user/repo')
console.log(status.currentBranch, status.ahead, status.behind, status.fileStatus)
const branches = await sandbox.git.branches('/home/user/repo')
console.log(branches.currentBranch, branches.branches)
分支管理
| 操作 | 方法 |
|---|---|
| 创建并切换 | createBranch(path, name) |
| 切换分支 | checkoutBranch(path, name) |
| 删除分支 | deleteBranch(path, name) |
| 强制删除 | deleteBranch(path, name, { force: true }) |
await sandbox.git.createBranch(repoPath, 'feature/auto-fix')
await sandbox.git.checkoutBranch(repoPath, 'main')
await sandbox.git.deleteBranch(repoPath, 'feature/auto-fix', { force: true })
暂存与提交
// 暂存所有变更
await sandbox.git.add(repoPath)
// 仅暂存指定文件
await sandbox.git.add(repoPath, { files: ['README.md', 'src/index.ts'] })
// 创建提交
await sandbox.git.commit(repoPath, 'feat: add new feature')
// 指定作者并允许空提交
await sandbox.git.commit(repoPath, 'chore: trigger CI', {
authorName: 'AI Bot',
authorEmail: 'bot@example.com',
allowEmpty: true,
})
推送与拉取
// 推送到上游,如果尚未关联远端则建立追踪关系
await sandbox.git.push(repoPath, {
remote: 'origin',
branch: 'feature/auto-fix',
setUpstream: true,
})
// 拉取最新变更
await sandbox.git.pull(repoPath, { remote: 'origin', branch: 'main' })
如果未使用 dangerouslyAuthenticate 或 GitHub 密钥注入,也可以在 push / pull 中通过 username 和 password 直接传入凭证。
管理远端
remoteAdd 用于添加新的远端,支持以下可选参数:
fetch: true— 添加后立即执行一次 fetchoverwrite: true— 如果远端名称已存在,覆盖其 URL
await sandbox.git.remoteAdd(repoPath, 'upstream', 'https://github.com/upstream/repo.git', {
fetch: true,
overwrite: true,
})
Git 配置读写
可以读取或写入 Git 配置项,支持全局或本仓库作用域。
// 全局设置
await sandbox.git.setConfig('pull.rebase', 'false')
const value = await sandbox.git.getConfig('pull.rebase')
// 仅作用于该仓库
await sandbox.git.setConfig('user.signingkey', 'KEYID', {
scope: 'local',
path: repoPath,
})
GitHub 密钥注入与仓库资源
平台为 GitHub 协作提供了两个密切相关的能力,二者可以独立使用,也可以组合使用以获得最佳安全性与开发体验。
| 能力 | 配置位置 | 作用 |
|---|---|---|
GithubInjection |
CreateParams.Injections |
沙箱运行时,自动为出站 github.com / api.github.com 的 HTTPS 请求注入 GitHub Token。Token 不会以明文进入沙箱。 |
GitRepositoryResource |
CreateParams.Resources |
沙箱启动前由平台克隆指定的 GitHub 仓库快照,并挂载到沙箱内的绝对路径。 |
平台首次拉取后会缓存仓库快照,后续创建的沙箱可能复用该快照而不会刷新到最新 HEAD。如果对最新性敏感,可在沙箱内通过
sandbox.git.pull主动刷新。
GitHub 注入与仓库资源相关能力由七牛 Go SDK v7.26.11+ 提供。下面所有 Go 示例均依赖 github.com/qiniu/go-sdk/v7/sandbox 包,stringPtr 辅助函数与现有 docs/developer/example/test-injection-rules.go 保持一致。
工作原理
启动前: 平台克隆 GitHub 仓库快照 ──▶ 挂载到沙箱内 MountPath
运行时: Sandbox ── HTTPS ──▶ 沙箱平台 ── 注入 GitHub Token ──▶ github.com
(无真实 token) (按规则注入)
- 沙箱内进程发起的 HTTPS 请求被网关拦截,仅当目标域名命中
github.com或api.github.com时,网关会注入Authorization: Bearer <token> - Token 由平台保管,沙箱内无法通过任何方式读取
git clone/git push/git pull等命令底层走的就是 HTTPS,因此沙箱内可以直接使用sandbox.git.*API 操作私有仓库,无需任何凭证
仅启用 GitHub 密钥注入
适合”沙箱内的 AI Agent 自主决定要不要拉取或推送代码”的场景。Token 不会以任何形式进入沙箱。
package main
import (
"context"
"log"
"os"
"github.com/qiniu/go-sdk/v7/sandbox"
)
func stringPtr(s string) *string { return &s }
func main() {
ctx := context.Background()
client, err := sandbox.NewClient(&sandbox.Config{
APIKey: os.Getenv("QINIU_API_KEY"),
Endpoint: os.Getenv("QINIU_SANDBOX_API_URL"),
})
if err != nil {
log.Fatal(err)
}
sb, _, err := client.CreateAndWait(ctx, sandbox.CreateParams{
TemplateID: "base",
Injections: &[]sandbox.SandboxInjectionSpec{
{
// GitHub 注入:仅平台持有 token,沙箱内的 git 请求自动鉴权
Github: &sandbox.GithubInjection{
Token: stringPtr(os.Getenv("GITHUB_TOKEN")),
},
},
},
})
if err != nil {
log.Fatal(err)
}
defer sb.Kill(ctx)
// 沙箱内无需任何凭证,即可克隆私有仓库
result, err := sb.Commands().Run(ctx,
"git clone https://github.com/owner/private-repo.git /workspace/repo && "+
"cd /workspace/repo && echo 'change' >> README.md && "+
"git -c user.email=bot@example.com -c user.name=AI-Bot commit -am 'docs: update' && "+
"git push origin HEAD")
if err != nil {
log.Fatal(err)
}
log.Println(result.Stdout)
}
要点:
- 沙箱内没有 GitHub Token,即便沙箱被 Agent 滥用、被 dump 文件系统,Token 也不会泄露
- 平台只在命中
github.com/api.github.com时注入,其他出站请求保持透传 - 同一沙箱中所有 GitHub 仓库共用同一个 Token
启用 GitHub 仓库资源(预克隆)
适合”沙箱启动即拥有仓库代码,直接进行构建 / 测试 / 代码审查”的场景。平台在沙箱启动前完成克隆,沙箱启动后即可直接访问挂载路径。
sb, _, err := client.CreateAndWait(ctx, sandbox.CreateParams{
TemplateID: "base",
Resources: &[]sandbox.SandboxResourceSpec{
{
GitRepository: &sandbox.GitRepositoryResource{
Type: sandbox.GitRepositoryTypeGithub,
URL: "https://github.com/owner/private-repo.git",
MountPath: "/workspace/repo",
AuthorizationToken: stringPtr(os.Getenv("GITHUB_TOKEN")),
},
},
},
})
if err != nil {
log.Fatal(err)
}
defer sb.Kill(ctx)
// /workspace/repo 已经包含仓库内容
sb.Commands().Run(ctx, "ls -la /workspace/repo")
要点:
URL支持 HTTPS (https://github.com/owner/repo.git) 与 SSH (git@github.com:owner/repo.git) 两种写法MountPath必须是绝对路径AuthorizationToken当前必填(即使是公共仓库)。平台会用它克隆仓库,并自动派生一个运行时的 GitHub 注入,让沙箱内对github.com/api.github.com的 HTTPS 请求继续可用- 同一沙箱内多个仓库资源当前必须共用同一个 token
- 平台首次拉取的快照可能被后续沙箱复用,如需最新提交,沙箱启动后执行
sandbox.git.pull
组合使用:预克隆 + 运行时推送
最常见的 AI Agent 工作流:启动时拉取仓库快照,沙箱内 Agent 改完代码后直接推送回 GitHub。整个过程中 Token 始终由平台持有。
重要:当
CreateParams.Resources已经包含GitRepository资源时,不要再显式传入GithubInjection——服务端会拒绝这种组合并返回400 github_repository resources do not support explicit github injections。平台会根据资源里的AuthorizationToken自动派生运行时的 GitHub 注入,沙箱内对github.com/api.github.com的 HTTPS 请求会自动鉴权。
sb, _, err := client.CreateAndWait(ctx, sandbox.CreateParams{
TemplateID: "base",
Resources: &[]sandbox.SandboxResourceSpec{
{
GitRepository: &sandbox.GitRepositoryResource{
Type: sandbox.GitRepositoryTypeGithub,
URL: "https://github.com/owner/private-repo.git",
MountPath: "/workspace/repo",
// 同一 token 既用于预克隆,也由平台自动派生为运行时 GitHub 注入
AuthorizationToken: stringPtr(os.Getenv("GITHUB_TOKEN")),
},
},
},
})
if err != nil {
log.Fatal(err)
}
defer sb.Kill(ctx)
// Agent 在 /workspace/repo 改完代码后,直接推送
// 平台自动派生的 GitHub 注入会为出站请求注入 token,沙箱内无需任何凭证
sb.Commands().Run(ctx, `
cd /workspace/repo &&
git checkout -b feature/auto-fix &&
git -c user.email=bot@example.com -c user.name=AI-Bot commit -am 'feat: auto fix' &&
git push -u origin feature/auto-fix
`)
如果工作流不需要预克隆、只希望沙箱内自行 git clone 拉取代码,改用 GitHub 密钥注入与仓库资源 中“仅启用 GitHub 密钥注入”的写法,显式配置 GithubInjection。
更多资源挂载示例可参考 Go SDK 仓库的 examples/sandbox_resources/main.go。
与已保存注入规则配合
GitHub 注入同样可以保存为可复用的命名规则,使用 client.CreateInjectionRule 创建后,通过 ID 引用:
rule, err := client.CreateInjectionRule(ctx, sandbox.CreateInjectionRuleParams{
Name: "github-bot-token",
Injection: sandbox.InjectionSpec{
Github: &sandbox.GithubInjection{
Token: stringPtr(os.Getenv("GITHUB_TOKEN")),
},
},
})
if err != nil {
log.Fatal(err)
}
sb, _, err := client.CreateAndWait(ctx, sandbox.CreateParams{
TemplateID: "base",
Injections: &[]sandbox.SandboxInjectionSpec{
{ByID: &rule.RuleID},
},
})
完整的注入规则管理(创建、列出、更新、删除)请参考 密钥注入。
安全建议
- 优先使用 GitHub 密钥注入:AI Agent 场景下避免任何形式的 token 进入沙箱
- 使用具备最小权限的 Token (例如 GitHub Fine-grained PAT),仅授予所需仓库的读写权限
- 谨慎使用
dangerouslyAuthenticate和dangerouslyStoreCredentials,只在你完全控制沙箱生命周期且不会将其镜像或快照外泄时启用 - 沙箱使用完毕后立即调用
sandbox.kill()销毁实例,避免凭证或克隆出的代码长期驻留