AI 把软件生态打成筛子之后:我们怎么守住客户的名单、录音和密钥

这一周,做技术的人大概都过得很紧张。
Railway 公告客户的加密环境变量可能被读走,数据库密码、API Key、镜像仓库凭据都在里面;LiteLLM 的 PyPI 包和 Docker 镜像被塞了木马,GitHub 上的源码看着干干净净,装下来的却是带毒的;OpenCode 后台被入侵,有用户一夜之间多出一千多美元的模型调用账单;印度 CA 机构 eMudhra 有张中间证书,私钥就是数字 4。
还有两件事,很多人忽略了。一件是攻击者用智能体渗透工具加大模型,在很短的时间里接连打进了几家韩国金融机构。另一件是 PocketOS 的开发者让 AI 编程 Agent 处理测试环境的凭据问题,Agent 自己找到一个权限过大的 Token,9 秒删掉了生产数据库,连同备份。
前一件说的是:AI 让攻击变便宜了。后一件说的是:没有攻击者,AI 自己也能闯祸。
这两件事都跟我们有关。作为一家做 AI 营销客服数字员工的公司,我们手里有客户托付的敏感数据,同时每天有大量数字员工拿着真实权限在干活。所以借这次事件,我们把这半年多在安全上做的改进、踩过的坑和还没做完的部分,简单讲一讲。
一、先盘点:我们的资产是什么
按文章的说法,第一步是盘点资产清单。我们的数字资产有这几类东西:
- 客户资料:客户的销售资产。
- 通话内容:录音、转写、AI 生成的摘要和意向判断。客户的报价、病情、家庭情况,都可能在通话里说出来。
- 话术:一家企业打磨了几个月的话术和打法,就是它的商业秘密。
- 密钥:客户配置的大模型供应商 Key、线路的 SIP 账号、推送用的 Webhook 密钥,每一把都能直接花钱。
- 会动手的 AI:数字员工能建项目、导线索、发起外呼、改话术。
前四类是"被偷了就完了",第五类是"自己乱动也完了"。下面分开讲。
二、我们也翻过车
安全文章里最没用的话,是"我们非常重视安全"。所以先说几件我们自查发现的问题,和针对性的改进。
SSH 配置写的是禁止密码登录,实际却允许。 4 月份排查时发现,服务器主配置里明明写着禁止密码登录,但云厂商初始化脚本在 sshd_config.d/ 下放了一个文件把它改了回去,而那个目录在主配置里引用得更早,先读到的值生效。几台机器合计被暴力破解了几十万次。我们加了一个排序更靠前的加固配置,让云厂商脚本以后再改也覆盖不掉;装了 fail2ban;之后排查 SSH 只看 sshd -T 输出的实际生效值,不再看配置文件里写了什么。
几个接口没有鉴权。 7 月中旬自查时发现,策略优化面板有 4 个接口不检查登录。偏偏首页公开了一个演示用数字员工的 ID,理论上不登录就能改它的配置,还能按通话 ID 读到别人的通话内容。当天补上了登录校验和归属校验,并且改成从数字员工反查话术 ID,不再相信前端传上来的值。
内部接口暴露在公网上。 数字员工调用工具的内部接口原本只打算给内网用,实际上公网也能访问到,而且不需要鉴权。现在必须带上服务端密钥才能调用,对外开放的那一份按 Key 单独限流。
日志把密码写了出来。 服务器的容器引擎开着调试模式,每创建一个容器,就把完整的环境变量写进系统日志,数据库和缓存的密码都在里面。我们紧急关掉了线上调试模式,清理了历史日志。
公开仓库差点被当成投毒。 我们开源的 Agent 工具包里,依赖锁文件指向了一个已经下线的私有 npm 源,被威胁情报平台判定为疑似供应链攻击。没有恶意,但"锁文件指向一个陌生地址"本身就是投毒的典型特征。我们用官方源重新生成了锁文件,并在项目里固定了 registry。
这些坑有个共同点:它们都不是什么高深的漏洞,就是某个环节默认值不对,或者某个口子没人想起来去关。 跟 eMudhra 那个私钥"4"差不多。真正的威胁很少需要破解密码学,攻击者找的是最草率的那个环节。
三、数据:默认加密,谁也看不到的明文
Railway 这次事故里最扎心的一句话是:"获取了部分变量解密后的内容"。加了密,最后还是被解密了。
所以我们给自己定的目标是:就算数据库整个被拖走,拖走的也只是一堆密文。
从 5 月底开始,我们分四批给数据库上了字段级加密,覆盖了上百张表:
- 手机号用确定性加密。同一个号码每次加密的结果一样,所以去重、按号码查找这些功能照常能用;单独保留后四位,用来做模糊搜索。
- 通话内容、录音地址、话术配置、推送内容、工单、微信消息等大字段随机加密,同样的内容每次加密结果都不相同,先压缩再加密,存储空间反而比原来小。
- 供应商密钥、线路凭证、API 调用日志一并加密。
- 有些 JSON 配置要靠里面的某个字段做查询,比如"这个群关联哪个项目"。这些字段单独预留明文,其余部分整体加密。
代价也不小。数据库里的模糊搜索基本不能用了,项目名、话术名、线索包名的搜索,都改成了取出来在代码里匹配;所有手写 SQL 都要手动加解密。为了不影响白天的业务,几百万行的历史数据是分好几个晚上回填完的。
我们觉得这个代价是值得的。数据库泄露不是会不会的问题,而是发生时损失多大的问题。
四、密钥:不放在服务器上
LiteLLM 和 Railway 两件事说的其实是同一个道理:密钥放在哪,攻击者就会去哪。
我们现在的做法是:
- 生产环境变量加密后进代码仓库,解密私钥不在服务器上。部署时在本地解密,通过 SSH 推到服务器的内存盘里,容器启动后立刻删除。服务器的硬盘上不会有明文配置,也没有解密私钥。
- 后端产物打包压缩后再上线,source map 只开发本地保留,线上报错在本地反查源码位置。服务器上也就看不到可直接阅读的业务源码。
- 登录生产机单独的 SSH 密钥,不共用。审计日志里看到哪个密钥指纹登录,就知道是否有攻击者。
- 敏感接口的设计原则是列表不返回密钥,最小公约结果。只有打开详情、编辑页面才拿得到明文,而且每次查看都会记进审计日志。
虽然这样也不一定会完全防止攻击者攻破,仍然可以读到运行中容器的环境变量。但第三节的数据加密是最后一道防线:就算密钥层失守,数据库里的数据也不会直接变成明文。
五、接口和权限:每一次请求都需要鉴权
第二节那几个坑说明,漏洞最常见的形态不是缓冲区溢出,而是某个接口忘了问一句"你是谁、这是不是你的东西"。
这几个月我们把这件事作为常态化工作来做:
- 越权专项排查。 统计接口、并发接口、成员列表、企业切换、线路绑定等,逐个检查"能不能拿别人的 ID 读到别人的数据"。查出来的问题都补上了项目和企业级的归属校验。
- 只给内部用的接口,就只能内部调用。 企业微信管理、短信测试发送、对话上传、训练重跑这类接口,统一改成只接受内部调用。
- 限流。 对外 API 按 Key 限流,同一条线索单独限频,防止一把泄露的 Key 被拿去刷量;公开的短信验证码接口加了频控。
- 登录风控。 同一 IP 连续失败会被封禁,同一手机号连续失败会被锁定,发短信按号码和 IP 两个维度限频。获取真实 IP 时从
X-Forwarded-For的右侧往左取,跳过我们自己的代理,最左边那段客户端可以随便伪造。 - 按角色脱敏。 普通账号看到的手机号是打码的,完整号码只给有权限的角色。
- 所有写操作自动审计。 我们把原来散落在 90 多处的手写日志,改成了统一的审计中间件:任何增删改都会自动记录操作人、IP 和参数(参数先脱敏);导出数据、查看密钥这类敏感读操作也会单独记录。
- 补上了 HSTS、禁止被嵌套(X-Frame-Options)等安全响应头。
六、AI 员工:给它干活的权限,不给它闯祸的权限
PocketOS 件事,我们复查了好几遍。
Agent 本身并没有恶意。它想解决一个凭据问题,找到了一把权限过大的 Token,在没有确认目标环境的情况下执行了删除,9 秒钟,库和备份都没了。问题不在 AI 不够聪明,而在于它能直接执行不可逆的操作,中间没有任何人确认。
我们的数字员工每天也在做真实的操作。比如微信管家花花,客户在微信里跟它说一句话,它就能建项目、导线索、发起外呼、改话术。下面是我们给它定的规矩:
1. 它的权限,就是你的权限。 花花替谁办事,就按谁在系统里的权限来,网页上点不了的按钮,它也不能替你点。私聊里如果认不出对方是谁,它就既不能查也不能改;绑定账号必须在服务端校验短信验证码,不能靠对方在聊天里自称是谁。在群里,它只能操作这个群关联的项目。对外开放的 API 也是一样,用 Key 调用时,操作人就是创建这把 Key 的人,Key 的权限范围只能比这个人小,不能比他大。
2. 危险操作先出方案,确认之后才执行。 比如"抓一批线索然后自动拨打"这种会花钱、会打扰别人的操作,花花会先把计划存到服务端,你确认之后才执行,不会因为对话里一句模棱两可的话就开拨。
3. 只改局部,每次改动都留版本。 我们在这里吃过亏:有一次花花帮客户改话术,本该只改一段,结果把整篇话术替换掉了,八千多字变成了四百多字,而且当时没有留版本。现在改话术只能做局部修改,每次修改都存一个版本,随时可以回退。
4. 改动先评测,再上线。 训练中心自动应用的每一条话术建议,都要先用评测用例测评,分数不下降才可放行;同一个配置项 24 小时内不会被反复修改,每天自动应用的条数也有上限。
5. 线上有刹车。 人工坐席通话时,如果命中高风险话术(违规承诺、诱导之类),系统会直接挂断,累计命中多次会自动暂停整个项目。
6. 写代码的 AI,也不碰生产开关。 我们的研发数字员工"阿修"会读工单、在客户原声上回放复现问题、写修复,但代码只能提交到单独的工单分支,技术同意之后才会应用到线上。我们自己开发也大量用 AI 辅助,规则一样:发布 npm 包要通过人的二次验证,部署语音 Agent 由人来操作,部署完要进容器核对文件和启动时间,不能只看脚本说"部署完成"。
总结成一句话:AI 负责提方案、干苦活,不可逆的那一步留给人。 我们在训练中心那篇文章里讲过类似的话:"这不是把人踢出回路的自我改进,而是有 SOP、有考核、有刹车的自我改进。"安全也是一样。
七、供应链:只信自己验证过的产物
LiteLLM 的教训是:审查源码不等于审查了你实际运行的东西。
- 内部共享包走自建的私有 npm 源,依赖用锁文件冻结安装,锁文件里的校验和对不上,构建直接失败,不会悄悄换一个版本装进去。
- 线上构建在我们自己的机器上完成,不从公共镜像拉取未经确认的"latest"。语音网关使用的是打过补丁的镜像,明确规定不可拉官方最新镜像覆盖。
- 每次部署后进容器核对实际运行的文件,防止缓存命中导致"部署了个寂寞"。这条最早是为了防假部署,用来验证构建产物也一样合适。
八、还没做完的
最后说说还没做到的,这部分也想写清楚:
- 缓存和消息队列里的数据加密尚未完善。 数据库已经覆盖了,但部分缓存(比如数字员工的配置缓存)和队列里的任务数据还是明文。
- 依赖漏洞扫描和密钥检测还没有强制放进构建流程。 目前是我们人工跟进安全公告,比如这周的 Node.js、Nginx、Linux 内核更新都要逐个核对。下一步要把扫描做成构建流程里的硬性关卡,不过就不让上线。
- 密钥轮换还不够勤。 加密密钥和各类服务密钥目前没有按固定周期轮换,这是接下来会立刻改进的。
结语
文章最后问了一个问题:如果 AI 几分钟就能找到一个潜伏多年的漏洞,而企业要几周才能修完,攻防规则会变成什么样?
我们的回答比较朴素:修复也得交给 AI 一起来做。 这几个月的越权排查、加密改造、几十张表的回填,大部分代码是我们和 AI 一起写的。攻击者在用 AI,防守方也必须用。
什么能做、什么不能做,由人来决定;删不掉、不可撤回的步骤,也必须由人来完成。