工程手记

上线前,我把整个仓库审了一遍

一次上线前的静态安全审查:四个并行只读代理加人工逐条复核,产出 20 条分级风险与三档修复路线图。本文记录方法,不复述条目。

STRUCTURE ── 篇幅剖面9 节 · 点击跳转

上线前做了一次全量静态审查。

本文只讲方法和结论分布。审查报告本身不入库,也不在这里复述——那份报告带内网拓扑、密钥存放位置和尚未修复问题的文件行号证据,公开它就等于公开一份攻击者的地图。

一、审查怎么进行

三个部分:

  1. 人工列清单。 先按「一个系统可能从哪些地方被打穿」列出检查面,而不是按目录扫。
  2. 四个并行只读探查代理。 每个代理负责一组检查面,只读,不允许改任何文件。
  3. 关键证据人工逐条复核。 代理解释现象可以,但「这条算不算风险、算多重」由人定。

第三步是必要的。代理很容易把「写法不理想」报成「高危」,也很容易漏掉需要跨文件才能看出的问题。它适合做的是穷举和取证,不适合定级。

审查方式记为:静态审查(人工 + 4 个并行只读探查代理)+ 关键证据人工逐条复核。

二、只读约束的作用

让探查代理只能读,有两个好处。

一是没有副作用。审查过程中仓库保持原样,报告的结论可以对着同一个 commit 复现。

二是省掉了「代理顺手改一下」的干扰。审查和修改混在一起,会出现「报告说有问题的地方已经被改过」的状态,后续复核就无法对着证据走。

修改是审查之后单独一轮的事。

三、20 条与四个级别

产出 20 条风险,编号 R01 到 R20。每一条有:位置、触发条件、影响、修复优先级。

按修复优先级分四档:

优先级条数排期
P02本周内
P16两周内
P25一个月内
P37一个月内

另外有一个独立的「严重级别」列,按严重 / 高 / 中 / 低分:1 / 4 / 7 / 8。两列不对应是有意的——「有多严重」和「该多快修」是两件事。一条严重但需要停机才能修的问题,排期上反而不能放在本周。

整体评级:

四、结论里最值得记的一句

未发现 SQL 注入、越权与 IDOR、存储型 XSS 等直接可利用的应用漏洞。

这一句和「整体评级高」摆在一起看,是这个月里最值得记的一条工程结论。

风险分布和直觉不一致。业务代码那一侧没查出可直接利用的漏洞——那部分每天在改、每天在跑、每天有人看。问题更多集中在业务代码之外:构建、部署、配置、凭据流转、依赖引入、运行时加固。

这些东西的共同点是平时不跑。部署脚本一天执行几次,Dockerfile 改一次放很久,集群配置是照着文档抄的。没有日常反馈的东西,问题会一直留在那里,而且不会有人因为「它今天没出事」而去核对它。

五、修复的排期方式

路线图按三档排:P0 本周、P1 两周内、P2/P3 一个月内。

排期依据不是严重级别,是两件事:能不能立即修,和修它会不会动到线上。一条严重但需要停机或改基础设施配置的问题,得等一个发布窗口;一条中等但改动只在一处代码里的问题,可以马上做。P2/P3 是加固项,攒到下一个版本一起走。

这样做的好处是排期可执行。「两个月内修完 20 条」听起来完整,实际会卡在第一条需要停机的问题上。

六、报告为什么不入库

仓库的忽略规则里专门有一条:

# 安全审查报告:含内网拓扑、密钥存放路径与未修复漏洞的 file:line 证据,绝不入库

三条理由,最后一条最关键:

  • 内网拓扑
  • 密钥存放位置
  • 未修复漏洞的精确定位

前两条是常识。第三条是这类文档的特例:一份「待修复清单」在修完之前,本身就是攻击说明书。而它又必须有精确定位,否则修不动。

所以这份文档的流转方式和代码相反。代码进仓库、留历史;它不进仓库,修完一条删一条,修完的结论改记到提交信息里。

七、审查之后,同一天修的东西

审查和修复放在同一天做,这是这次的一个做法。

当天修的一批,主题集中在一类:不报错的失败。队列投递端断线后不重连、依赖返回的包装形状读错导致判定恒为假、入队不带权重插到最前面、参数格式不对被上游全部拒收、重试链被自己的去重压短、管理操作的幂等键缺失、用户可控的文件名直接进对象存储键、唯一漏掉来源校验的 IPC handler、并发删除后返回假成功、过期验证码被复活、临时文件不清理。

这些已经有单独一篇记录,这里只提它们的共性:都不抛异常

审查报告里那些「不进仓库」的条目和这些「当天修完」的条目,其实是同一批检查过出来的。区别只在于前者需要动基础设施、要等窗口,后者可以立刻改。

八、静态审查的边界

这一轮做的是静态审查,能覆盖和不能覆盖的东西都比较明确。

能覆盖:

  • 配置与依赖:镜像标签、依赖来源、加密参数、权限配置。
  • 错误处理:异常的吞掉与上抛、失败路径有没有留痕。
  • 凭据流转:什么地方读、什么地方写、会不会进构建产物。
  • 部署清单:运行用户、资源限制、网络策略、滚动更新参数。
  • 代码层面的模式:路径拼接、命令拼接、输入校验的缺失。

覆盖不到:

  • 运行时竞态。两个请求同时到、并发删除、租约过期,这些要在真实并发下才暴露。
  • 真实的流量特征。哪些入口真的被外部打到、参数实际长什么样,静态看只能推测。
  • 业务逻辑漏洞。越权取数据这类问题需要理解「这个人该不该看到这条」,而业务意图不在代码里。
  • 组合链。单看每一步都合规,串起来才是问题——这类要动态测试或者真实对抗。

第三类是最容易被静态审查漏掉的:代码里所有校验都写了,但校验的规则和业务规则不一致。

所以静态审查的产出不是「系统安全了」,而是「在它能看见的那一面里,还剩哪些要做」。

九、一次审查真正留下的东西

审完当天修掉的那批,留下的是代码。报告里那些没修的,留下的是三样东西:

  1. 一份带精确定位和优先级的清单,不进仓库,逐条消掉。
  2. 一个判断:风险集中的地方和日常注意力集中的地方不一样。业务代码天天改,反而干净;构建部署配置平时没人动。
  3. 一次复核的习惯:审查的结论要能对着一个 commit 复现,所以探查只读、修改单独一轮。

第 2 条比第 1 条重要。清单会修完,但「平时不跑的东西没人核对」这件事不会自己消失。

星野的头像

星野 XINGYE

全栈工程师。这里记录 82 篇复盘:24 份故障档案、OTA、架构演进与工作流。