资讯动态

AI桌面客户端安全审计实战:从威胁建模到漏洞修复全流程

发布时间:2026/10/19 5:18:25 来源:尧图企业网站定制
说实话把“安全审计”放在一个普通的 AI 桌面客户端上很多人第一反应是“小题大做”——不就是一个调用大模型 API 的壳子吗真有那么多值得挖的东西但我把某个开源的 AI 客户端完整过了一遍之后发现这类应用的攻击面比想象中大得多本地配置文件、SQLite 数据库、日志目录、会话凭证、插件机制、临时文件、甚至渲染层加载的远程内容每一样都可能成为突破口。这篇文章就把我这次对某 AI 客户端下文统一叫“该客户端”做安全审计的完整过程拆给你看。不是单纯喊你跑两个扫描器交差而是从威胁建模、依赖供应链、本地数据、网络通信、修复验证一条线走下来每个环节都有可复现的命令和判断依据。适合三类人看一是自己开发了类似工具的独立开发者想给产品补一轮安全自查二是安全岗位的同学接手一个桌面端应用不知道怎么下手三是普通用户想搞清楚本机 AI 工具到底把你的 API 密钥和聊天记录放在哪、暴露面有多大。审计这件事知道边界比知道技巧更重要。1. 审计前先想清楚这类应用到底要防谁1.1 划定审计边界和威胁模型桌面客户端审计和 Web 应用审计完全是两套逻辑。Web 你关心的是服务端注入、越权、SSRF桌面端你关心的是本地文件的机密性、进程间通信、供应链投毒、凭证泄露。所以动手前先画一张简化的数据流图用户输入 → 前端进程 → 本地存储数据库/配置文件→ API 请求 → 远端模型服务 → 响应渲染。每一个箭头都是一条审计路径。我给自己列的资产清单包括四类一是本地持久化数据聊天记录、配置项、索引文件二是敏感凭证API Key、访问令牌、会话 Cookie三是功能扩展入口插件、自定义 Prompt、导入导出四是与远端的通信链路。威胁模型按“单机单人使用”和“多用户共享设备”两种场景分别推演了一遍。前者重点防恶意文件、恶意脚本、钓鱼数据包后者还要额外考虑操作系统账户隔离和文件权限。这一阶段容易犯的错误是只盯着“能不能被远程攻击”忽略了本地威胁。实际上对于一个 AI 客户端来说本机恶意软件只要拿到了你的~/.config目录基本上就等于拿走了你的 API 密钥和全部历史对话。所以审计边界的核心原则是凡是用户数据落盘的地方都必须假定会被攻击者读到凡是发起网络请求的地方都必须假定会被中间人监听。1.2 审计环境与工具准备清单别直接在你日常用的那台机器上做审计建议拉一个干净的虚拟机或者用一台闲置机器。原因很简单漏洞扫描和动态调试会产生大量脏数据而且一些测试动作可能污染你真实的 API 凭证。我这次用的环境是 Ubuntu 24.04 虚拟机装了这些工具用途工具说明依赖扫描npm audit / pip-audit / osv-scanner锁定文件级别的已知漏洞匹配供应链元数据某代码托管平台安全监控、Scorecard 风格脚本检查依赖维护状态、发布源完整性本地文件监控inotifywait / fsmonitor观察应用运行期间读写路径网络抓包mitmproxy / tcpdump审计 HTTPS 流量、证书校验逻辑动态调试strace / lsof / gdb跟踪文件句柄、进程行为静态分析Semgrep / CodeQL 轻量规则扫描源码中的危险模式环境准备好之后再明确一个合规底线你只能审计自己有权审计的对象。拿到源码的开源项目可以在本机做任意实验但不得对托管服务发起未授权的探测。整个审计过程里我所有对远端接口的测试都限制在客户端本身发起的正常请求范围内不做超出协议语义的越界请求。安全审计的价值在于把自身的伤口暴露出来而不是去攻击别人。2. 供应链与依赖漏洞先把这个面扫干净2.1 依赖树梳理比想象中更难搞这类客户端绝大多数基于 Electron 架构依赖数量轻松破千。刚开始我直接跑了一遍npm audit结果让人头大几百条告警里夹杂着大量“high”级别问题但如果直接照着升级应用立刻跑不起来。原因在于依赖冲突——Electron 的上层封装库和大模型 SDK 之间往往锁定了一组特定版本。所以正确流程是先建立完整的依赖清单。我用三条命令并行# 查看生产依赖的生命周期和锁定关系 npm ls --depth10 --omitdev dependency_tree.txt # 导出全部生产包信息 npm list --json --omitdev production_deps.json # 用通用扫描器做一次全量匹配 osv-scanner scan --lockfile package-lock.json --lockfile pnpm-lock.yaml必须说明的是osv-scanner的输出比npm audit更全面因为它的漏洞库覆盖了多种生态而不局限于 npm。跑完之后我把它按“直接依赖”和“传递依赖”分开筛选因为决策逻辑完全不同。直接依赖你可以自主升级换板传递依赖只能靠间接升级或绕过路径。2.2 判断依赖风险的三层标准拿到扫描报告不要急着全修。我按三层标准做筛选避免被报告牵着鼻子走第一层看漏洞利用条件。一个需要本地文件写入权限才能触发的“high”漏洞在沙箱环境里的实际危害远小于一个远程可触发的“medium”。至少要把报告里每条漏洞的 CVE 描述、攻击向量AV:N / AV:L看一遍再决定优先级。第二层看依赖链路相关度。如果一个有漏洞的包只在构建时用到不会进入最终分发产物它的风险很低。我用npm ls 包名确认包在依赖树中的位置对于深度超过四层的传递依赖先查它上游是否已经修复再决定是否升级。第三层看运行时是否真的调用了危险函数。静态扫描报告经常覆盖不到“代码实际执行路径”。比如某个加密相关的库存在内存信息泄露漏洞但应用其实只在安装向导里用了一下这种风险就可以降低处理。这里贴一段我当时用来自动筛选高危直接依赖的思路用 semgrep 规则辅助判断是否把漏洞库引入了关键调用路径rules: - id: dangerous-http-client pattern: | require($HTTP_LIB) metavariable-regex: metavariable: $HTTP_LIB regex: (request|axios|undici) message: 检查客户端是否为该 HTTP 库设置了证书校验 languages: [javascript] severity: WARNING依赖治理做完之后我核对了所有工具的lockfile状态发现仓库里存在两个package-lock.json其中一个已经长期没有更新。这种锁文件不一致的情况在生产包里非常危险——打包时可能加载的是另一个版本的依赖。项目必须保证所有安装入口使用同一份锁文件并且在 CI 里加一道“锁文件 fresh 校验”这是我在这次审计里最深的体会之一。3. 本地数据与凭证安全客户端的命门在这里3.1 配置文件、数据库和日志目录的盘查桌面客户端的本地数据基本逃不出两三个固定目录。Linux 下是~/.config/应用名和~/.local/share/应用名macOS 下是~/Library/Application Support/应用名Windows 下是%APPDATA%。审计第一步就是监控应用启动到退出的完整生命周期把所有落盘文件的路径抓出来。我用了最简单的strace跟踪# 跟踪文件写入、创建、权限变更事件 strace -f -e tracecreat,openat,rename,mkdir,chmod -o file_ops.log ./应用可执行文件配合inotifywait监控目录变化inotifywait -m -r ~/.config/应用名 ~/.local/share/应用名 -e create,modify,delete --format %w%f %e跑完一轮之后典型的风险点会浮出来配置目录下往往有config.json存着 API 地址和密钥logs/下会滚动记录请求信息和错误堆栈。最扎心的是日志文件——我发现有个版本的日志把请求头完整打印了一遍里面带着Authorization: Bearer字段。这等于把密钥明文写进了日志日志轮转后还可能残留一个月甚至更久。检查配置目录权限时我顺手做了一遍比对find ~/.config/应用名 -exec stat -c %a %n {} \; | sort -u看到了什么数据库文件是644配置文件是644日志目录是755。这意味着同一台机器上的其他普通用户进程只要知道路径就能直接读取你的 API Key 和所有对话记录。对多用户设备来说这是硬伤。3.2 凭证存储的三个常见坑第一明文密钥直接放在配置文件。这是最高频的低级问题。解决方法不是简单改个文件名而是把敏感字段迁移到操作系统级密钥链。在 Electron 应用里普遍做法是用keytar或safeStorage机制。Linux 下推荐通过secret-tool存到 Secret Service示例secret-tool store --labelAI客户端 API Key serviceappname userdefault secret-tool lookup serviceappname userdefault应用层读取逻辑变成程序启动后从系统密钥链取密钥使用完毕后立即从内存中清除引用而不是从配置文件加载。第二令牌刷新逻辑缺失。很多客户端把用户的长期 API Key 存在本地有效期动辄一年甚至永久。如果本地文件被读走攻击者直接拥有完整权限。更稳妥的设计是引入短期访问令牌加长期刷新令牌的机制并且刷新令牌也要分段存储、加密处理。第三备份文件残留。很多应用会在覆盖配置文件前生成.bak或config.json~临时文件。我在目录里翻到过两份内容一致但权限不一致的历史版本旧版甚至注释了完整的密钥。审计时一定要通配符搜索所有隐藏文件ls -la ~/.config/应用名/ find ~/.config/应用名/ -name *.bak -o -name *.tmp -o -name *~顺手还要检查数据库。SQLite 是这类应用最常用的存储引擎数据库文件默认不加密。如果聊天记录里有业务敏感信息至少要做字段级加密。Electron 生态常用better-sqlite3配合SQLCipher才能做整体加密迁移成本不算低但这是隐私产品绕不开的一环。4. 网络通信与输入校验入口处把住门4.1 抓包看 HTTPS证书校验是否被绕过桌面端应用最容易出问题的网络环节是“证书校验被关闭”。开发者为了开发方便拦截了 HTTPS 证书错误结果顺手把代码合到了生产环境。用 mitmproxy 开一个代理然后把系统流量指过去立刻就能验证mitmproxy --mode transparent --showhost --set keep_host_headertrue跑起来后如果客户端正常启动了、请求也正常发出了且代理能看到明文请求内容说明客户端信任了代理的自签证书——这意味着中间人攻击也能做到同样的事。如果你还能在代理里直接看到请求中包含的密钥那就是双重打击。审计中我重点关注三类请求一是根域名是否为固定主机二是有没有做证书固定certificate pinning三是请求路径里的参数是否会带出额外信息。很多客户端直接把用户输入的模型名称、参数塞进 URL query 里中间人看一眼流量就能推断用户行为习惯。左图右表的记录方式做一个结论化管理我当时把全量请求按以下表格归档请求类型目标域名是否加密是否固定证书请求头敏感字段备注对话补全api.example-model.com是否Authorization正常遥测上报trace.example-analytics.com是否设备 ID存在过度收集自动更新update.example-cdn.com是是无正常图片渲染cdn.example-img.com是否Cookie携带会话信息值得注意的一点证书固定不是越高越好。很多客户端选择不固定证书是为了让企业环境里的 SSL 解密代理正常工作。所以审计结论不要只写“缺少证书固定”而要评估威胁模型——面向大众用户且处理敏感数据的应用建议固定证书面向企业需要走代理的场景至少要做严格的证书透明度校验。4.2 本地命令执行和路径穿越的排查AI 客户端有一个非常特别的功能面它往往允许用户加载本地文件文档、图片、音频然后通过提示词让模型分析部分客户端还支持插件系统或自定义脚本。这个面一旦没做输入校验就会从“数据处理工具”变成“本地命令执行器”。我重点检查了三条路径文件路径解析、插件加载、模型返回内容是否被当成命令执行。最容易踩雷的是文件路径解析。比如某个功能要读取用户指定的 PDF代码如果写成const filePath path.join(userInputDir, userInputFileName); const content fs.readFileSync(filePath);攻击者构造../../../../etc/passwd就能读取任意文件。正确姿势是做规范化normalize之后再校验前缀拒绝包含..的路径const resolved path.resolve(baseDir, userInputFileName); if (!resolved.startsWith(path.resolve(baseDir))) { throw new Error(非法路径); }插件系统是更大的雷。我看到过不少客户端通过eval或者new Function去执行模型返回的代码片段这本质上是把远端模型给的结果当成了本地命令。正确的设计是若需要执行动态逻辑用沙箱进程或在 Worker 线程里做严格白名单过滤能不用eval就坚决不用。模型返回内容的处理也很关键。如果模型回答里包含类似rm -rf的文本客户端不能把它拼接到 shell 命令里。审计时我专门查了有没有把模型输出直接传入child_process.exec的代码路径凡是出现的一律改为execFile加参数数组避免 shell 解析。5. 漏洞修复实操从定位到验证闭环5.1 修复优先级排序按“可利用性 × 影响范围”打分扫描报告动不动就是几百条一股脑全修既不现实也容易引入回归。我采用了一个简化版打分公式风险值 可达性 × 危害等级 × 被利用概率。可达性按“是否与网络入口直接接触”和“是否可被本地低权限用户触发”排序危害等级参考 CVSS被利用概率参考是否存在公开 PoC。按照这个标准这次审计得出的 TOP 风险项如下排名问题风险描述修复策略1API Key 明文保存在配置目录本地其他用户可直接窃取账号权限迁移到系统密钥链启动时读取内存短生命周期2日志记录请求头中的鉴权字段日志泄漏导致长期凭证暴露全局脱敏过滤器对 Authorization 字段做掩码处理3传递依赖中的漏洞被本地可调用路径触发攻击者可借由恶意文件执行任意代码升级上游依赖同时在调用边界做参数校验4证书校验存在绕过逻辑中间人攻击可窃取对话内容移除绕过代码启用严格验证模式5文件路径穿越恶意文档可读取任意本地文件路径规范化 前缀校验修复的顺序不要按“报告顺序”走而是按“攻击者最容易碰到的入口”走。密钥存储、网络校验、路径穿越这类问题优先修间接依赖的升级放在第二波因为需要额外的回归测试。5.2 修复验证改完不等于修完把代码改掉只是第一步验证修复是否有效才是关键。我总结了五步验证法第一步静态复查。修完的文件重新过一遍 Semgrep 规则确认告警消失。但静态规则只保证“模式”没有了不保证逻辑正确。第二步动态复现。对前一步发现的每个漏洞编写最小验证用例。比如路径穿越用临时目录里放一个../../etc/passwd试试修复后是否还会读取比如证书绕过重新挂上 mitmproxy 确认握手失败。第三步回归功能。安全修复最容易影响正常功能尤其是依赖升级。跑一遍核心流程配置用户 → 发起对话 → 切换模型 → 本地历史记录 → 导入导出。每一步都做人工核验重点看依赖升级后有没有出现“API 返回格式不兼容”的问题。第四步数据完整性校验。安全性变更后确认本地数据库能正常读写迁移到密钥链后重启应用两次以上确保密钥链解锁状态正常不会要求用户反复输入。第五步版本留痕。所有修复用一个独立的SECURITY.md记录归属和验证证据配合 git tag 和 release notes 形成可回溯的审计链。这样下次审计或者监管方问起来可以直接给出证据。验证阶段我顺手补了一个简单的持续审计脚本可以放进 CI#!/usr/bin/env bash set -euo pipefail # 1. 锁文件变更检测 git diff --exit-code package-lock.json || echo 锁文件发生变化需要复查依赖树 # 2. 密钥明文扫描 grep -rIE Bearer [A-Za-z0-9_-]{20,}|api[_-]?key.*.*[\][^\]{20,} --include*.js --include*.ts src/ || true # 3. 禁止 eval 调用 grep -rE eval\(|new Function src/ || true # 4. 依赖漏洞扫描失败仅告警不阻断 osv-scanner scan --lockfile package-lock.json || echo 发现漏洞请在发布前评估注意脚本里第四段的轻轻处理不要让扫描告警直接卡掉 CI否则第二天就会被同事冲突掉。治理的目标是让扫描结果“可见可追踪”而不是“失败就爆炸”。6. 审计中的踩坑实录与排查速查表6.1 三个很容易被忽略的坑第一个坑扫描器的“误报”让你错失真正的问题。npm audit 报告里有一条 high 漏洞来自某个图片处理库的传递依赖我差点直接忽略因为应用里几乎没直接调用它。但深挖之后发现应用在加载用户头像时确实调用了该库的一个缩略图接口而且可以通过构造特殊图片激活漏洞。扫描器给出的“是否受影响”结论不可全信必须回到代码里确认调用链。第二个坑修复依赖引发的“连坐效应”。升级一个 Electron 底层库后应用崩溃率瞬间上升。原因是新版本改了原生模块的 ABI 接口之前编译的.node扩展全部失效。解决方案是在 CI 里保留构建产物缓存升级前先全量重编译验证同时盯住postinstall脚本里是否有隐藏的二进制下载。第三个坑日志系统的“静默放大”。很多应用没有统一的日志脱敏层开发阶段为了调试爽直接console.log(data)。审计时我在某个错误处理分支里发现了完整的模型返回内容上下文里包含了请求体。解决这类问题不能只靠改一行日志而是配置一个全局拦截器对所有写日志的数据做字段级过滤黑名单包括authorization、apiKey、token、password、cookie。6.2 常见问题速查表现象可能原因排查命令/方式修复方向npm audit 报 high 但无法升级上游依赖锁定需连带升级npm why 包名检查依赖链升级间接依赖的上游包配置文件权限异常安装包默认 umask 宽松stat -c %a ~/.config/...对包含密钥的文件设置600抓包看不到请求内容客户端启用证书固定mitmproxy 证书未导入系统信任区若确为固定证书评估是否需要数据库文件加密后应用变慢SQLCipher 加密导致性能损耗对比加密前后的读写耗时日志采用字段级加密仅加密敏感列密钥链读取失败导致启动崩溃Linux 无密钥环守护进程echo $GNOME_KEYRING_CONTROL增加 fallback 机制显式提示用户日志文件持续膨胀缺少轮转和压缩策略du -sh ~/.local/share/应用名/logs/配置 logrotate 或者内置日志轮转SVG 图片加载执行脚本渲染层未隔离用浏览器加载恶意 SVG 样本测试CSP 禁止脚本或对 SVG 做白名单清洗6.3 最后分享一个有点“反直觉”的经验审计做到后期我发现一个问题安全修复做得越狠用户的“可用性”投诉越多。密钥链存取频繁弹窗、证书校验严格到自家更新服务器都连不上、日志脱敏导致排障看不到关键报错——这些都是典型的过度修复。安全不是把攻击者挡在外面而是在攻击者进不来和用户出不去之间找一个平衡点。我个人的做法是每次安全变更前写一份“用户影响清单”列出这个修复会让用户感知到什么。比如把 API Key 迁移到系统密钥链要明确在无桌面环境的 Linux 服务器上怎么 fallback否则远程部署的用户会直接卡在启动阶段。安全工程师交付的不应该是让产品变得更难用的一堆防线而是让攻击成本高到“不值得”同时让正常路径平滑得像什么都没发生。只有这样安全审计才真正落了地而不是一份漂亮却无人执行的报告。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