Skip to content

[sight] bug: agentsight dashboard 设置页保存的 LLM API Key 以明文存储到 optimization_config.json,仅靠 0o600 文件权限保护,缺乏加密 #1952

Description

@zhangtaibo

Component

sight

Bug Description

agentsight dashboard 新增的"设置"页(SettingsPage.tsx,UI 文案 "管理 Dashboard 的全局配置。")允许用户为优化分析功能配置 LLM provider / API Key / Base URL / Model。后端把 api_key 以明文 JSON 写入 /var/log/sysak/.agentsight/optimization_config.json,只通过 chmod 0o600 限制 owner-only 读/写,没有任何加密保护。

文件权限 0o600 只能阻挡非 root 用户的直接 cat,但在以下场景里都失效:

  • root 用户本机任何进程都可以读到明文 key(agentsight 自己就是 root 跑的,所有 root 进程共享 read 权限)
  • 系统备份 / 日志采集 / 配置管理工具(Ansible / Puppet / scp / rsync)在打包 /var/log/sysak/ 时把明文 key 一并带走,落盘到备份中心 / jump host / 镜像仓库
  • root 权限被入侵后(apt/cron/服务漏洞),cat /var/log/sysak/.agentsight/optimization_config.json 一行就能拿到 LLM API Key,无声无息
  • 文件被误 chmod / cp / tar 后丢失权限保护,明文 key 进入共享介质
  • ECS 快照 / 镜像制作时把含明文 key 的文件打进镜像,分发给其他用户

#1658(cosh-ng ~/.copilot-shell/config.toml 明文存储 api_key / access_key / security_token)是同类问题但不同组件 —— #1658 修了 cosh-ng 的明文存储,但 agentsight dashboard 这个新加的"设置"页又把同样的问题引入了一份,没有走统一 secret store。

转正阻断场景:agentsight 作为 eBPF 观测工具默认以 root 跑、装在测试机 / 镜像里,用户在 dashboard 配了真实可计费的 LLM API Key(DashScope / OpenAI / DeepSeek 等),key 以明文落盘到 /var/log/sysak/.agentsight/optimization_config.json。一旦该机器被共享 / 被入侵 / 被备份外带,API Key 就泄漏,产生计费损失。

Steps to Reproduce

ssh root@<ECS-IP>   # 授权测试机,读者自行替换
# 1. 已装 agentsight(RPM 0.9.0+)
rpm -q agentsight

# 2. 启动 agentsight serve(默认监听 0.0.0.0:7396)
systemctl start agentsight
# 或:agentsight serve &

# 3. 在浏览器打开 dashboard http://<ECS-IP>:7396/
#    进入"设置"页(路径 /settings 或侧栏 ⚙️ 设置)
#    在 "LLM 配置" 表单里:
#      云服务商 选 阿里云 DashScope
#      API Key 输入 sk-<真实 key>
#      模型 选 qwen3-max 或自定义
#    点 "保存配置"
#    页面提示 "✓ 配置已保存并生效"

# 4. 在 ECS 上 cat 配置文件,直接看到明文 key:
cat /var/log/sysak/.agentsight/optimization_config.json
#    期望:api_key 字段是加密的密文 / 在 keyring 里 / 不出现在文件中
#    实际:api_key 字段就是用户输入的明文 sk-<真实 key>

# 5. 验证文件权限(确实是 0o600 owner-only,但这只挡非 root 用户):
stat -c '%a %U:%G %n' /var/log/sysak/.agentsight/optimization_config.json
#    输出:600 root:root /var/log/sysak/.agentsight/optimization_config.json

# 6. 模拟 root 进程读 key(本机 root 任何进程都能读):
head -c 100 /var/log/sysak/.agentsight/optimization_config.json

Actual Behavior

实测(2026-07-28 授权测试机,已 redact 真实 API Key / ECS IP):

[root@<INTERNAL-HOST> ~]# rpm -q agentsight
agentsight-0.9.0-1.alnx4.x86_64

[root@<INTERNAL-HOST> ~]# systemctl status agentsight | head -3
● agentsight.service - AgentSight - eBPF-based AI Agent Observability Tool
     Loaded: loaded (/usr/lib/systemd/system/agentsight.service; enabled; preset: disabled)
     Active: active (running) since Tue 2026-07-28 14:21:30 CST; 9min ago

[root@<INTERNAL-HOST> ~]# ss -tlnp | grep 7396
LISTEN 0      1024         0.0.0.0:7396       0.0.0.0:*    users:(("agentsight",pid=15990,fd=21))

[root@<INTERNAL-HOST> ~]# ls -la /var/log/sysak/.agentsight/optimization_config.json
-rw------- 1 root root 155 Jul 28 14:24 /var/log/sysak/.agentsight/optimization_config.json

[root@<INTERNAL-HOST> ~]# cat /var/log/sysak/.agentsight/optimization_config.json
{
  "api_key": "<REDACTED-API-KEY>",
  "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
  "model": "qwen3-max"
}

[root@<INTERNAL-HOST> ~]# stat -c '%a %U:%G %n' /var/log/sysak/.agentsight/optimization_config.json
600 root:root /var/log/sysak/.agentsight/optimization_config.json

文件权限 -rw-------(0o600)正确生效,但 cat 直接出明文。base_url 和 model 是非敏感配置项,明文存储无问题;问题在 api_key 字段明文。

Expected Behavior

