执行摘要
- 一句话:Bench 日志与 PyTorch 元数据脱敏,防止凭据外泄
- 推荐动作:值得精读,改动虽小但展示了日志脱敏的通用套路:共享纯函数
redact_sensitive_namespace 加调用点替换,并刻意把脱敏副本与请求数据路径分离,避免在打印与写入 artifact 的过程中泄露凭据。值得关注的决策:一是用浅拷贝而不是原地修改,保证原参数零副作用;二是 save_to_pytorch_benchmark_format 也传入脱敏副本,防止凭据从 stdout 转移到结果文件。建议后续在 tests/benchmarks/test_serve_cli.py 中补充正式断言,把 stdout 不含明文 header / api_key、结果文件不含明文、原始 args 不变这三点固化下来,并考虑基于参数命名约定自动识别敏感字段。
功能与动机
PR body 明确指出:vllm bench serve printed its full argument namespace, including values passed through --header, while the multi-turn benchmark logged both --api-key and --header. This could expose credentials in local or CI logs。也就是说,用户通过 --header 传入的 Authorization: Bearer ... 或通过 --api-key 传入的密钥,会随启动诊断日志出现在终端、日志文件与 CI 产物中。修复目标是只对日志与元数据输出做脱敏,同时保留原始参数用于真实 HTTP 请求。
实现拆解
- 新增共享脱敏工具:在
vllm/benchmarks/lib/utils.py 中新增 redact_sensitive_namespace(args, fields)。它通过 argparse.Namespace(**vars(args)) 浅拷贝整个参数 Namespace,再对 fields 中值为非 None 的字段统一赋值为 '***',最后返回副本。这样任何调用方都可以在打印或序列化前获得一份脱敏数据,而原始 args 完全不变,请求发送路径不受影响。
serve.py 双路径接入:在 vllm/benchmarks/serve.py 中引入该工具并定义 _SENSITIVE_ARG_FIELDS = ('header',)。main_async 启动时的 print(args) 改为 print(redact_sensitive_namespace(args, _SENSITIVE_ARG_FIELDS));同时 save_to_pytorch_benchmark_format 调用 convert_to_pytorch_benchmark_format 时,args 参数也换成脱敏副本,避免 --header 中的 Authorization 头以元数据形式写入 .pytorch.json 结果文件。
- 多轮 benchmark 接入:在
benchmarks/multi_turn/benchmark_serving_multi_turn.py 中同样导入该工具,定义 _SENSITIVE_ARG_FIELDS = ('api_key', 'header'),并将 logger.info(args) 改为打印脱敏副本。这样 --api-key 与 --header 不再出现在启动日志里。
- 测试与配套情况:本次没有新增或修改测试文件;PR body 中描述了本地验证方式——用
python -c 断言 stdout、PyTorch 元数据、多轮日志均脱敏且原始 Namespace 值保持不变,并执行 python -m pytest tests/benchmarks/test_serve_cli.py 与 pre-commit 检查。这意味着脱敏行为目前缺乏仓库内的自动化回归测试。
关键文件:
vllm/benchmarks/lib/utils.py(模块 脱敏工具;类别 source;类型 core-logic;符号 redact_sensitive_namespace): 新增核心脱敏工具 redact_sensitive_namespace,serve 与 multi_turn 两个入口均复用它,是整个修复的公共支点。
vllm/benchmarks/serve.py(模块 基准脚本;类别 source;类型 dependency-wiring;符号 main_async, save_to_pytorch_benchmark_format): serve 启动日志与 PyTorch 元数据写入路径都使用脱敏副本,覆盖 --header 中的 Authorization 头,是泄露面最大的调用点。
benchmarks/multi_turn/benchmark_serving_multi_turn.py(模块 多轮脚本;类别 source;类型 dependency-wiring;符号 main): 多轮 benchmark 的启动日志同时覆盖 --api-key 与 --header 两个敏感参数,是第二处泄露入口。
关键符号:redact_sensitive_namespace
关键源码片段
vllm/benchmarks/lib/utils.py
新增核心脱敏工具 redact_sensitive_namespace,serve 与 multi_turn 两个入口均复用它,是整个修复的公共支点。
# vllm/benchmarks/lib/utils.py
# 核心改动:共享的脱敏工具,供 serve 与 multi_turn 两个入口复用。
import argparse
def redact_sensitive_namespace(
args: argparse.Namespace, fields: tuple[str, ...]
) -> argparse.Namespace:
# 用 vars(args) 浅拷贝一份全新的 Namespace,原始对象保持不变
redacted_args = argparse.Namespace(**vars(args))
for field in fields:
# 只替换非 None 值,避免把未启用的可选参数也打印成 '***'
if getattr(redacted_args, field, None) is not None:
setattr(redacted_args, field, '***')
return redacted_args
vllm/benchmarks/serve.py
serve 启动日志与 PyTorch 元数据写入路径都使用脱敏副本,覆盖 --header 中的 Authorization 头,是泄露面最大的调用点。
# vllm/benchmarks/serve.py
# 敏感字段清单只包含 header,Authorization 头主要由它携带。
_SENSITIVE_ARG_FIELDS = ('header',)
def save_to_pytorch_benchmark_format(
args: argparse.Namespace, results: dict[str, Any], file_name: str
) -> None:
# 输出到 PyTorch 元数据的指标集合
metrics = [
'median_ttft_ms', 'mean_ttft_ms', 'std_ttft_ms', 'p99_ttft_ms',
'mean_tpot_ms', 'median_tpot_ms', 'std_tpot_ms', 'p99_tpot_ms',
'median_itl_ms', 'mean_itl_ms', 'std_itl_ms', 'p99_itl_ms',
]
# 原始输出序列体积较大,暂不进元数据
ignored_metrics = ['ttfts', 'itls', 'generated_texts', 'errors']
# 关键变更:传入脱敏副本,避免 header 中的凭据写入 .pytorch.json
pt_records = convert_to_pytorch_benchmark_format(
args=redact_sensitive_namespace(args, _SENSITIVE_ARG_FIELDS),
metrics={k: [results[k]] for k in metrics if k in results},
extra_info={
k: results[k]
for k in results
if k not in metrics and k not in ignored_metrics
},
)
if pt_records:
# 故意不使用 json 后缀,防止 CI 误采集该文件
pt_file = f'{os.path.splitext(file_name)[0]}.pytorch.json'
write_to_json(pt_file, pt_records)
benchmarks/multi_turn/benchmark_serving_multi_turn.py
多轮 benchmark 的启动日志同时覆盖 --api-key 与 --header 两个敏感参数,是第二处泄露入口。
# benchmarks/multi_turn/benchmark_serving_multi_turn.py
# 多轮 benchmark 的敏感字段同时覆盖 api_key 与 header。
_SENSITIVE_ARG_FIELDS = ('api_key', 'header')
async def main() -> None:
args = parser.parse_args()
# 原代码是 logger.info(args),会把裸的 API Key 打印到日志中
logger.info(redact_sensitive_namespace(args, _SENSITIVE_ARG_FIELDS))
# 后续读取 url、model 等字段仍使用原始 args,不受脱敏影响
logger.info(f'url={args.url}')
logger.info(f'model={args.model}')
评论区精华
本 PR 没有产生实质性 review 讨论:GitHub 上没有 inline review 评论;claude[bot] 自动提示 fork PR 默认关闭自动 review,维护者 yewentao256 直接批准并给出 LGTM, thanks for the work!。可留意的点在于 PR body 用复现命令与 main/branch 输出对比证明了脱敏效果,但这些断言没有被固化为仓库测试。
风险与影响
- 风险:
redact_sensitive_namespace 依赖调用方手工维护敏感字段清单:serve.py 只覆盖 header,多轮脚本覆盖 api_key 与 header。未来新增 --token、--password、带明文凭据的 URL 等参数时,若忘记扩展 _SENSITIVE_ARG_FIELDS,日志仍会泄露凭据。本次没有新增测试文件,脱敏行为只存在于 PR body 的本地断言中,仓库内的回归保护不足。另外该函数是浅拷贝,如果敏感值类型变成嵌套对象或可变容器,副本与原始对象可能共享内部状态,需要重新评估。多轮脚本新增了对 vllm.benchmarks.lib.utils 的导入,脚本被单独拷贝到无 vLLM 包的环境时会出现 ImportError,但仓库内运行不受影响。
- 影响:对用户:运行
vllm bench serve 与多轮 benchmark 的用户在 stdout / 日志中不再看到 --api-key 与 --header 的明文值;--save-result 等生成的 .pytorch.json 元数据文件同样脱敏。对系统:请求路径使用的仍是原始 args,吞吐、时延与请求内容没有任何变化。对团队:CI 日志安全等级提升;但由于敏感字段清单是硬编码的,后续新增相似入口时需要同步接入,否则会出现覆盖盲区。
- 风险标记:缺少测试文件变更, 敏感字段清单需人工维护, 浅拷贝边界需留意
关联脉络
- PR #51104 [Rust][Benchmark] Support HF ShareGPT datasets in multi-turn mode: 同属 benchmark 工具链的演进,但位于 Rust 侧,与本 PR 没有共享文件,关联较间接。
- PR #55650 [Bugfix] Make KV cache and MFU log lines backend-neutral: 同属日志输出治理类改动,但作用于 metrics 日志路径,与本 PR 不在同一模块。
参与讨论