Security guide · 2026-10-08
减少本地凭据:用 1Password 和不用 1Password 的做法
这一页按凭据类型列出两套做法。一套用 1Password,另一套只用系统自带工具和开源工具。第三节单独讲办公室里当服务器用、平时没人在旁边的机器。
先说结论
目标是磁盘上不留明文凭据。有人在旁边的电脑,用 1Password 桌面应用加 Touch ID 发放凭据:SSH 代理、op run、shell 插件。
没人在旁边的机器按不了 Touch ID。这类机器各用一个只读的服务账号,只能读一个专用保险库。这个服务账号的令牌是磁盘上唯一留下的凭据,放进系统钥匙串或 systemd-creds。
生产环境和个人账号的凭据不放到无人值守的机器上。这套做法防的是同步、备份、git 和丢电脑造成的泄露。以你的用户身份运行的恶意程序仍然能拿到当时正在用的凭据。
一、本地凭据从哪里泄露
1.1 五条泄露路径
| 路径 | 例子 | 去掉明文后 |
|---|---|---|
| 云同步 | Dropbox、iCloud 里的 .env、密码导出文件、私钥 | 挡住 |
| Git 提交 | 写在 .zshrc 或配置文件里的密钥,被 push 到 GitHub。删掉文件后,历史里还在 | 挡住 |
| 备份 | Time Machine、整机镜像、发给别人的日志和导出包 | 挡住 |
| 设备丢失 | 笔记本被偷,磁盘没加密或开机密码太弱 | 挡住(磁盘加密是前提) |
| 本机恶意程序 | 粘贴执行了一条来路不明的命令,程序以你的身份读文件和钥匙串 | 只能缩小范围 |
最后一条在常见的例子中是这样发生的:有人在 MacBook 的终端里执行了一条伪装成 base64 的命令,后门拿到了 macOS 登录密码。这时登录钥匙串、浏览器保存的密码和 Cookie、SSH 私钥、云平台凭据都在攻击者的能力范围内。如果 1Password 当时处于解锁状态,它的本地数据也在范围内。攻击者随后就能从外部用这些凭据操作 GitHub 和 AWS。
1.2 这套做法挡不住什么
- 以你的用户身份运行的程序,可以读同一用户下其他进程的环境变量,也可以在 1Password 解锁期间调用它。
- 浏览器 Cookie 和应用自己缓存的登录状态(Slack、VS Code、Codex 的
auth.json)。它们由应用管理,1Password 接不进去。 - 已经泄露过的凭据。把明文删掉不会让旧值失效,只有轮换(作废旧值、换新值)才会。
所以这套做法要配三件事:不在终端里粘贴来路不明的命令;每个凭据只给最小权限;发现异常时能快速轮换。
二、按凭据类型的做法
2.1 总表
| 凭据 | 用 1Password | 不用 1Password |
|---|---|---|
| SSH 私钥 | 1Password SSH 代理,私钥不写到磁盘,每次使用 Touch ID | macOS:Secretive(私钥在 Secure Enclave);两种系统都可用 YubiKey 的 ed25519-sk 密钥 |
API 密钥、.env | .env 只写 op:// 引用,用 op run 启动程序 | sops 加 age 加密 .env;或者运行时从钥匙串取值 |
| 命令行工具令牌 | shell 插件(gh、aws 等) | 工具自带的钥匙串存储(gh 默认就是);AWS 用 aws-vault |
| Git HTTPS 凭据 | shell 插件或 gh auth setup-git | osxkeychain、libsecret 凭据助手 |
| 提交签名 | 用 1Password 里的 SSH 密钥签名(op-ssh-sign) | 用 Secretive 或 YubiKey 里的 SSH 密钥签名 |
| 云平台密钥 | shell 插件;能用 SSO 短期凭据时优先用 SSO | SSO 短期凭据;aws-vault |
| 网站密码、两步验证 | 1Password 保存密码、TOTP、通行密钥;Watchtower 查弱密码和泄露 | Apple 密码 App;两步验证用硬件密钥或单独的验证器 |
| 无人值守机器 | 每台机器一个只读服务账号,一个专用保险库 | macOS 钥匙串;Linux systemd-creds;sops 加 age |
2.2 SSH 私钥
用 1Password。在 1Password 里新建或导入 SSH 密钥,然后在“设置 → 开发者”里打开“使用 SSH 代理”。在 ~/.ssh/config 里把代理指向 1Password:
Host *
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock" # macOS
# Linux: IdentityAgent ~/.1password/agent.sock
1Password 对每把密钥单独要求授权,用 Touch ID 或系统密码批准。确认能登录以后,删掉 ~/.ssh/id_* 私钥文件。
不用 1Password。macOS 用开源应用 Secretive,私钥在 Secure Enclave 里生成,导不出来,每次使用可以要求 Touch ID。Linux 和 macOS 都能用 YubiKey 生成 ed25519-sk 密钥,每次使用要碰一下硬件。最低要求:私钥加 passphrase,再交给 ssh-agent,不要放进 Dropbox。
2.3 API 密钥和 .env 文件
用 1Password。.env 里只写引用,格式是 op://保险库/条目/字段:
OPENAI_API_KEY=op://Dev/openai/credential
DATABASE_URL=op://Dev/slax-local-db/url
启动时用 op run --env-file=.env -- npm run dev。真实值只出现在这个进程的环境变量里。op run 默认把输出里的密钥值替换成一段提示文字。这种 .env 可以提交到 git。配置文件模板用 op inject -i config.tpl.yml -o config.yml,生成的文件要加进 .gitignore。
1Password Environments 是更新的做法:在桌面应用里管理一组变量,把它挂载成本地 .env。官方说明写的是“不把明文密钥写到磁盘”。它要求管理员打开 Environments 策略,桌面应用要处于登录状态。
不用 1Password。用 sops 加 age 把 .env 加密后提交,运行时 sops exec-env .env.enc 'npm run dev'。age 私钥是唯一的明文,放在 ~/.config/sops/age/keys.txt,不进 Dropbox。只有一两个变量时,直接从钥匙串取:export OPENAI_API_KEY="$(security find-generic-password -s openai -w)"。这一行可以写进 .zshrc,文件里没有明文。
2.4 命令行工具的令牌
用 1Password。op plugin init gh,按提示选择 1Password 里的令牌条目。以后运行 gh 时,1Password 在运行那一刻提供令牌,需要 Touch ID。1Password 为几十个工具提供了插件,包括 gh、aws、stripe、openai、huggingface。接入后删掉工具原来的明文文件,比如 ~/.config/gh/hosts.yml 里的 oauth_token。
不用 1Password。很多工具默认用系统钥匙串,gh 新版本默认存进钥匙串,加了 --insecure-storage 才写明文。先 gh auth logout,再 gh auth login,检查 hosts.yml 里不再有令牌。Docker 设置 credsStore,npm 和 PyPI 用短期令牌或可信发布。
2.5 Git 凭据和提交签名
用 SSH 地址时,Git 用的就是 2.2 节的 SSH 密钥。提交签名也用同一把密钥:
git config --global gpg.format ssh
git config --global user.signingkey "ssh-ed25519 AAAA..." # 公钥
git config --global gpg.ssh.program "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
git config --global commit.gpgsign true
不用 1Password 时,不设置 gpg.ssh.program,签名密钥指向 Secretive 或 YubiKey 的公钥。HTTPS 地址的凭据交给 osxkeychain(macOS)或 libsecret(Linux 桌面)凭据助手。不要用 store 助手,它把令牌明文写进 ~/.git-credentials。
2.6 云平台访问密钥
长期访问密钥(AWS 的 AKIA 开头那种)是最危险的一类:泄露后在作废前一直能用。能用 SSO 的,用 aws sso login、gcloud auth login 拿几小时有效的短期凭据。必须用长期密钥时,用 1Password 的 aws 插件,或者用 aws-vault 把密钥存进钥匙串,每次换成短期凭据再交给命令。~/.aws/credentials 里不留明文。
2.7 网站密码、两步验证和恢复码
- 浏览器导出的密码 CSV 是全部密码的明文。导入密码管理器以后马上删除;在 Dropbox 网页的“已删除文件”里再永久删除一次。
- 关掉浏览器自带的密码保存,避免两处各存一份。
- 1Password 的 Watchtower 列出重复、弱、已泄露的密码和“可开启两步验证”的网站,按列表处理。不用 1Password 时,Apple 密码 App 也有“安全建议”。
- 1Password、Apple ID、主邮箱这三个账号的恢复码,另外在线下存一份。它们是找回其他一切的入口,不能只存在 1Password 里。
三、无人值守的机器
3.1 问题:没有人按 Touch ID
1Password 桌面应用给命令行授权时,需要有人在这台机器上按 Touch ID 或输入密码。授权在终端空闲 10 分钟后失效,最长 12 小时;应用锁定后全部授权立即失效。人在外面时,办公室的机器弹出授权框也没人批准,代理和定时任务就会卡住或失败。
不能为了方便关掉锁定,也不能让个人账号一直保持解锁。这类机器要用另一种身份:机器自己的身份,权限固定,而且很小。
3.2 用 1Password:服务账号
服务账号是给机器用的 1Password 身份,用一个令牌登录,不需要桌面应用和 Touch ID。设置 OP_SERVICE_ACCOUNT_TOKEN 环境变量后,op read、op run、op inject 都照常工作。
- 每台机器单独建一个保险库,例如
Server-M2,只放这台机器的无人值守任务需要的凭据。 - 每台机器单独建一个服务账号,只给这个保险库的读权限。泄露时只作废这一个。
- 服务账号读不到 Personal、Private、Employee 保险库和默认的 Shared 保险库。个人凭据因此天然隔开。
- 权限建好后不能修改。要改范围,就新建一个服务账号,再删掉旧的。
- 令牌只显示一次,先存进 1Password 自己的条目里,再放到机器上。
| 套餐 | 每个令牌每小时 | 整个账户每 24 小时 |
|---|---|---|
| Business | 读 10,000 次,写 1,000 次 | 50,000 次 |
| Teams | 读 1,000 次,写 100 次 | 5,000 次 |
| Individual、Families | 读 1,000 次,写 100 次 | 1,000 次 |
个人和家庭套餐每天只有 1,000 次,所有服务账号共用。每分钟跑一次的定时任务如果每次都调用 op,一天就超了。长期运行的程序应该在启动时取一次凭据,不要每次请求都去读。
3.3 不用 1Password:系统钥匙串和加密凭据
macOS。用户保持登录时,登录钥匙串是解锁的,后台任务可以直接读:
security add-generic-password -a "$USER" -s openai-m2 -w # 回车后输入值,不进 shell 历史
security find-generic-password -a "$USER" -s openai-m2 -w # 脚本里这样取值
Linux。系统服务用 systemd-creds 加密凭据。有 TPM2 芯片时,密文和这台机器绑定,拷到别的机器上解不开:
sudo systemd-creds encrypt --name=openai - /etc/credstore.encrypted/openai
# 单元文件里:
LoadCredentialEncrypted=openai:/etc/credstore.encrypted/openai
# 程序从 $CREDENTIALS_DIRECTORY/openai 读取
多台机器共用一批凭据时,用 sops 加 age:密文进 git,每台机器各有一把 age 私钥,丢一台只作废那一把。
3.4 人不在时需要高权限
无人值守的机器只放自动任务需要的凭据。人不在办公室、又要做高权限操作(生产部署、改 DNS、动个人账号)时,按下面的顺序选:
- 从随身电脑 SSH 到办公室机器,用
ssh -A转发 1Password SSH 代理。授权框弹在随身电脑上,用 Touch ID 批准,办公室机器上不留私钥。只对自己的机器用-A,不要在配置里写Host * ForwardAgent yes。批准以后,这个会话里同一把密钥的后续连接都不再询问。 - 操作在随身电脑上本地做,不经过办公室机器。
- 等回到办公室再做。
不要为了远程方便,把生产密钥放进服务器保险库。
四、AI 代理的规则
Claude Code、Codex 这类代理以你的用户身份运行,能读你能读的文件,也能运行 op。下面五条把它们能拿到的东西压到最小:
- 代理进程只拿它需要的环境变量。用
op run --env-file=agent.env -- env -u OP_SERVICE_ACCOUNT_TOKEN 代理命令启动:代理拿到解析后的值,拿不到服务账号令牌本身。 - 有人值守的电脑上,没有预期的 1Password 授权框一律拒绝。批准一次,这个终端会话就有最多 12 小时的访问权。
- 代理用的 GitHub 令牌用细粒度令牌:只给需要的仓库,设置到期日,不给组织管理权限。
- 代理可读的目录里不放
.env明文。项目的.env只写op://引用。 - 代理的日志和对话导出可能带上密钥。导出包放进 Dropbox 前,先搜一遍
sk-、ghp_、AKIA、PRIVATE KEY。
五、方法与来源
1Password 的功能、时限和请求限制在 2026-10-08 按官方开发者文档核对。FileVault 远程解锁按 Apple 的 macOS 26 企业版说明核对。gh 从哪个版本开始默认使用钥匙串,本次没有核对。
- 1Password Developer,Service accounts: Get started,1password.dev/service-accounts/get-started
- 1Password Developer,Service account rate limits,1password.dev/service-accounts/rate-limits
- 1Password Developer,CLI app integration security,1password.dev/cli/app-integration-security
- 1Password Developer,SSH agent forwarding,1password.dev/ssh/agent/forwarding
- 1Password Developer,Environments,1password.dev/environments
- Apple Support,What's new for enterprise in macOS Tahoe 26,support.apple.com/en-us/124963
- Secretive(Secure Enclave SSH 密钥),github.com/maxgoedjen/secretive
- sops,github.com/getsops/sops
- aws-vault,github.com/99designs/aws-vault
- systemd-creds 手册,freedesktop.org/…/systemd-creds.html