安全审查不是上线前的一次体检,而是每次写代码时的习惯
很长一段时间,我对「安全审查」的理解是:功能做完了、快上线了,跑一遍检查,看看有没有明显的问题。这种做法的本质是把安全当成一道「出口质检」,而不是生产过程中的工序。
直到我开始整理一套可复用的安全检查清单,才意识到:大多数安全漏洞在写代码的那一刻就已经埋下了,上线前再查,代价远比写代码时就防住要高得多。
安全问题有「类型」,每种类型有对应触发时机
把安全问题列出来,大致分这几类:
- 密钥和凭据:API key、数据库密码写进代码里——只要碰过环境变量配置就可能触发
- 输入校验:用户提交的数据没有经过 schema 验证直接进了数据库——只要写了接收用户输入的逻辑就要考虑
- SQL 注入:字符串拼接构造查询——只要写了数据库查询就要考虑
- XSS:把用户内容
dangerouslySetInnerHTML出去、没有清洗——只要渲染用户内容就要考虑 - 认证 / 鉴权:token 存了
localStorage、操作前没有校验角色权限——只要写了登录态或敏感接口就要考虑 - 速率限制:接口没有限频,搜索、登录、发验证码等高成本操作可以被无限调用——只要新开了 API endpoint 就要考虑
把这些问题和「触发时机」对齐之后,逻辑就清晰了:不是「快上线了再查」,而是「一旦我在写这类代码,就同时把对应检查做掉」。
不需要每次都过完整 checklist。关键是识别「这段代码属于哪类场景」,只针对那类场景的检查项过一遍,成本极低,效果远好过事后补救。
一个可操作的清单该长什么样
我整理下来,每类场景对应的核心约束大概是:
密钥管理:任何凭据只进环境变量,从不硬编码;.env.local 进 .gitignore;启动时 fail-fast 校验缺失的必要变量。
输入校验:用 Zod 或等价的 schema 库验证所有外部输入,包括 API body、query string、文件上传的类型和大小;白名单校验优于黑名单。
数据库查询:参数化查询,永远不拼 SQL;使用 ORM 时确认 ORM 的动态条件构造方式是否安全。
认证与 token:JWT 和 session token 存 httpOnly cookie,不存 localStorage;每个敏感操作前都显式检查调用方角色,不依赖「前端没有渲染这个按钮」作为鉴权。
输出到浏览器:渲染用户生成内容时用 DOMPurify 清洗,配 CSP 头缩小注入的攻击面。
速率限制:每个公开 API 都加限频;登录、搜索、发码这类高成本操作要用更严格的策略。
错误处理和日志:报错只向用户返回通用文案,详细错误只写服务端日志;日志里不记录密码、token、完整卡号。
上线前 checklist 的意义——确认,而非补救
整理好这套按场景触发的习惯之后,上线前的 checklist 作用就变了:它不再是「发现问题」,而是「确认上面这些习惯都被执行到了」。
两者看起来都是过一遍 checklist,心理预期完全不同。「发现问题」模式下,你预期会找到问题,于是最后关头的压力会让你倾向于「先上线再说」;「确认习惯」模式下,每一条你都能说出「这个在写代码时已经做了,体现在某某地方」,上线前 review 才是真的有底。
依赖管理是个容易遗漏的例外——库的漏洞不是你写代码引入的,所以确实需要在上线前专门跑 npm audit,并把 Dependabot 之类的自动化扫描挂进 CI。
一句话心法
安全不是出口质检,是每类操作在落笔时就要内建的约束——识别场景、对应检查项、当场做掉。