Prhub

#54684 [Bugfix][Security] Bound the validation-error response body

原始 PR 作者 lzhan011 合并时间 2026-09-01 14:51 文件变更 2 提交数 2 评论 9 代码增减 +176 / -1

执行摘要

修复验证错误响应体无限放大漏洞,通过限制错误条目数、摘要输入值并清理冗长的 loc 路径。

根据关联 Issue #49239 和 PR body 描述,生产环境观察到单个格式错误的 /v1/responses 请求产生了 12,047 个验证错误和约 114MB 的响应体,这导致前置代理(LiteLLM)因读取和解析巨大响应而停滞,引发服务中断。根本原因是 Pydantic 验证错误处理器会将原始请求的完整 input 值复制到每个错误条目中,导致响应体大小按 request_size × error_count 放大。

此 PR 值得精读。它不仅是一个重要的安全修复,更展示了处理不可信输入/输出时的实用设计模式:如何智能地截断、摘要日志/错误信息以防止资源耗尽,同时尽可能保留其诊断价值。实现方案考虑周全(容器描述、loc 清理、str() 异常捕获),测试充分。对于所有开发 API 服务的工程师都有借鉴意义。

讨论亮点
  • 请求示例展示:维护者 @DarkLight1337 要求提供修复后的错误消息示例,以直观评估变更效果。作者 @lzhan011 在 PR 描述中补充了详细的前后对比,展示了修复后响应大小从 23.6MB 降至 2.3KB 的显著效果,并因此发现了 loc 路径仍过长的问题,推动了第二次提交。
  • CI 失败分析:CI 首次运行时在 test_openai_schema 中出现 500 错误。作者准确分析指出,该失败与本 PR 无关,因为它发生在 RequestValidationError 处理器(返回 400)的外部,且是已知问题(关联 #54116)。通过 /ci retry 重试后通过。这展示了作者对系统边界和测试失败原因的清晰判断。

实现拆解

  1. 定义边界常量与核心摘要函数:在 vllm/entrypoints/serve/exception_handling/handlers/validation.py 中引入三个边界常量 _MAX_REPORTED_ERRORS (10)、_MAX_ERROR_INPUT_CHARS (200) 和 _MAX_ERROR_CHARS (1000)。新增 _summarize_error_input 函数,根据输入值的类型(字符串、字节、容器、其他标量)进行智能摘要,避免对大容器执行 repr(),而是描述其结构(如 <list of 300 items>)。
  2. 新增单条错误格式化函数:新增 _format_error 函数,在渲染单个错误字典前,调用 _summarize_error_input 替换其 input 字段,并调用模块内已有的 clean_loc_for_param 清理 loc 元组中冗长的联合类型分支名,最后对渲染后的字符串长度进行截断。
  3. 修改主异常处理器逻辑:修改 validation_exception_handler 函数。在构建错误消息时,只对 exc.errors() 的前 _MAX_REPORTED_ERRORS 条错误调用 _format_error 进行渲染。如果错误总数超过截断数,则在消息末尾添加 ...and N more errors 提示。这从数量和单条大小两个维度限制了响应体。
  4. 添加全面的测试覆盖:在 tests/entrypoints/serve/exception_handling/test_validation_exception_handler.py 中新增 TestValidationErrorBodyIsBounded 测试类,包含 5 个测试用例,分别验证:错误数量截断、容器输入被描述而非回显、长字符串输入被截断、小输入值被原样保留(确保诊断信息不丢失)、以及联合类型的 loc 被清理。所有测试通过。
文件 模块 状态 重要度
vllm/entrypoints/serve/exception_handling/handlers/validation.py 入口层 modified 7.7
tests/entrypoints/serve/exception_handling/test_validation_exception_handler.py 异常处理测试 modified 7.27

关键符号

_summarize_error_input _format_error validation_exception_handler

关键源码片段

vllm/entrypoints/serve/exception_handling/handlers/validation.py entrypoint

核心修复文件,修改了全局验证错误处理器,实现了对错误响应体的大小限制。

# 以下是变更后 `validation_exception_handler` 中渲染错误消息的核心逻辑片段,展示了如何应用边界限制。
# 源文件 : vllm/entrypoints/serve/exception_handling/handlers/validation.py
​
# ... ( 前置代码省略 )
    if errors:
        count = len(errors)
        label = "error" if count == 1 else "errors"
        # 只取前 _MAX_REPORTED_ERRORS 个错误进行渲染,控制总体数量
        reported = errors[:_MAX_REPORTED_ERRORS]
        message = f"{count} validation {label}:\n"
        # 使用 _format_error 对每个错误条目进行格式化(摘要输入、清理 loc、截断)
        message += "".join(f"  {_format_error(err)}\n" for err in reported)
        # 如果实际错误数超过渲染数,添加提示行
        if count > len(reported):
            message += f"  ...and {count - len(reported)} more {label}\n"
        message = message.rstrip()
    else:
        message = "Validation error"
# ... ( 后续代码省略 )
# 以下是新增的 `_summarize_error_input` 函数,实现了对验证错误中 `input` 值的智能摘要。
# 它避免了直接渲染大容器,而是描述其结构;对字符串进行截断;对其他标量安全转换。
# 源文件 : vllm/entrypoints/serve/exception_handling/handlers/validation.py
​
def _summarize_error_input(value: object) -> object:
    """A size-bounded stand-in for a validation error's `input` value.
​
    Containers are described rather than rendered: `repr()` of a large
    list materializes the whole string first, which is the cost this is
    meant to avoid.
    """
    if isinstance(value, str):
        if len(value) > _MAX_ERROR_INPUT_CHARS:
            return value[:_MAX_ERROR_INPUT_CHARS] + "...[truncated]"
        return value
    if isinstance(value, (bytes, bytearray)):
        return f"<{type(value).__name__} of {len(value)} bytes>"
    if isinstance(value, (list, tuple, set, frozenset, dict)):
        # 对容器类型,只返回其描述,避免递归渲染可能的大内容
        return f"<{type(value).__name__} of {len(value)} items>"
    try:
        text = str(value)
    except Exception:
        # 捕获 int 等类型在转换为字符串时可能抛出的异常 ( 如超过 sys.get_int_max_str_digits())
        return f"<{type(value).__name__}>"
    if len(text) > _MAX_ERROR_INPUT_CHARS:
        return text[:_MAX_ERROR_INPUT_CHARS] + "...[truncated]"
    return text

评论区精华

请求展示修复后的错误消息示例 documentation

@DarkLight1337 要求将修复前后的错误消息示例放入 PR 描述中,以直观评估变更效果。

结论:作者补充了详细的前后对比示例,并因此发现了 `loc` 路径冗长的问题,推动了第二次提交。 · 已解决

CI 失败归因与重试 测试

CI 首次运行在 `test_openai_schema` 中失败,产生 500 错误。作者分析指出该失败发生在 `RequestValidationError` 处理器(返回 400)外部,与本 PR 无关,是已知问题 (#54116),并请求重试。

结论:通过 `/ci retry` 重试后 CI 通过,确认失败非本 PR 引入。 · 已解决

风险与影响

  1. 兼容性风险:修改了全局错误响应格式,特别是 input 字段的展示方式和 loc 路径的清理。依赖原始错误响应格式(如解析 input 字段内容或 loc 元组结构)的客户端或监控系统可能需要适配。但这是必要的安全加固,且新格式保留了核心诊断信息。
  2. 回归风险:风险较低。变更逻辑集中,不涉及核心推理或调度路径。新增的 5 个测试用例覆盖了边界条件和原有行为,且回归测试证实了原有测试仍然通过。
  3. 性能影响:正向影响。修复显著减少了错误响应的构建时间(从数秒降至毫秒级)和网络传输开销。

此修复对 vLLM 服务器的所有 API 端点生效(因为处理器是全局注册的)。它直接解决了生产环境中因恶意或格式错误请求导致的潜在服务中断问题,增强了系统的健壮性和安全性。对开发者而言,错误消息在保留可操作性的同时变得更加简洁。对团队而言,这是对 API 层安全边界的一次重要加固。

安全修复(响应放大) API 响应格式变更

关联 Issue

#49239 Malformed /v1/responses request returns a >100MB validation-error body (full input echoed per error)

完整报告

参与讨论