vLLM的--api-key藏隐患:用白名单代理锁死未授权端点

vLLM 的 --api-key 只保护部分端点,需用反向代理、回环绑定和版本更新构建纵深防御。
vLLM 的 --api-key 参数只对 /v1、/v2、/inference、/cohere 前缀的端点生效,/invocations、/tokenize、/pause、/update_weights 等敏感路由完全无认证。加之 vllm serve 默认监听所有接口,任何能访问端口的人都能消耗 GPU 或中断他人请求。文章给出官方推荐的分层方案:先用 --host 127.0.0.1 将服务绑定回环、用 VLLM_API_KEY 环境变量存放密钥,再用 Caddy 或 nginx 反向代理只放行应用实际调用的端点,其余返回 404。同时强调验证每一层防护、注意节点间通信属于独立信任域需依赖网络隔离,且代理无法替代版本更新,0.31.0 已修复 30 余个安全公告中的问题。
如果你以为执行 vllm serve --api-key ... 后,服务器就自动变成了私有状态,vLLM 官方安全指南会给你泼一盆冷水。指南明确指出,API key 仅对 /v1、/v2、/inference 和 /cohere 这几个路径前缀下的端点生效,而“同一 HTTP 服务器上暴露的许多其他敏感端点完全没有任何认证强制”。官方甚至直接警告:“不要完全依赖 --api-key 来保护 vLLM 的访问安全。”这意味着,一个看似加了锁的服务,实际上仍有大量后门敞开。

