Skip to content

支持从系统钥匙串读取密码,降低配置文件的静态泄漏面 #3

Description

@CassianFlorin

背景

connections.json / connections.local.json 目前以明文保存数据库密码。文件权限是 0600,v1.1.2 起默认路径也移到了仓库外的 ~/.config/database-cli/connections.json,但这两项都只解决了"被别的用户读到"和"被误提交",没有解决以当前用户身份运行的任意进程都能直接读取凭据

典型场景:一个恶意的 npm postinstall 脚本、任意一个有文件读权限的 agent、或者把 ~/.config 同步上云的备份工具。

现状:已经做对的部分

先说清楚这些,因为它决定了本提案的增量有多大。密码解析集中在 db-querypassword_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),实际影响面有限。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions