yangsi7/rotate-env
GitHub: yangsi7/rotate-env
一个轻量 CLI 工具,用于在递归查找的所有 .env 和 .mcp.json 文件中安全、批量地轮换泄露的 API 密钥,全程不暴露密钥内容。
Stars: 1 | Forks: 0
rotate
一个小型的 CLI 工具,可以批量轮换从你运行它的位置递归查找到的 `.env` 和 `.mcp.json` 文件中出现的所有 API-key 环境变量。手动或由 AI agent 运行都很安全:它从文件或环境变量中读取新 key,并且绝不显示它。
你的 AI 编码 agent 刚刚把一个有效的 API key 粘贴到了你一半项目的十二个 `.env` 文件和三个 `.mcp.json` 配置中。现在你必须轮换它:找到每一个文件,用它们各自恰好使用的引号风格替换其中的值,并且在这个过程中不能把新 key 泄漏到你的 shell 历史记录或已提交的模板中。`rotate` 恰好能在一行命令中完成这些操作,而且全程不会打印出密钥。

## 快速开始
```
# 1) Dry-run:查看所有持有该 key 的文件(不写入任何内容):
rotate SOME_API_KEY_ENV_VAR
# 2) 无需值守地在所有地方 rotate 它:
ROTATE_NEW_VALUE='sk_your_new_api_key_value' rotate --apply --yes SOME_API_KEY_ENV_VAR
```
默认情况下,`rotate` 会递归搜索当前目录。你可以使用 `--root` 将其指向其他位置:
```
rotate --apply --root ~/code ELEVENLABS_API_KEY
```
## 安装
**Homebrew** (macOS 和 Linux):
```
brew install yangsi7/tap/rotate
```
**Nix** (flake):
```
nix run github:yangsi7/rotate-env -- --help # run without installing
nix profile install github:yangsi7/rotate-env # install
```
**安装脚本** (任何带有 `curl` 的 Unix 系统):
```
curl -fsSL https://raw.githubusercontent.com/yangsi7/rotate-env/v0.1.3/install.sh | bash
```
这会将 `rotate` 安装到 `~/.local/bin` 中(无需 sudo)。想在运行前先检查一下?先阅读脚本:
```
curl -fsSLO https://raw.githubusercontent.com/yangsi7/rotate-env/v0.1.3/install.sh
less install.sh
bash install.sh
```
手动安装 (git clone)
```
git clone https://github.com/yangsi7/rotate-env.git
cd rotate-env
make install # copies rotate into ~/.local/bin (override with PREFIX=...)
# 或者直接将其复制到您 PATH 上的任意位置:
install -m 755 rotate ~/.local/bin/rotate
```
可以使用 `make uninstall` 或 `bash install.sh --uninstall` 卸载。
**前置要求:** `bash`、`awk` 以及 [`fd`](https://github.com/sharkdp/fd)(Debian/Ubuntu 中为 `fd-find`,二进制文件为 `fdfind`)。仅在处理 `.mcp.json` 文件时才需要 [`jq`](https://jqlang.github.io/jq/)。
## 用例
- **AI agent 泄漏了 key。** Agent 将密钥复制到了多个项目文件中。一次性在所有地方轮换它。
- **密钥被入侵或过期。** 你重新生成了提供商的密钥,并且需要每个项目都应用新的密钥。
- **离职处理 / 凭据清理。** 有人离开了团队,或者某个密钥超出了策略使用期限。按计划进行轮换。
- **Monorepo 和多项目设置。** 一个被数十个 `.env` 文件和 MCP 配置引用的密钥。
## 用法
```
rotate [options]
```
默认模式是掩码的预演(dry-run)预览。传入 `--apply` 来实际写入(会有确认提示,或者加上 `--yes` 跳过提示)。
| 选项 | 描述 |
| --- | --- |
| `-n, --dry-run` | 预览更改而不写入(默认) |
| `--apply` | 写入更改(会提示确认) |
| `-y, --yes` | 跳过确认提示(非交互式 `--apply` 必需) |
| `-b, --backup` | 在写入之前将每个文件复制到 `.bak.`(chmod 600) |
| `--value-file ` | 从文件中读取新值 |
| `--old-value-file ` | 仅替换当前值匹配的项(精准替换) |
| `--root ` | 递归搜索根目录(可重复指定;将替换默认设置) |
| `--shallow-root ` | 深度为 1 的搜索根目录(可重复指定) |
| `--max-depth ` | 限制递归深度 |
| `--list` | 列出已发现的目标文件并退出 |
| `-v, --verbose` | 调试日志 |
| `-V, --version` | 打印版本 |
| `-h, --help` | 包含更多示例的完整帮助 |
### 提供新值(绝不在命令行上提供)
密钥绝不会从 flag 中读取,因为命令行参数对每个进程来说通过 `ps` 都是可见的。请改用以下方式之一来提供:
```
rotate --apply --value-file ~/new.key ELEVENLABS_API_KEY # from a file
ROTATE_NEW_VALUE='sk_...' rotate --apply ELEVENLABS_API_KEY # from an env var
pbpaste | rotate --apply ELEVENLABS_API_KEY # piped on stdin
rotate --apply ELEVENLABS_API_KEY # silent interactive prompt
```
### 精准轮换
仅替换特定的泄漏值,保留同一变量的任何其他值不受影响:
```
ROTATE_OLD_VALUE='sk_leaked' ROTATE_NEW_VALUE='sk_fresh' rotate --apply --yes ANTHROPIC_API_KEY
```
## 支持的文件格式
**`.env`, `.env.*`, `.envrc`**(`=` 分隔符),包含所有常见的形式:
```
KEY=value
KEY="value"
KEY='value'
export KEY=value
KEY= # empty / declared-but-unset is filled in
KEY=value # inline comment (comment preserved)
```
保留原始的引号风格、`export` 前缀和行尾注释,并且按字面值插入(因此 key 中的 `/`、`&` 和反斜杠都是安全的)。匹配是精确的,所以轮换 `API_KEY` 绝不会影响 `GOOGLE_API_KEY` 或 `NEXT_PUBLIC_API_KEY`。
**`.mcp.json`**(JSON `:` 分隔符),覆盖密钥隐藏的所有三个位置:
```
{ "mcpServers": {
"a": { "env": { "ELEVENLABS_API_KEY": "value" } }, // env block
"b": { "headers": { "x-api-key": "value" } }, // HTTP headers
"c": { "args": ["env", "ELEVENLABS_API_KEY=value", "cmd"] } // args[] wrapper
}}
```
## 工作原理
1. **发现** 每次运行都使用 `fd` 查找目标文件(这样新项目会被自动识别)。名为 `node_modules`、`worktrees`、`.git`、`.next`、`dist`、`build` 的目录会被剔除;`*.yaml`/`*.yml` 文件会被跳过;占位符文件(`*example*`、`*sample*`、`*template*`、`.env.dist`、`.env.tmpl`、`.env.defaults`,不区分大小写匹配)以及 `.bak`/`.orig` 备份文件会被排除,这样真实的密钥就永远不会被写入到已提交的模板中。
2. **编辑** 使用 `awk` 处理 `.env` 文件(字面值、感知引号、精确匹配 key),使用 `jq walk` 处理 `.mcp.json`(覆盖 env/headers/args)。
3. **写入** 原子化操作:在同目录下生成临时文件,然后执行 `mv`,保留原始权限。
## 安全保证
- **密钥绝不会出现在 argv 上**(通过环境变量传递给 `awk`/`jq`),并且**绝不会被打印**(所有日志都会将其掩码处理为 `ab****yz`)。
- **默认空运行(Dry-run)。** 除非传入 `--apply`,否则不会写入任何内容,而且 dry-run 绝不会写入临时文件,因此新值在预览期间永远不会触及磁盘。
- **幂等性。** 使用相同的值重新运行不会改变任何内容。已经等于新值的条目会被跳过,因此不包含该 key 的文件绝不会被重写。
- **原子化且可逆。** 针对每个文件进行临时文件+`mv` 操作并保留权限,外加可选的 `--backup`(chmod 600)。注意这里没有跨文件事务:如果在写入过程中失败,之前的文件已经被轮换了,所以如果你想要回滚功能,请使用 `--backup`。
## 适合从 AI agent(或 CI)中运行
`rotate` 首先是一个普通的命令行工具:将其指向某个目录,它就会轮换每个匹配文件中的 key。由于它的设计初衷是绝不暴露密钥,因此把它交给 AI 编码 agent 或 CI 任务也是安全的,不会把密钥泄漏到对话记录、模型上下文或你的日志中(这正是这款工具存在的意义,用来清理这类烂摊子)。
- **它绝不打印密钥。** 所有输出都被掩码为 `ab****yz`,因此该值绝不会落入 agent 对话记录、CI 日志或终端回滚缓冲区中。
- **它绝不会将密钥放在 argv 上。** 该值通过环境变量传递给 `awk`/`jq`,因此无法通过 `ps`、shell 历史记录或 agent 记录的命令行捕获。直接拒绝将密钥作为 `--value` flag 传递。
- **它完全以非交互方式运行。** 通过 `$ROTATE_NEW_VALUE`、`--value-file` 或 stdin 提供值,并在没有 TTY 的情况下运行 `--apply --yes`。默认开启 dry-run,因此 agent 可以在最终确定前预览(已掩码的)计划。
- **它不会泄漏到已提交的文件中。** 绝不会写入占位符和模板文件(`*example*`、`.env.dist` 等,不区分大小写匹配),因此 agent 不会意外地将真实 key 轮换到被 git 追踪的模板中。
- **它是确定性的且可解析的。** 分级日志发送到 stderr,数据发送到 stdout,并且退出码是稳定的(`0` 正常,`2` 用法错误,`127` 缺少依赖),因此 agent 可以根据结果执行不同分支。运行具备幂等性,所以重试总是安全的。
Agent 可以在一次无人值守的调用中,跨每个项目轮换受入侵的 key:
```
ROTATE_NEW_VALUE="$NEW_KEY" rotate --apply --yes ELEVENLABS_API_KEY
```
**Claude Code 用户:** 本仓库内置了一个 `/rotate-key` 技能(`.claude/skills/rotate-key/),它封装了 CLI,提供了一个安全且绝不暴露密钥的工作流(预览,然后使用带外提供的值进行应用)。当你在本仓库的克隆副本中工作时,它会自动可用。
## 版本控制
本项目遵循 [语义化版本](https://semver.org/)。公开形式包括 flags、输出格式和退出码。请参阅 [CHANGELOG.md](CHANGELOG.md)。
## 贡献
欢迎提交 Issue 和 PR。在提交之前,请运行 `make lint`(ShellCheck)和 `make test`(bats)。CI 会在 Linux 和 macOS 上运行这两者。
## 许可证
[MIT](LICENSE) © yangsi7标签:LNA, SOC Prime, StruQ, 应用安全, 开发工具, 网络调试, 自动化