未受保护的端点:比想象中更危险
vLLM 安全指南中列出了无需 API key 即可访问的路由,这些端点并非无关紧要的边缘功能。
/invocations:提供与/v1完全相同的推理能力。攻击者可以绕过认证直接消耗你的 GPU 资源。/pooling、/classify、/score、/rerank、/tokenize、/detokenize:涉及模型推理和文本处理的核心功能。/pause、/resume、/abort_requests、/update_weights:这些是管理控制端点,可以暂停服务器、取消其他用户的请求,甚至更新模型权重。
问题不止于认证缺失。vllm serve 默认监听所有网络接口,除非显式指定 --host 参数。其启动器绑定的是 (args.host or "", args.port),其中空字符串代表所有接口。Docker 的 -p 8000:8000 映射同样会在宿主机上暴露所有接口。最终结果是:任何能访问 8000 端口的人,无论有没有 key,都能在你的 GPU 上运行提示、暂停服务器或取消他人的请求。
vLLM 的 HTTP 服务由 FastAPI 驱动,路由按前缀分组挂载。--api-key 的认证逻辑实际上是一个挂在特定路径前缀(/v1、/v2、/inference、/cohere)上的中间件或依赖,而非全局中间件——这是问题的根源。凡是注册时不带这些前缀的路由,都不会经过认证检查。/invocations 之所以危险,是因为它是这些前缀之一之外的推理入口,功能与 /v1/completions 等价;/pooling、/tokenize 等属于模型预处理与推理辅助端点;/pause、/update_weights 等则是管理控制面。换句话说,vLLM 把「API 兼容层」和「服务内部接口」注册在同一个进程与端口上,认证只覆盖了前者,后者默认可达。这也是为什么官方反复强调不要把 vLLM 直接暴露到不可信网络。
官方推荐的防御策略:白名单反向代理
vLLM 安全指南将“只允许特定端点的反向代理”称为“最有效的方法”。下面是一个最小化的实现方案。
第一步:绑定 localhost,用环境变量存放密钥
export VLLM_API_KEY="$(openssl rand -hex 32)" # 留一份副本给客户端 vllm serve Qwen/Qwen3-8B --host 127.0.0.1 --port 8000 --disable-fastapi-docs
使用 VLLM_API_KEY 环境变量而非 --api-key 参数,是为了避免密钥出现在进程列表中,被机器上的其他用户看到。即使配置了代理,也要保留这个 key,以防代理配置失误导致 /v1 直接暴露。同时,永远不要在可访问的服务器上设置 VLLM_SERVER_DEV_MODE=1,这会引入 /collective_rpc 端点,官方指南称之为“极其危险”。
如果使用 Docker,请用 -p 127.0.0.1:8000:8000 发布端口,或者干脆不配置 vLLM 服务的 ports:,只发布代理端口。需要留意的是,在 Linux 上,Docker 发布的端口会绕过 ufw 和 firewalld 防火墙规则。
--host 127.0.0.1 的作用是让 vLLM 只监听本机回环接口,外网流量必须先经过反向代理才能到达。回环绑定配合代理白名单,才能同时解决「认证缺失」和「接口暴露」两个问题:即使某些端点没有 key 校验,外部也无法直接访问,只能走代理的白名单路径。用 VLLM_API_KEY 环境变量而非 --api-key 命令行参数,是因为命令行参数会出现在 ps aux、/proc/<pid>/cmdline 等位置,同一台机器上的其他用户可直接读取。Docker 场景下 -p 127.0.0.1:8000:8000 同理,它让端口只发布到宿主机回环。此外,Linux 上 Docker 的端口发布规则会绕过 ufw、firewalld 等基于 netfilter 的宿主防火墙,因此不能依赖系统防火墙来阻断直连,必须在发布端口时就限定到回环或干脆不发布。
第二步:用 Caddy 或 nginx 配置端点白名单
以 Caddy 为例,配置的核心思路是只允许应用实际调用的路由通过,其余全部拒绝。
llm.example.com { @api path /v1/chat/completions /v1/completions /v1/embeddings /v1/models @api_with_key { path /v1/chat/completions /v1/completions /v1/embeddings /v1/models header Authorization "Bearer PASTE_YOUR_KEY_HERE" }
handle @api_with_key {
reverse_proxy 127.0.0.1:8000 {
flush_interval -1
}
}
handle @api {
respond "Missing or wrong API key" 401
}
handle {
respond 404
}
}
Caddy 的匹配器规则要求:同一个命名匹配器内的不同匹配类型必须同时满足,因此 @api_with_key 既需要路径在允许列表中,也需要请求头完全匹配。Handle 块按书写顺序执行,首个匹配生效,没有匹配器的块最后执行。path 指令使用精确匹配,所以 /invocations、/tokenize、/pause 和 /metrics 都会返回 404。
如果你更熟悉 nginx,等效配置使用 map 指令校验 Authorization 头,然后在 location 块中用正则限制路径。两个方案的核心逻辑一致:只放行白名单内的端点,其余一律拒绝。
Caddy 的匹配器语义是这里的关键:同一个命名 matcher 中的多个匹配类型默认是 AND 关系,所以 @api_with_key 必须同时满足「路径在白名单内」和「Authorization 头完全等于指定值」两个条件。handle 块按书写顺序求值,第一个匹配的块生效,因此把带 key 的块写在前面、不带 key 的降级块写在后面、最后放一个无 matcher 的兜底块,就实现了「有 key 且路径合法 → 放行;路径合法但无 key → 401;其余 → 404」的判定顺序。path 指令是精确匹配而非前缀匹配,所以 /invocations、/tokenize、/pause、/metrics 这些不在列表中的路径会直接落入兜底块返回 404。nginx 方案中常用的替代是 map $http_authorization $authorized 做头部校验、再用 location ~ ^/v1/(chat/completions|completions|embeddings|models)$ 之类的正则限制路径,逻辑与 Caddy 完全一致。
验证每一层防护是否生效
配置完成后,必须从外部验证防护措施是否真正生效。
不带 key:应返回 401
curl -s -o /dev/null -w "%{http_code}\n" https://llm.example.com/v1/models
带 key:应返回模型信息
curl -H "Authorization: Bearer YOUR_KEY" https://llm.example.com/v1/models
访问 --api-key 不覆盖的路由:应返回 404
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://llm.example.com/invocations
从另一台机器直连 vLLM:应连接失败
curl --max-time 5 http://<server LAN IP>:8000/version
如果最后一条命令得到了响应,说明代理可以被绕过。此时需要检查 --host 参数、Docker 的 ports: 配置,并用 ss -tlnp | grep 8000 确认监听状态。
代理无法覆盖的领域:节点间通信与软件更新
反向代理能保护 HTTP 层面的访问,但 vLLM 的节点间通信是另一个完全不同的信任域。官方指南指出,节点间流量(PyTorch 分布式、KV 缓存传输、并行计算)默认是不安全的,PyTorch 的 TCPStore 监听所有接口。多节点部署应将其保持在隔离网络上,并将 VLLM_HOST_IP 设置为私有地址。
如果你在 0.9.2 到 0.23.x 版本上使用 P2pNcclConnector 运行分离式服务,有一个已披露的安全漏洞(GHSA-vfp2-c8pq-v6h6)值得关注:它允许 X-Request-Id 头泄露 KV 缓存数据,而上述代理配置会原样传递该头部。
更重要的是,代理不能替代软件更新。持有正确 key 的用户仍然可以访问推理代码,而 vLLM 自 9 月 12 日以来披露的 30 多个安全公告大多集中在这部分代码中,其中多数没有 CVE 编号,也不在 OSV 数据库中。0.31.0 版本修复了所有这些问题。
安全是一个纵深防御的体系:绑定本地回环、使用代理白名单、通过网络隔离保护节点间通信、及时更新版本。任何单独一层都不足以让人高枕无忧。
本文基于 vLLM 安全指南、CLI 参考、Docker 指南及启动器源码,以及 Caddy 官方文档整理。
vLLM 的多节点推理(张量并行、流水线并行、分离式 prefill/decode)依赖 PyTorch 的分布式通信原语。PyTorch 默认用 TCPStore 作为 rendezvous 服务,它绑定在所有接口上,且本身不带认证或加密;节点间传输的 KV 缓存、权重分片、控制消息同样默认是明文。这套通信走的是自定义二进制协议,不经过 HTTP 层,因此反向代理完全无法介入。隔离网络(VPC 子网、私有网段、专用互联)是唯一的边界手段,配合 VLLM_HOST_IP 把进程绑定到私有地址、避免服务自动选择到公网网卡。GHSA-vfp2-c8pq-v6h6 的 X-Request-Id 头泄露问题则说明,代理默认会透传请求头,攻击者可以利用这一点把数据从 KV 缓存侧信道带出——补救方式是使用已修复的版本,或在代理层剥离不受信任的 X-Request-Id。
相关推荐

一句提示词引发的全球PHP安全风暴:AI是救星还是隐患?
一句AI提示词竟引发全球PHP安全危机,维基百科、互联网档案馆受波及。本文梳理事件脉络,解析OpenAI与亚马逊的争议,探讨AI时代漏洞发现门槛降低带来的安全悖论与开发者应对策略。

当AI被拿走:依赖捷径如何削弱你的解题韧性
一项涉及1,222人的研究发现:使用AI答题后,一旦AI被移除,人们放弃解题的比例高于从未使用AI的对照组。文章分析AI依赖对学习韧性和元认知的影响,并探讨AI学习工具的设计启示。

Carta联手NewView:让创业公司员工提前套现股权
Carta 与 NewView Capital 合作推出新计划,简化创业公司 tender offer 流程,帮助员工提前出售股权套现。本文分析该合作的核心机制、员工股权流动性痛点及对创业公司股权激励的影响。