背景
connections.json / connections.local.json 目前以明文保存数据库密码。文件权限是 0600,v1.1.2 起默认路径也移到了仓库外的 ~/.config/database-cli/connections.json,但这两项都只解决了"被别的用户读到"和"被误提交",没有解决以当前用户身份运行的任意进程都能直接读取凭据。
典型场景:一个恶意的 npm postinstall 脚本、任意一个有文件读权限的 agent、或者把 ~/.config 同步上云的备份工具。
现状:已经做对的部分
先说清楚这些,因为它决定了本提案的增量有多大。密码解析集中在 db-query 的 password_value():
- 密码不进 argv。有密码时 DSN 以
include_password=False 构造,改由 sq add --password 从 stdin 读取,ps 看不到
- 审计日志有
redact(),密码在写入前被替换为 ***
- 配置文件
0600,临时 sq 配置目录 0700
也就是说运行时的泄漏面已经处理得比较干净,剩下的主要是静态存储这一处。
为什么 password_env 不算解法
目前的替代方案是 password_env,但密码总要从某处进入环境变量,实际做法通常是写进 ~/.zshrc。这在多数情况下更糟:
~/.zshrc 默认权限是 0644,比配置文件的 0600 更宽
export 后密码进入此后启动的每一个进程的环境
等于把明文从一个 0600 文件挪到一个 0644 文件,外加全局进程环境。对 CI 这类由外部注入环境变量的场景它是合适的,但对本机开发不是。
建议
新增可选字段 password_keychain,配置里只保存引用而非密码本身:
{
"environments": {
"qa01": {
"driver": "mysql",
"host": "db.example.internal",
"username": "readonly_user",
"password_keychain": { "service": "database-cli", "account": "qa01" }
}
}
}
取值时调用系统钥匙串,与现有两种来源并列,在 password_value() 里多一个分支即可:
- macOS:
security find-generic-password -s <service> -a <account> -w
- Linux:
secret-tool lookup service <service> account <account>
- Windows:凭据管理器
配套加一个写入入口(例如 scripts/init-config --password-keychain),避免用户手工调平台命令。
优先级建议:三者并列,password_keychain > password_env > password。
这个提案不能解决什么
不想把收益说过头:
- 密码仍会进入进程内存,仍会经 stdin 传给
sq
- 待确认:
sq add --password 之后,密码是否会写入 sq 的临时配置文件。我没能验证——sq add 在目标不可达时会拒绝添加,而我不想拿真实凭据去试。如果确实落盘,那么即使接入钥匙串,明文仍会短暂出现在 0700 临时目录中,这一处需要单独处理
- 非交互场景需要降级路径:macOS 首次访问钥匙串需要授权,CI 环境中通常没有可用钥匙串
代价
三套平台实现,加上取不到时的降级逻辑。对一个个人工具来说不算小,所以这里只提出问题和方案,是否值得做由维护者判断。
优先级
个人判断为中等。风险最高的是生产环境连接,但它通常配置为只读账号(writable: false),实际影响面有限。
背景
connections.json/connections.local.json目前以明文保存数据库密码。文件权限是0600,v1.1.2 起默认路径也移到了仓库外的~/.config/database-cli/connections.json,但这两项都只解决了"被别的用户读到"和"被误提交",没有解决以当前用户身份运行的任意进程都能直接读取凭据。典型场景:一个恶意的 npm postinstall 脚本、任意一个有文件读权限的 agent、或者把
~/.config同步上云的备份工具。现状:已经做对的部分
先说清楚这些,因为它决定了本提案的增量有多大。密码解析集中在
db-query的password_value():include_password=False构造,改由sq add --password从 stdin 读取,ps看不到redact(),密码在写入前被替换为***0600,临时 sq 配置目录0700也就是说运行时的泄漏面已经处理得比较干净,剩下的主要是静态存储这一处。
为什么
password_env不算解法目前的替代方案是
password_env,但密码总要从某处进入环境变量,实际做法通常是写进~/.zshrc。这在多数情况下更糟:~/.zshrc默认权限是0644,比配置文件的0600更宽export后密码进入此后启动的每一个进程的环境等于把明文从一个
0600文件挪到一个0644文件,外加全局进程环境。对 CI 这类由外部注入环境变量的场景它是合适的,但对本机开发不是。建议
新增可选字段
password_keychain,配置里只保存引用而非密码本身:{ "environments": { "qa01": { "driver": "mysql", "host": "db.example.internal", "username": "readonly_user", "password_keychain": { "service": "database-cli", "account": "qa01" } } } }取值时调用系统钥匙串,与现有两种来源并列,在
password_value()里多一个分支即可:security find-generic-password -s <service> -a <account> -wsecret-tool lookup service <service> account <account>配套加一个写入入口(例如
scripts/init-config --password-keychain),避免用户手工调平台命令。优先级建议:三者并列,
password_keychain>password_env>password。这个提案不能解决什么
不想把收益说过头:
sqsq add --password之后,密码是否会写入 sq 的临时配置文件。我没能验证——sq add在目标不可达时会拒绝添加,而我不想拿真实凭据去试。如果确实落盘,那么即使接入钥匙串,明文仍会短暂出现在0700临时目录中,这一处需要单独处理代价
三套平台实现,加上取不到时的降级逻辑。对一个个人工具来说不算小,所以这里只提出问题和方案,是否值得做由维护者判断。
优先级
个人判断为中等。风险最高的是生产环境连接,但它通常配置为只读账号(
writable: false),实际影响面有限。