安全最佳实践
本教程共 76 篇 · 第 59 篇 · 更新于 2026-07-25 · 约 8 分钟阅读
59. 安全最佳实践
本节目标:- 怎么用
npm audit和锁文件管住依赖供应链 - 最小权限原则:别用 root 跑、文件权限收紧 - Node.js 权限模型--permission怎么给进程「上锁」 - 密钥管理、传输安全、日志脱敏等日常习惯
前面几章讲的都是「某个具体攻击怎么挡」。这一章往上拔一层,聊工程层面的安全习惯:你的依赖干不干净、进程跑在什么权限下、密钥放哪、上线前检查什么。这些事不写在业务代码里,但决定了你家底会不会一夜被端。
Node.js 官方安全文档把这类风险叫「应用层威胁」——Node 本身没漏洞,是你运行环境里的依赖、配置和权限把门敞开了。下面几条,按「性价比从高到低」排。
依赖安全:最大的风险面
Node 项目动辄几百个间接依赖,每一个都是别人写的代码,且默认拥有你进程的完整权限(能读文件、能联网)。供应链攻击是现在最凶的一类:有人故意发个名字很像的包(typosquatting),或者在热门包里塞个 postinstall 脚本偷环境变量。
1. 跑审计,别装了就不管。
npm audit # 检查已知漏洞
npm audit fix # 能自动修的尽量修
npm audit --production --audit-level=high # 只看生产依赖的高危项
把审计加进 CI,每次提交都扫一遍。还可以接 Snyk、Socket 这类工具做静态分析,能发现「这个包居然要联网/要读写文件」这种反常行为。
2. 锁文件 + npm ci。
package-lock.json 把每一层依赖都钉死。上线部署用 npm ci 而不是 npm install——npm ci 严格按锁文件装,锁文件和 package.json 对不上就直接报错,不会偷偷改依赖。
Warning安装时记得
npm config set ignore-scripts true(或临时npm install --ignore-scripts),能挡掉绝大多数「包安装时就执行恶意脚本」的攻击。当然这会让一些需要编译的原生模块装不上,需要的话只对可信包放行。
3. 给新包设个「冷静期」。
npm v11.10+ 支持 --min-release-age(单位是天),要求包至少发布满 N 天才能装。绝大多数供应链攻击的恶意包几小时内就被下架,设个 min-release-age=1 就能挡掉大部分短命攻击:
npm install some-pkg --min-release-age=1
4. 当心依赖混淆(dependency confusion)。
很多公司内部有自己的私有包(比如 @mycorp/utils),但如果 package.json 里只写了包名没锁源,攻击者可能把一个同名包发到公共 npm 上,并且版本号比你私有的更高。npm 默认会去公共源拉「最新版」,结果把公网上的恶意包拉进来了。防御是配置 .npmrc 把私有作用域(scope)指向内部源,并且所有依赖都走锁文件。
5. 别用根用户跑、也别用过时运行时。
Node.js 本身隔三差五修安全漏洞,保持运行时在受支持的 LTS 版本上是底线。别在已经 EOL(停止维护)的旧版本上跑生产,那是给自己埋雷。
最小权限:进程别穿「皇帝的新衣」
1. 别用 root 跑 Node。
很多新手图省事 sudo node app.js,结果应用一被攻破,攻击者直接拿到系统最高权限。建个普通用户(比如 nodeapp)专门跑服务,文件 owner 也归它:
sudo useradd -m nodeapp
sudo chown -R nodeapp:nodeapp /var/www/myapp
sudo -u nodeapp node /var/www/myapp/server.js
2. 文件权限收紧。
.env 里是密钥,必须 600(仅 owner 可读写);代码文件 644;目录 755。别让同机其他用户能读到你的秘钥:
chmod 600 /var/www/myapp/.env
chmod 644 /var/www/myapp/*.js
3. 密钥放环境变量,别进仓库。
用 dotenv 在开发时读 .env,但 .env 一定写进 .gitignore。生产环境直接用平台的环境变量注入(Docker/K8s 的 secret),不落盘更好。
// 开发时加载 .env,生产环境由平台注入,不读文件
if (process.env.NODE_ENV !== 'production') {
(await import('dotenv')).config();
}
const dbPassword = process.env.DB_PASSWORD; // 永远从环境变量取
权限模型:给进程「上锁」
Node.js 从 v20 引入、到 v24.2.0 稳定的权限模型(Permission Model),能让你精确声明「这个进程能干什么」。用 --permission 开启后,文件系统、网络、子进程等默认全部禁止,你只把需要的那几样显式放开。
# 只允许读应用目录、写日志目录、访问网络
node --permission \
--allow-fs-read=/var/www/myapp \
--allow-fs-write=/var/www/myapp/logs \
--allow-net \
app.js
可用的几个开关:
| 参数 | 作用 |
|---|---|
--allow-fs-read=路径 | 允许读指定目录/文件(* 表示全部) |
--allow-fs-write=路径 | 允许写指定目录/文件 |
--allow-net | 允许网络访问(v24.12+,可限制到具体域名:端口) |
--allow-child-process | 允许创建子进程(还记得第 33 章吗) |
--allow-worker | 允许创建 worker 线程 |
--allow-inspector | 允许调试协议(生产环境别开) |
Warning权限模型不会自动继承到子进程和 worker 线程。父进程开了
--permission,子进程默认还是「零权限」,得单独给它配。而且它挡的是node:fs这类 API,挡不住你用child_process调的外部命令绕过——所以子进程该不该放行,要想清楚。
在 PM2 里用的话,把参数塞进 node_args:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'api',
script: './server.js',
node_args: '--permission --allow-fs-read=/var/www/myapp --allow-fs-write=/var/www/myapp/logs --allow-net',
instances: 'max',
exec_mode: 'cluster',
}],
};
传输与响应:别裸奔
1. 生产环境强制 HTTPS。
用反向代理(Nginx)终结 TLS 是最常见做法,Node 只跑内网 HTTP。也可以在 Node 里直接用 https 模块挂证书。关键是在 helmet 里开 HSTS(上一章讲过),告诉浏览器「之后只走 HTTPS」。
2. 安全响应头别漏。
helmet() 默认那一套(CSP、X-Content-Type-Options、X-Frame-Options 等)直接开,成本几乎为零,收益很高。
3. 限制请求体大小。
攻击者可以发个几 GB 的 body 把你内存撑爆。用 express.json({ limit: '1mb' }) 之类限制住,再配合第 58 章的速率限制。
4. 设好超时。
server.headersTimeout、server.requestTimeout 这些(第 26 章提过)要配,防 Slowloris 这类慢速耗尽连接的攻击。前置反向代理也能帮忙丢掉卡住的慢连接。
运行环境加固
1. 冻结内置对象。
--frozen-intrinsics 会把 Array.prototype 这类内置对象递归冻结,防止被「猴子补丁(monkey patching)」篡改。它还是实验特性,生产慎用,但值得了解。
2. 禁掉原型污染入口。
--disable-proto=throw 让访问 __proto__ 直接抛错,从运行时层面挡住第 58 章讲的那种原型污染。
3. 别在生产开调试器。
--inspect 开的调试端口一旦暴露,配合 DNS 重绑定的攻击能让你本地进程被远程控制。生产环境打死不开,本地调试完就关。
日志与监控
- 日志别打敏感信息:密码、token、身份证号这些绝对不能进日志。打认证日志记「谁、从哪个 IP、成功还是失败」就够了,别顺手把密码也写出来。
- 结构化日志:用 pino / winston(第 65 章细讲)输出 JSON,方便后续检索和告警。
- 及时打补丁:关注依赖的安全公告,定期
npm audit,别让已知漏洞在生产躺几个月。
一份上线前的安全清单
-
npm audit无高危漏洞,npm ci部署 - 密钥全部走环境变量 / secret 管理,
.env不进仓库 - 进程以非 root 普通用户运行,文件权限
600/644配好 - 必要时开启
--permission权限模型,最小化放行 -
helmet()安全头已开,生产走 HTTPS + HSTS - 所有外部输入都经过校验(express-validator / zod)
- 数据库查询全部参数化,调用系统命令用
spawn数组参数 - Cookie 配齐 httpOnly / secure / sameSite
- 速率限制已加,请求体大小和超时已设
- 日志不含敏感字段,监控告警已接
安全不是「写完代码再补一层」的事,而是从依赖、权限到每一行输入处理都得带着的意识。把这份清单养成习惯,你的 Node.js 服务才算真正经得起线上考验。