首页 / Node.js 教程 / 安全最佳实践

Node.js 教程

安全最佳实践

本教程共 76 篇 · 第 59 篇 · 更新于 2026-07-25 · 约 8 分钟阅读

Node.js安全依赖审计权限模型最佳实践

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.headersTimeoutserver.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 服务才算真正经得起线上考验。