一个越来越常见的场景:你在手机上用 Codex Remote 或 SSH 驱动另一台机器上的 coding agent,干着干着它说「请提供 API Key」。把 sk- 开头的密钥直接粘进会话,是很多人下意识的动作——但密钥从此进入了会话记录:它会被写进 transcript、跟着会话同步、进日志、可能被截屏、可能随上下文发给模型服务商。Simon Willison 在 2026 年 9 月 20 日发布的 llm-keys-ui 0.1(llm 命令行工具的插件)就是冲着这个习惯来的:密钥通过一个本地网页表单写进 llm 的密钥库,Agent 只在需要时用 llm keys get 取用——密钥进去和存取,全程不经过 Agent 会话。本篇十分钟带你把这条密钥卫生链路跑通。
先理解:为什么「粘进会话」是坏习惯
Agent 会话不是保密容器。你粘进去的每一段文本都会成为对话历史的一部分,而对话历史会去很多地方:本地日志、会话同步、团队回放、模型服务的上下文传输,某些工具还会把 transcript 用于调试或训练。密钥一旦进了 transcript,你就要按「已泄露」来处理。正确的分离是:密钥的录入走一条 Agent 看不见的通道,Agent 只拿到「使用密钥的能力」而不是密钥本身。llm 生态已经把地基打好了——llm 工具自带一套密钥管理命令,密钥落在本机的 keys.json 里,模型调用时自动取用;llm-keys-ui 补上的正是「录入」这一环:把「人在终端输入」扩展成「人在浏览器表单输入」,这样即使你只能通过 Agent 操作远程机器,也能让密钥绕开会话直达密钥库。
先检查存量。如果你以前粘过密钥进任何 Agent 会话,按泄露处理:立刻去服务商后台轮换(revoke + 重新签发),再回来配新流程。轮换比任何清理都可靠——你无法确定那段 transcript 已经去了哪里。
Step 1:认识 llm 的密钥库
# 安装 llm(若未安装)
pip install llm
# 密钥库文件在哪
llm keys path # 输出 keys.json 的绝对路径
# 看已存了哪些名字(只列名字,不显示值)
llm keys list
# 人在终端手动录入(值不进 shell 历史)
llm keys set openai
# 按提示粘贴,回车即存入 keys.json
llm 的密钥管理设计给「人坐在终端前」的场景:keys set 单独提示输入值,不上命令行参数,所以 shell 历史里不会留下明文。但这套防护在 Agent 场景里失效——坐在键盘前的不再是人而是模型,它看到的一切都会被记录。所以录入环节要换通道,这正是下一步。
keys.json 的文件权限(它在你的用户目录下,默认只对当前用户可读写)。多用户的机器上确认没有别的账户能读它。Step 2:启动密钥表单服务
# 推荐方式:uvx 临时运行,不污染环境
uvx --with llm-keys-ui llm keys-ui
# 输出示例(默认绑定 127.0.0.1:8010):
# Serving on http://127.0.0.1:8010
# 常用参数:
# -p 8123 换端口
# -h 127.0.0.1 指定绑定地址(默认即本机回环)
# --all 绑定 0.0.0.0,并打印本机所有 IPv4 地址的访问入口
预期效果:终端出现 Serving on 地址,浏览器打开后是一个极简表单——两个输入框(密钥名称、密钥值)加一个保存按钮。默认只绑定本机回环地址,外面碰不到;如果密钥要配到远程机器上(这正是 Willison 的原始场景:从手机驱动远程机器上的 agent),加 --all 让它监听所有网卡,然后从输出里找到局域网地址或 Tailscale 设备地址,用你手头的设备浏览器打开。
这个界面完全没有鉴权。官方 README 原话如此:任何能访问到端口的人都能打开表单、写入密钥(已存的值不会显示,界面只写不读,但写入不受限)。默认的 127.0.0.1 绑定只有本机能碰到,风险可控;一旦用 --all 暴露到网络,就是明文 HTTP 对整个可达网段开放——只在受信任的网络里用、用完立刻停(下一节),这是硬要求不是建议。
Step 3:浏览器里填 Key,密钥直达密钥库
# 表单里填:
# Name: anthropic (密钥名称,之后取用就靠它)
# Key: sk-ant-xxxxxxxx (真实密钥值)
# 点保存 → 写入 llm 的 keys.json
# 验证:名字进了列表,值无处可看
llm keys list
# anthropic
# openai
# ...
llm keys get anthropic # 这条会打印值——只在终端里自己看
保存后密钥直接落进 keys.json,表单全程不回显已存的值——界面上你永远只能「写入」不能「读出」,这保证了就算表单被别人打开,也拿不走你已经存的密钥。注意 llm keys get 这条命令本身会打印明文,它是给终端里的你自己用的;把它放进 Agent 会话输出里和粘密钥没有区别——取用姿势看下一节。
--all 启动,从手机浏览器走 Tailscale 内网地址打开表单——流量走加密隧道,不暴露公网,是这套流程里安全与便捷的平衡点。Step 4:让 Agent「用得上但看不见」
# 场景 A:给某个进程注入环境变量(值在子进程里,不回显)
export ANTHROPIC_API_KEY=$(llm keys get anthropic)
./run-my-task.sh
# 场景 B:直接用 llm 发起模型调用(llm 自动读 keys.json)
llm -m claude-sonnet "总结这个目录的改动"
# 场景 C:告诉远程 Agent 的指令模板
# "运行 llm keys-ui 服务并告诉我访问地址,
# 我在浏览器里把 Key 填进去;
# 之后需要 Key 时用 llm keys get xxx 放进环境变量,
# 不要把密钥内容打印到会话里。"
关键区别在「通道」:Agent 执行 llm keys get anthropic 时,如果命令结果被重定向进环境变量($(...))或直接被下游进程消费而不回显,密钥就不进 transcript;反过来,如果 Agent 把取到的值 print 出来,那一切都白搭。所以给 Agent 的指令要写明规则:取用可以,回显不行。场景 C 的指令模板可以直接抄——先让 Agent 拉起表单服务并报告地址(这一步只暴露地址不暴露密钥),你填完后让它停服务,之后它按需通过命令取用。
再把验证做严一层:取用后翻一下命令历史与 Agent 的输出日志,确认没有出现 sk- 前缀的明文,也没有多余的 keys get 回显痕迹;把这两个检查写进验收清单,比事后追查省心得多。如果团队用共享的构建机,还可以更进一步:把 keys get 包进一层只允许特定任务调用的包装脚本,让「谁能取哪个密钥」从口头约定变成可审计的配置——密钥治理到这里就从个人习惯升级成了团队机制。
表单用完立刻停。回到运行服务的终端按 Ctrl-C 结束进程。无鉴权的表单多活一分钟,暴露面就多一分钟;把「启动 → 填写 → 停止」当成一个一分钟内完成的动作来执行。
Step 5:固化为团队习惯
# 军规清单(可直接抄进团队文档):
# 1. 密钥永不进 Agent 会话/聊天窗口/工单——录入走表单或终端
# 2. 远程配 Key:llm keys-ui + Tailscale,填完即停服务
# 3. Agent 取用只走 $(llm keys get xxx) 重定向,禁止回显
# 4. 误粘即轮换:revoke 旧 Key 重新签发,再排查扩散面
# 5. 定期 llm keys list 盘点:不该存在的名字及时删掉
这套流程的价值不在工具多精巧——它只是把一个朴素原则做顺了手:密钥的录入通道和使用通道都要绕开会被记录的地方。没有 llm-keys-ui 也有等价做法:SSH 过去手动 keys set、用密钥管理器、写临时 .env——原则相同。工具的意义是让正确的事情做起来顺手,顺手的事情才会成为习惯。
sk- 前缀——搜不到任何密钥,就说明这套流程跑对了。进阶辨析:它和 .env、密钥管理器是什么关系
常见疑问:系统里已经有 .env 文件和各类密钥管理器,为什么还要多此一举?三者其实各管一段:.env 适合「进程启动时注入一批配置」,但它是个明文文件,谁拿到磁盘读权限谁就拿走全部密钥,而且经常被顺手 commit 进仓库;系统级密钥管理器(macOS 钥匙串、Windows 凭据管理器、Vault 等)安全性最高,但跨平台使用与自动化调用都要写额外代码;llm-keys-ui 这条链路解决的是一个更窄但高频的问题——人在远程、Agent 在现场时,怎么把密钥安全地送进机器。它不替代前两者,而是补上「远程录入」这块短板。实践里的合理组合是:本机开发用 .env 或密钥管理器,远程 Agent 场景用 keys-ui 表单加 keys get 取用,密钥的长期保管仍然交给专业工具。选型判断只看一个问题:密钥从服务商后台到进程环境变量的路径上,有几处会留下明文?处数越少、越不经过会被同步和记录的通道,越安全。
常见问题速查
| 你遇到的现象 | 大概率原因 & 解决 |
|---|---|
| 浏览器打不开表单 | 远程机器端口不通。确认服务在跑、地址输对;跨机器访问需要 --all + 可达的内网/Tailscale 地址 |
| keys get 输出进了会话记录 | Agent 把值回显了。改用 $(...) 重定向进环境变量,并在指令里明确禁止打印 |
| 忘了密钥存没存过 | llm keys list 只列名字;要确认有效性去服务商后台看用量 |
| 担心 keys.json 被读 | 检查文件权限(仅当前用户可读写);共享机器改用系统密钥管理器 |
| 表单服务忘了停 | 按绑定地址评估暴露面:127.0.0.1 仅本机风险低,--all 立即停进程 |
| 已经粘过密钥进会话 | 按泄露处理:立刻轮换,再排查 transcript 的去向(日志/同步/团队空间) |