Browse Source

fix: reject benchmark url control characters

Problem:
Benchmark base URLs containing Unicode category C characters could pass configuration validation and fail later during httpx client construction.

Risk:
An ASCII-only restriction would reject valid printable internationalized URLs; regression tests reject every category C subtype representative while preserving internationalized HTTP(S) targets and existing URL rules.
zhenyu.hu 2 weeks ago
parent
commit
bd36806189
2 changed files with 34 additions and 0 deletions
  1. 3 0
      src/agent_lab/application/benchmark.py
  2. 31 0
      tests/test_benchmark_config.py

+ 3 - 0
src/agent_lab/application/benchmark.py

@@ -1,4 +1,5 @@
 import json
+import unicodedata
 from collections.abc import Mapping
 from types import MappingProxyType
 from typing import Literal, TypeAlias
@@ -66,6 +67,8 @@ class _FrozenStrictBenchmarkModel(BaseModel):
 def _validate_base_url(value: str) -> str:
     if not value or value != value.strip() or any(char.isspace() for char in value):
         raise ValueError("base_url must be a non-empty HTTP(S) URL")
+    if any(unicodedata.category(char).startswith("C") for char in value):
+        raise ValueError("base_url must not contain Unicode category C characters")
     try:
         parsed = urlsplit(value)
         hostname = parsed.hostname

+ 31 - 0
tests/test_benchmark_config.py

@@ -82,6 +82,37 @@ def test_benchmark_config_rejects_unsafe_or_non_http_base_urls(base_url: str):
         BenchmarkConfig.model_validate(payload)
 
 
+@pytest.mark.parametrize(
+    "character",
+    [
+        pytest.param(chr(0), id="nul-cc"),
+        pytest.param("\r\n", id="crlf-cc"),
+        pytest.param("\t", id="tab-cc"),
+        pytest.param(chr(0x200B), id="zero-width-space-cf"),
+        pytest.param(chr(0x202E), id="right-to-left-override-cf"),
+        pytest.param(chr(0xE000), id="private-use-co"),
+        pytest.param(chr(0xD800), id="surrogate-cs"),
+        pytest.param(chr(0x0378), id="unassigned-cn"),
+    ],
+)
+def test_benchmark_config_rejects_unicode_category_c_in_base_url(character: str):
+    payload = {
+        **VALID_CONFIG,
+        "base_url": f"https://provider.example/v1{character}segment",
+    }
+
+    with pytest.raises(ValidationError, match="base_url"):
+        BenchmarkConfig.model_validate(payload)
+
+
+def test_benchmark_config_accepts_printable_internationalized_base_url():
+    base_url = "https://例え.テスト/路径/模型-🚀"
+
+    config = BenchmarkConfig.model_validate({**VALID_CONFIG, "base_url": base_url})
+
+    assert config.base_url == base_url
+
+
 @pytest.mark.parametrize("model", ["", "   "])
 def test_benchmark_config_rejects_empty_models(model: str):
     payload = {**VALID_CONFIG, "model": model}