API Key 应该满足下列任一保护方式,不能直接以明文 JSON 字段落盘:

  1. OS keyring 存储(Linux 上用 keyutils / Secret Service / kernel keyring,key 文件只存 keyring handle,不存明文)
  2. 可逆加密 + 密钥派生自机器绑定信息(machine ID + TPM / KMS / 至少 AES-GCM with key from /etc/machine-id + per-install salt)
  3. 统一 secret store(anolisa 全家桶共享一份加密的 secret store,不在各组件各自维护明文 config)
  4. 最低限度:对称加密 + key 文件单独 0o400(比当前 0o600 多一层,但仍是软件层加密,key 在磁盘上)

#1658 的修复方向(cosh-ng config.toml 明文 → 加密),agentsight dashboard 这个新加的"设置"页应该走同样的加密路径,而不是又造一份明文存储。

POST /api/optimize/config 返回值里的 api_key 字段已经做了脱敏(首 6 + •••• + 末 4 字符,见 optimize.rs:85-97 masked_api_key),GET 同样返回脱敏值。这说明设计者已经意识到 API Key 是敏感字段,但只做了传输层脱敏,没做存储层加密 —— 这两个层的处理不对等。

Root Cause

已确认根因(代码层面已静态定位):

  1. src/agentsight/src/server/optimize.rs:48-59save() 函数只 chmod 0o600,不加密:

    fn save(&self, path: &Path) -> std::io::Result<()> {
        let json =
            serde_json::to_string_pretty(self).map_err(|e| std::io::Error::other(e.to_string()))?;
        std::fs::write(path, json)?;
        // Config contains an API key — restrict to owner.
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
        }
        Ok(())
    }

    serde_json::to_string_pretty(self) 直接把 OptLlmConfig.api_key: Option<String> 序列化为明文 JSON,写入磁盘,然后 chmod 0o600 —— 这是对文件权限的保护,不是对内容的加密。

  2. src/agentsight/src/server/optimize.rs:30-38 OptLlmConfig struct 里 api_keyOption<String>,存储的是明文,没有任何 marked-for-encryption 的标记 / 包装类型。serde 直接序列化 String 字段就是明文。

  3. 缺少 secret store 抽象:agentsight 的 server/auth.rs 里有 DashboardAuth.dashboard_token 文件,也是明文存储(但 dashboard_token 是服务自己生成的随机 token,泄漏后影响范围限于 dashboard 访问,跟 API Key 的计费损失不可类比)。agentsight 没有"敏感凭据统一加密存储"的模块,每个新加的 secret 字段都各自走"明文 + chmod 0o600"的捷径。

Suggested Fix

按优先级:

方向 A(推荐,根治)—— 用 OS keyring 存储 API Key

修改 OptLlmConfig::save()OptLlmConfig::load():

  • api_key 字段从 config 文件里移除,改存到 Linux kernel keyring(keyutils)或 Secret Service(D-Bus)
  • config 文件里只保留 base_url / model 等非敏感字段
  • keyring key 用 agentsight.optimize.<base_url> 或类似 namespace 派生
  • 跨用户 / 跨容器场景 keyring 不可用时,fallback 到方向 B

Linux keyring 是最干净的方案:key 永远不出现在磁盘上,只有持有 keyring perm 的进程能 read。Linux 内核自带 keyutils,无需额外依赖。

方向 B(配套,fallback)—— 可逆加密 + 密钥派生自 machine-id

如果 keyring 不可用(如容器 / 无 loginuid 场景),用 AES-GCM 加密 api_key,密钥派生自 /etc/machine-id + per-install salt(首次随机生成存到 /var/log/sysak/.agentsight/.secret_salt 0o400):

  • 加密后 api_key 字段值是 enc:<base64-ciphertext>,不再是明文
  • 即使文件被 cp 出去,没有 machine-id 也解密不了
  • machine-id 是机器绑定的,跨机器迁移时 key 自动失效(用户需要重新配,符合预期)

这是 #1658 的同类修复方向(cosh-ng config.toml 加密),agentsight dashboard 应该走同样路径,甚至共享同一份加密实现。

方向 C(长期)—— 统一 anolisa 全家桶 secret store

anolisa 的多个组件(cosh-ng / agentsight / 未来其他)都有"用户配 API Key"的需求,各自走"明文 + chmod 0o600"的捷径,会反复踩同一个坑。长期应该有一个统一的 secret store 模块:

  • 统一加密 / 解密接口
  • 统一存储位置(如 /etc/anolisa/secrets/~/.local/share/anolisa/secrets/)
  • 统一 keyring fallback 策略
  • 各组件的 config 文件里只存非敏感字段,敏感字段走 secret store

这条最难但最干净,可以分阶段推进(先抽 agentsight 的 secret store 模块,再推广到 cosh-ng / 其他)。

方向 D(防御性,短期)—— 文档警告 + 配置项开关

短期补救:在 dashboard 设置页 + README + agentsight.json 配置文档里明确警告 "API Key 以明文存储于 /var/log/sysak/.agentsight/optimization_config.json,请确保该文件不被共享 / 备份 / 入侵",并加配置项 optimization.api_key_storage = "plaintext" | "keyring" | "encrypted" 让用户能选存储方式(默认 plaintext 保持兼容,推荐 keyring)。

Environment

  • OS: Alibaba Cloud Linux 4.0.3 (Agentic Edition), kernel 6.6.102-6.2.alnx4.x86_64
  • Component version: agentsight 0.9.0
  • Package/RPM: agentsight-0.9.0-1.alnx4.x86_64
  • 文件路径: /var/log/sysak/.agentsight/optimization_config.json(默认 storage_base_path = /var/log/sysak/.agentsight,见 src/agentsight/src/config.rs:1355-1356)
  • 文件权限: -rw-------(0o600,owner-only read/write)

Additional Context

Metadata

Metadata

Labels

bugcomponent:sightsrc/agentsight/priority:p2Normal: planned product or engineering work

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions