Trivy 是 aquasecurity 团队开源的一款综合安全扫描工具,它最大的特点是能够对容器镜像做逐层分析,把镜像里安装的系统包、语言依赖、配置文件甚至密钥信息全部翻出来检查一遍。对于包含多层构建的镜像来说,这一点尤其重要——很多漏洞其实藏在某个中间层里,即使最终镜像看起来很干净,历史层中的问题依然可能被利用。本文结合一个多层构建的 Node.js 网络服务镜像,完整演示 Trivy 的扫描流程和结果分析方法。

Trivy 的工作原理与安装
Trivy 扫描镜像时会先解析镜像的 manifest 和层信息,把每一层的 tar 文件解出来,读取里面的包管理器数据库(比如 Debian 系的 dpkg 状态文件、RedHat 系的 rpm 数据库),再结合语言生态的锁文件(package-lock.json、yarn.lock 等)确定镜像内实际安装的软件版本。拿到版本清单后,它会与本地漏洞库比对,漏洞库可以通过 trivy image --download-db-only 预先下载,也支持离线环境使用。
安装方式很多,以常见的几种为例:macOS 用 Homebrew 执行 brew install trivy 即可;Linux 推荐用官方安装脚本,或者直接下载 deb/rpm 包;如果不想污染本机环境,也可以用容器方式运行。安装完成后执行 trivy --version 确认可用。
# Linux 安装脚本方式 curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin # 或者使用官方容器镜像扫描(无需本地安装) docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:latest image myapp:latest
扫描多层镜像中的漏洞
多层镜像(multi-stage build)虽然能有效减小最终镜像体积,但也带来一个认知误区:不少人以为多阶段构建天然安全,因为构建工具不会进入最终镜像。这个判断大体成立,但前提是你的 COPY 语句没有把构建阶段的产物之外的东西带进来,比如整个 node_modules 目录或虚拟环境。Trivy 的逐层分析正好能验证这一点,它会明确告诉你某个漏洞来自哪一层、由哪条指令引入。
先构建一个示例镜像,然后执行最基础的扫描命令。下面这个 Dockerfile 就是一个典型的两层构建的 Web 服务镜像:
# ---- 构建层 ---- FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build # ---- 运行层 ---- FROM node:18-slim WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules EXPOSE 3000 CMD ["node", "dist/server.js"]
构建后执行扫描:trivy image myapp:latest。默认输出会按严重程度(CRITICAL、HIGH、MEDIUM、LOW、UNKNOWN)分组列出漏洞,包括 CVE 编号、受影响包、当前版本、修复版本等信息。实际工程中建议直接过滤严重级别,避免低危噪音淹没关键问题:
# 只显示高危及以上漏洞,输出更聚焦 trivy image --severity HIGH,CRITICAL myapp:latest # 输出 JSON 格式,方便接入自动化处理 trivy image --format json --output report.json myapp:latest # 针对特定组件单独检查,例如只关心 openssl 相关漏洞 trivy image --ignore-unfixed myapp:latest | grep -i openssl
需要注意,node:18-slim 这类基础镜像自带的 openssl、curl 等网络相关库是漏洞高发区。如果扫描发现基础镜像本身问题很多,优先考虑升级基础镜像版本,而不是逐个修补应用依赖,这样一次性收益最大。
发现网络服务相关的配置错误与敏感信息泄露
除了 CVE 漏洞,Trivy 还能扫描镜像内的配置问题和泄露的密钥。网络服务镜像里最常见的几类问题包括:配置文件中硬编码的数据库密码和 API Token、.env 文件被意外打包进镜像、私钥文件遗留在某个历史层中。使用 --scanners secret,config 参数可以开启这两类检查:
# 同时扫描漏洞、密钥泄露和配置错误 trivy image --scanners vuln,secret,config myapp:latest # 只检查密钥泄露,输出泄露位置和内容片段 trivy image --scanners secret myapp:latest
密钥扫描基于内置规则库,能识别 AWS Access Key、GitHub Token、私钥 PEM 块等常见格式。如果某个误报需要跳过,可以编写 .trivyignore.yaml 自定义豁免规则,比如排除测试用的假密钥:
# .trivyignore.yaml
secrets:
- id: "aws-access-key-id"
path: "*/test/fixtures/*"
reason: "测试用例中的假密钥,不会用于生产环境"另外一个容易被忽略的点是镜像内的应用配置。Trivy 的 config 扫描器内置了基于策略的检查规则,可以识别出 SSH 配置过于宽松、运行目录权限异常等问题。虽然它对应用自身的业务配置覆盖有限,但结合自定义策略(用 Rego 语言编写)可以扩展出针对你自家服务的检查规则,例如禁止配置文件中出现明文数据库连接串。
将扫描集成到 CI 流水线并持续治理
一次性扫描的价值有限,镜像安全的关键在于持续化。Trivy 提供了 --exit-code 参数,让扫描结果直接影响流水线的成败,配合 GitHub Actions、GitLab CI 都很方便。下面是一个典型的 GitLab CI 片段,构建完成后立即扫描,发现高危漏洞且存在修复版本的直接阻断发布:
# .gitlab-ci.yml 片段
stages:
- build
- scan
container_scan:
stage: scan
image: aquasec/trivy:latest
variables:
TRIVY_NO_PROGRESS: "true"
script:
- trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
allow_failure: false对于历史遗留镜像量大、短期内无法全部修复的场景,建议采用分级策略:CRITICAL 且有修复版本的立即处理;HIGH 级别安排到常规迭代;无法修复的(上游尚未发布补丁)记录在案并通过 --ignore-unfixed 过滤,避免每次扫描都重复告警导致团队对扫描结果麻木。
最后提醒两点实践细节。一是 Trivy 的漏洞库要保持更新,长期运行的自托管环境要配置定时 trivy image --download-db-only 任务,否则扫描结果会严重滞后。二是修复优先级不要只看严重程度标签,还要结合服务是否对外暴露:一个暴露在公网的 API 网关镜像里的中危漏洞,往往比内网工具镜像里的高危漏洞更值得优先处理。把 Trivy 的扫描结果和网络拓扑信息放在一起评估,才能真正把风险控制在合理范围内。