内容
# ironrun
**通过 AI 代理运行命令,无需将您的密钥暴露在代理的上下文中。**
[](https://github.com/generalized-labs/ironrun/actions/workflows/ci.yml)
[](https://goreportcard.com/report/github.com/generalized-labs/ironrun)
[](https://github.com/generalized-labs/ironrun/blob/main/LICENSE)
---
ironrun 是一个小型命令运行器。您在一個文件中列出 AI 代理允许运行的命令(您的测试、开发服务器、部署脚本),代理通过 ironrun 运行它们,而不是直接输入 shell 命令。ironrun 将您的实际密钥注入命令,并从输出中剥去任何密钥值,以便代理在看到它们之前。
您的测试仍然可以获得实时的 `DATABASE_URL`。代理返回 `exit_code: 0` 和 `tests passed` — 而且永远不会看到连接字符串。
---
## 为什么您可能需要这个
编码代理(Claude Code、Codex、Cursor 等) 在您的机器上运行 shell 命令,而这些命令可以查看您的环境。大多数情况下,这是没问题的。问题在于少数打印环境变量的命令:
```bash
printenv # 打印所有环境变量,包括密钥
cat .env # 打印您的本地密钥文件
echo $STRIPE_SECRET_KEY # 一个正常的调试步骤
docker inspect web # 配置块通常包含凭证
```
当代理运行其中一个命令时(通常是在调试您要求它修复的内容时),密钥值会出现在聊天记录中。从那里,它可能出现在模型的上下文中,可能出现在提供商的日志中,也可能出现在屏幕录制或共享会话中。您无法取消发送它,因此安全的做法是轮换凭证。
这不是假设的。从 Claude Code 问题跟踪器:
> *"三起确认事件发生在同一操作员工作站,时间约为 6 天。每次事件都迫使进行凭证轮换。没有一个是奇异的 — 它们是在模型被要求执行的正常诊断工作中出现的问题。"*
> — [anthropics/claude-code#65122](https://github.com/anthropics/claude-code/issues/65122)
代理不会编辑自己的输出,而您已经使用的密钥管理器(`op run`、`doppler run`、`infisical run`)会注入密钥但不编辑。ironrun 添加了缺失的部分:它位于代理和命令之间,以便密钥流入但从不流出。
### 您可能尚未看到的威胁
当 Claude Code 运行使用密钥的命令时 — 使用带有 Authorization 头的 `curl`、部署脚本读取 DATABASE_URL — 该命令调用被逐字记录到:
```
~/.claude/projects/<hash>/YYYYMMDD_HHMMSS_*.jsonl
```
这些 JSONL 文件是 Claude Code 的对话历史记录。它们包含每个工具调用、每个 shell 命令、每个输出片段 — 包括那些输出中出现的密钥值。它们在会话之间和项目重启之间持续存在。
一位开发人员偶然发现了这一点:
> *"它将 grep 日志并尝试从其他项目/过去会话中找到一个有效的密钥"*
日志可由任何具有访问您主目录权限的进程读取。如果您曾经通过 Claude Code 运行过涉及密钥的命令,请假设该值存在于这些日志中。
ironrun 在输出到达代理之前会编辑密钥值 - 因此值永远不会进入对话,也不会出现在 JSONL 日志中。
---
## 工作原理
```
┌─────────────────────────────────────────────────────────────┐
│ AI 代理(Claude Code、Codex、Cursor、…) │
│ │
│ "运行测试套件" │
│ │ │
│ ▼ │
│ run_sealed("test") ◄── 一个 MCP 工具调用 │
│ │ │
└────────┼────────────────────────────────────────────────────┘
│ ironrun 接管这里
▼
┌─────────────────────────────────────────────────────────────┐
│ ironrun │
│ │
│ 1. 在 ironrun.yml 中查找 "test" — 是否允许? │
│ 2. 从您的管理器(1Password,…)解析其密钥 │
│ 3. 使用这些密钥运行命令 │
│ 4. 通过编辑器流式传输输出: │
│ 任何出现的密钥值 → [已编辑] │
│ 5. 返回退出代码 + 清理输出 │
│ │
│ 代理看到: exit_code=0, stdout="ok tests passed" │
│ 代理看不到: DATABASE_URL=postgres://… │
└─────────────────────────────────────────────────────────────┘
```
代理只能运行您列出的命令 — 它不能要求 ironrun 运行任意的 shell 命令,也没有返回密钥值的工具。
> ironrun 为每个命令调用增加了大约 5-10ms:~2ms 用于提供者查找(env/envfile)高达 ~100ms 用于 1Password CLI 调用。编辑层为典型的输出大小添加了不到 1ms。
---
## 安装
```bash
# curl(Linux/macOS — 无需 Go 工具链,验证校验和)
curl -fsSL https://raw.githubusercontent.com/generalized-labs/ironrun/main/install.sh | bash
# Go(任何平台)
go install github.com/generalized-labs/ironrun/cmd/ironrun@latest
```
检查它是否在您的路径中:
```bash
ironrun version
# ironrun 0.2.0 (源代码构建打印 "ironrun dev")
```
---
## 快速入门
在任何项目的根目录下:
```bash
ironrun init
```
它会检查您的项目并写入三个文件:
- **`ironrun.yml`** — 一个启动策略。它检测您的堆栈(npm/pnpm/yarn/bun、Go、Rust、Python)和您的 `.env`,并预填充 `test`、`dev` 和 `build` 等命令及其找到的环境变量。
- **`.mcp.json`** — 将 Claude Code 连接到 ironrun(项目范围的 MCP 服务器,与任何现有文件合并)。
- **`CLAUDE.md`、`AGENTS.md`、`.cursorrules`** — 告诉代理(Claude Code、Codex、Cursor)通过 `run_sealed` 运行命令,而不是将它们键入 shell。
对于 Node 项目,生成的 `ironrun.yml` 如下所示:
```yaml
version: "1"
provider: env # 密钥来自当前环境,按名称
commands:
- id: dev
argv: [npm, run, dev]
ttl: 0 # 无超时 — 长期运行的开发服务器
env:
DATABASE_URL: env:DATABASE_URL
STRIPE_SECRET_KEY: env:STRIPE_SECRET_KEY
- id: test
argv: [npm, test]
ttl: 120s
env:
DATABASE_URL: env:DATABASE_URL
STRIPE_SECRET_KEY: env:STRIPE_SECRET_KEY
- id: build
argv: [npm, run, build]
ttl: 120s
```
加载您的密钥,然后检查策略:
```bash
set -a; source .env; set +a # 将 .env 值放入环境中
ironrun validate
# 策略有效:定义了 3 个命令,provider=env
# • dev: [npm run dev]
# • test: [npm test]
# • build: [npm run build]
```
自己运行一个命令以查看编辑:
```bash
ironrun run test
# npm test 运行并设置 DATABASE_URL 和 STRIPE_SECRET_KEY。
# 如果测试记录连接字符串,您将看到 [已编辑] 代替。
```
现在启动您的代理(`claude`、`cursor` 等)。它将 `run_sealed` 视为一个工具,并使用它来运行 `test`、`dev` 和 `build` — 而且永远不会持有密钥值。
### 与 Codex 结合使用
将 ironrun 添加到 Codex 的 MCP 服务器列表中:
```bash
codex mcp add ironrun -- ironrun mcp
```
或手动添加到 `~/.codex/config.toml`:
```toml
[mcp_servers.ironrun]
enabled = true
command = "ironrun"
args = ["mcp"]
```
然后添加到您的项目的 CODEX.md(类似于 CLAUDE.md):
```
对需要凭证的所有命令使用 run_sealed。
不要运行 printenv、cat .env 或 echo $VAR。
```
### 与 Cursor 结合使用
将 ironrun 添加到 `~/.cursor/mcp.json`(合并,不要替换现有条目):
```json
{
"mcpServers": {
"ironrun": {
"command": "ironrun",
"args": ["mcp"]
}
}
}
```
Cursor 为所有项目加载此全局 MCP 配置。您还需要在项目中添加一个 `CURSOR.md` 或 `.cursorrules` 文件,告诉 Cursor 使用 `run_sealed` 代替直接 shell 命令。
---
## 手动编写策略
整个策略是一个 YAML 文件。以下是一个使用 1Password 存储令牌的服务的现实示例:
```yaml
version: "1"
provider: 1password
commands:
- id: test
argv: [npm, test]
ttl: 120s
env:
DATABASE_URL: "op://Engineering/staging-db/url"
- id: deploy
argv: [./scripts/deploy.sh, production]
ttl: 10m
no_network: false # 部署需要网络
env:
FLY_API_TOKEN: "op://Engineering/fly/token"
```
每个字段:
```yaml
version: "1" # 必需 — 目前始终为 "1"
provider: 1password # 密钥的来源(参见下表)
commands:
- id: deploy # 必需 — 代理调用的名称
argv: [./scripts/deploy.sh, production]
# 必需 — 确切的命令 + 参数。
# 无 shell,无管道,无通配符,无 $VAR 扩展。
ttl: 10m # 可选 — 此长时间后终止命令
max_bytes: 10485760 # 可选 — 限制 stdout+stderr(此处为 10 MB)
no_network: true # 可选 — 阻止出站网络
# (Linux:命名空间,macOS:sandbox-exec)
seccomp: true # 可选 — Linux seccomp 过滤器(默认开启;
# 设置为 false 以允许例如需要 ptrace 的调试器)
workdir: ./services/api # 可选 — 从此目录运行
env: # 可选 — 要注入的密钥,按引用
DATABASE_URL: "op://Engineering/prod-db/url"
```
### 额外强化(默认开启)
- **系统调用过滤器** — 在 Linux 上,命令在 seccomp 拒绝列表下运行,该列表阻止 `ptrace`/`process_vm_readv` 和类似的内存嗅探系统调用。如果内核不支持,则会发出警告并打开。
- **编码密钥编辑** — base64、十六进制和 URL 编码形式的密钥以及字面值都会被编辑,同时会对高熵令牌进行警告扫描。
- **审计日志** — 每次运行都会将命令 + argv + 密钥名称(而非值)的哈希链记录追加到 `$XDG_STATE_HOME/ironrun/audit.log`。使用 `ironrun audit verify` 检查它;使用 `IRONRUN_AUDIT_LOG=<path|off>` 或顶级 `audit_log:` 字段重定向或禁用。
```bash
ironrun lint # 策略的安全审查(shell argv,缺少 ttl,密钥 + 开放出口,…)
ironrun audit verify # 确认审计日志未被篡改
```
`argv` 是一个字面列表,而不是 shell 行。 `["npm", "test"]` 直接运行 `npm test` — 中间没有 shell,因此注入的值永远不会被重新扩展或管道输送到意外的位置。
---
## 密钥来源
设置 `provider:` 一次,然后在 `env:` 中引用每个密钥。
| 提供者 | 引用格式 | 示例 |
|---|---|---|
| `envfile` | `envfile:<path>` (在 `provider:` 上设置) | `provider: "envfile:~/.secrets/myapp.env"` |
| `1password` | `op://vault/item/field` | `op://Engineering/stripe/secret_key` |
| `vault` | `vault://<path>#<field>` (KV v2;读取 `VAULT_ADDR`/`VAULT_TOKEN`) | `vault://secret/myapp#DATABASE_URL` |
| `doppler` | `doppler://project/config/NAME` 或仅 `NAME` | `doppler://myapp/prod/STRIPE_KEY` |
| `infisical` | `infisical://projectId/env/NAME` 或仅 `NAME` | `infisical://abc123/prod/DB_URL` |
| `env` | `env:NAME` 或仅 `NAME` | `env:DATABASE_URL` |
| `passthrough` | 字面值 | `postgres://localhost/dev` |
### 推荐用于本地开发:`envfile` 提供者
`env` 提供者从您的 shell 环境中读取 — 而正是代理可以与 `printenv` 转储的内容。`envfile` 提供者避免了这一点:它将密钥保存在 **存储库外部** 的文件中,并在命令运行时仅读取它们,因此它们永远不会出现在代理的环境中。
```bash
# 1. 一个安全的目录,外部于任何 git 仓库,仅限您访问
mkdir -p ~/.secrets && chmod 700 ~/.secrets
# 2. 您的真实密钥,一行一个 KEY=value
cat > ~/.secrets/myapp.env <<'EOF'
DATABASE_URL=postgres://app:s3cr3t@db.internal:5432/myapp
STRIPE_SECRET_KEY=sk_live_51Nabc123def456
RESEND_API_KEY=re_8Hk2Qa9xY
EOF
chmod 600 ~/.secrets/myapp.env
# 3. 将策略指向它
# provider: "envfile:~/.secrets/myapp.env"
```
现在 `ironrun run test` 仍然拥有所有密钥,但代理的 shell 中没有它们 — `printenv` 和 `cat .env` 返回空值。
### envfile — 将密钥存储在文件中,而不是 shell 环境中
用于本地开发与 AI 代理的最安全设置。密钥从文件读取,该文件永远不会被代理的 shell 进程继承。
```bash
# 一次性设置
mkdir -p ~/.secrets && chmod 700 ~/.secrets
cat > ~/.secrets/myproject.env << 'EOF'
DATABASE_URL=postgres://user:***@localhost/mydb
API_KEY=sk_live_...
EOF
chmod 600 ~/.secrets/myproject.env
```
```yaml
# ironrun.yml
version: "1"
provider: "envfile:~/.secrets/myproject.env"
commands:
- id: test
argv: [go, test, ./...]
ttl: 10m
env:
DATABASE_URL: DATABASE_URL # 从文件中读取
API_KEY: API_KEY
```
代理运行 `printenv DATABASE_URL` 时看到的是空值 — 该变量不在 shell 环境中。ironrun 在执行时从文件中读取它。
---
## 代理获得的 MCP 工具
当 ironrun 作为 MCP 服务器运行时(`ironrun mcp`),它会暴露三个工具 — 没有一个返回密钥值:
**`list_commands`** — 我可以运行什么? 返回命令名称和 argv,没有其他内容。
```
test: [npm test]
build: [npm run build]
deploy: [./scripts/deploy.sh production]
```
**`run_sealed`** — 运行其中一个。采用单个参数 `command_id: "<id>"` (来自 `ironrun.yml` 的 id;JSON 参数名称为 `command_id`,而不是 `id`)。返回退出代码、持续时间和编辑后的 stdout/stderr。
```
exit_code: 0
duration_ms: 4231
--- stdout ---
ok myapp 4.2s
--- stderr ---
(empty)
```
**`validate_policy`** — 策略是否正确? 返回提供者和命令数。
---
## 在 CI / GitHub Actions 中
ironrun 为受信任的运行注入密钥,并拒绝不受信任的运行,因此拉取请求无法通过 fork 欺骗工作流获取生产凭证。不需要配置。
| 事件 | 是否注入密钥? |
|---|---|
| 推送到您仓库中的分支 | ✓ 是 |
| 同一仓库的拉取请求 | ✓ 是 |
| 来自其他仓库的拉取请求 | ✗ 否 — 阻止(`ErrCIUntrusted`) |
| `pull_request_target` | ✗ 否 — 除非您设置 `IRONRUN_ALLOW_PRT=1` |
将其用作操作步骤:
```yaml
- name: 部署(密封)
uses: generalized-labs/ironrun@v0
with:
command_id: deploy # 来自您的 ironrun.yml 的命令
policy: ironrun.yml # 可选,默认为此值
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
```
或在任何作业中简单地安装二进制文件并运行命令:
```yaml
- run: go install github.com/generalized-labs/ironrun/cmd/ironrun@latest
- run: ironrun run test # 使用存储库中的 ironrun.yml
```
---
## 它保护什么,不保护什么
**它将密钥排除在:
- `printenv`、`env`、`echo $VAR` 和 shell 扩展技巧 代理可能运行的命令
- 代理的聊天记录和模型的上下文窗口
- 会话的屏幕共享、录制或截图
- 分叉 PR 作者可以读取的 CI 日志
**它不(在 v0 中)保护免受:
- 故意将密钥写入文件并在以后读取的命令
- 通过运行带有 `no_network: false` (默认)的命令进行网络外泄
- 命令本身创建的文件中的密钥
- 已经可以访问运行 ironrun 的机器的任何人
ironrun 保护命令和代理之间的路径。它不是用于敌对代码的沙箱。有关完整的威胁模型和如何报告问题,请参见 [SECURITY.md](SECURITY.md)。
---
## 与 `op run` / `doppler run` / `infisical run` 的比较
这些工具解析您的密钥并将它们作为环境变量注入 — 这很棒,ironrun 也会这样做。区别在于命令开始打印后发生的一切:
| | ironrun | op / doppler / infisical run |
|---|---|---|
| 将密钥注入为 env 变量 | ✓ | ✓ |
| 从输出中编辑密钥值 | ✓ | ✗ |
| 为代理公开 `run_sealed` 工具 | ✓ | ✗ |
| 允许列表 — 代理无法运行任意命令 | ✓ | ✗ |
| 阻止 fork-PR 运行获取密钥 | ✓ | ✗ |
| 跨 1Password、Doppler、Infisical、env 文件工作 | ✓ | 每个都绑定到自己的后端 |
Doppler 的确提供了一个 MCP 服务器,但它 [允许代理直接读取密钥值](https://docs.doppler.com/docs/mcp) — 目标相反。ironrun 的 MCP 服务器允许代理 *运行命令*,而永远不会 *读取密钥*。
---
## 故障排除
**首先运行 `ironrun doctor`。** 它运行只读检查:策略解析,提供者 CLI 已安装并已验证,编辑器剥去一个已知的密钥,每个命令的二进制文件在 PATH 上解析。它以非零退出并指出有什么问题。
```bash
ironrun doctor
```
```
✓ ironrun.yml 有效(v1,3 个命令)
✓ 提供者 1password (就绪)
✓ 自我测试通过编辑
✓ 命令 "test": "go" 在 PATH 上解析
✗ 命令 "deploy": "./deploy.sh" 未在 PATH 上找到
```
**`op: command not found`**
1Password CLI 未安装或不在您的 PATH 中。从 [1password.com/downloads/command-line](https://1password.com/downloads/command-line/) 安装它并运行 `op signin`。
**`密钥解析失败`**
运行 `ironrun doctor` — 它验证策略并检查提供者是否已安装并已验证(`op`、`vault`、`doppler`、`infisical`)。(`ironrun validate` 仅解析策略文件;它不检查提供者身份验证。)
**`命令超时`**
命令运行时间超过其 `ttl`。在 `ironrun.yml` 中增加它:
```yaml
- id: slow-test
argv: [go, test, ./...]
ttl: 30m # 原为 10m
```
**`不允许 shell 命令`**
您不能将 `sh`、`bash` 或其他 shell 作为 `argv[0]` 使用。如果您的命令需要 shell,请将脚本文件包装在其中并调用脚本:
```yaml
- id: deploy
argv: [./scripts/deploy.sh] # 不是:[bash, -c, ./scripts/deploy.sh]
```
**`密钥解析为空值 — 无法编辑`**
您的提供者为密钥返回了一个空字符串。这通常意味着密钥引用错误(1Password 路径中的拼写错误,env 变量名称不匹配)。在 `ironrun.yml` 中修复引用。
**代理忽略 `run_sealed` 并直接运行 shell 命令**
检查项目根目录中是否存在 `CLAUDE.md`(或 `CODEX.md` / `.cursorrules`)并包含明确指示使用 `run_sealed` 的指令。代理严格遵循这些文件中的明确指令。
---
## 贡献
欢迎 PR。标准很简单:
```bash
go build ./...
go test -race ./... # 保持绿色
gofmt -w . # 保持清洁
```
安全威胁模型和披露流程: [SECURITY.md](SECURITY.md).
## 许可证
MIT — 请参阅 [LICENSE](LICENSE). 由 [Generalized Labs](https://github.com/generalized-labs) 构建。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器