Skip to content

Commit a978b1d

Browse files
zerone0xPangjiping
authored andcommitted
fix(server): correct Kubernetes label key length validation
The previous implementation of _is_valid_label_key checked whether the total key length exceeded 253 characters. This incorrectly rejected valid label keys where the prefix portion was within the allowed 253-char limit but the combined prefix+"/"+name exceeded 253 characters. According to Kubernetes label key constraints: - The optional prefix must be a DNS subdomain with at most 253 characters - The name segment must be at most 63 characters - There is no constraint on the combined total length (max: 253+1+63=317) This commit removes the total-length check and replaces it with a direct prefix-length check, which matches the Kubernetes specification. Regression coverage is added for: - Valid key rejected by the old check (prefix=251 chars, total>253) - Prefix that exceeds 253 chars (should be rejected) - Invalid prefix format - Name that exceeds 63 chars - Value that exceeds 63 chars - Non-string metadata keys - Key with empty prefix (starts with '/')
1 parent ec7e7c2 commit a978b1d

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

server/src/services/validators.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,16 @@ def ensure_entrypoint(entrypoint: Sequence[str]) -> None:
5959

6060

6161
def _is_valid_label_key(key: str) -> bool:
62-
if len(key) > 253 or "/" in key and len(key.split("/", 1)[0]) > 253:
63-
return False
6462
if "/" in key:
6563
prefix, name = key.split("/", 1)
6664
if not prefix or not name:
6765
return False
66+
# Kubernetes requires the prefix to be a DNS subdomain <= 253 chars.
67+
# The name portion is validated separately below (max 63 chars).
68+
# Note: the total key length (prefix + "/" + name) may exceed 253 chars
69+
# when the prefix uses its full 253-character allowance; this is valid.
70+
if len(prefix) > 253:
71+
return False
6872
if not DNS_SUBDOMAIN_RE.match(prefix):
6973
return False
7074
else:

server/tests/test_validators.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,71 @@ def test_ensure_metadata_labels_allows_none_or_empty():
4848
ensure_metadata_labels({})
4949

5050

51+
def test_ensure_metadata_labels_rejects_name_too_long():
52+
"""Label name part exceeding 63 characters should be rejected."""
53+
long_name = "a" * 64
54+
with pytest.raises(HTTPException) as exc_info:
55+
ensure_metadata_labels({long_name: "value"})
56+
assert exc_info.value.status_code == 400
57+
assert exc_info.value.detail["code"] == SandboxErrorCodes.INVALID_METADATA_LABEL
58+
59+
60+
def test_ensure_metadata_labels_rejects_prefix_too_long():
61+
"""Label prefix (DNS subdomain) exceeding 253 characters should be rejected."""
62+
# Build a prefix that is longer than 253 chars: 5 labels of 62 chars = 314 chars
63+
label_part = "a" * 62
64+
long_prefix = ".".join([label_part] * 5) # 62*5 + 4 = 314 chars
65+
key = f"{long_prefix}/name"
66+
with pytest.raises(HTTPException) as exc_info:
67+
ensure_metadata_labels({key: "value"})
68+
assert exc_info.value.status_code == 400
69+
assert exc_info.value.detail["code"] == SandboxErrorCodes.INVALID_METADATA_LABEL
70+
71+
72+
def test_ensure_metadata_labels_accepts_key_with_max_length_prefix_and_name():
73+
"""Valid key where prefix <= 253 chars and name <= 63 chars but total > 253 should be accepted."""
74+
# prefix = 4 labels of 62 chars = 62*4 + 3 = 251 chars (valid DNS subdomain)
75+
label_part = "a" * 62
76+
prefix = ".".join([label_part] * 4) # 251 chars
77+
assert len(prefix) == 251
78+
key = f"{prefix}/valid-name" # total = 251 + 1 + 10 = 262 chars, but prefix <= 253 ✓
79+
# This was previously rejected due to the incorrect total-length check.
80+
ensure_metadata_labels({key: "value"}) # Should NOT raise
81+
82+
83+
def test_ensure_metadata_labels_rejects_invalid_prefix_format():
84+
"""Label prefix with invalid DNS subdomain characters should be rejected."""
85+
with pytest.raises(HTTPException) as exc_info:
86+
ensure_metadata_labels({"INVALID_PREFIX.io/name": "value"})
87+
assert exc_info.value.status_code == 400
88+
assert exc_info.value.detail["code"] == SandboxErrorCodes.INVALID_METADATA_LABEL
89+
90+
91+
def test_ensure_metadata_labels_rejects_value_too_long():
92+
"""Label value exceeding 63 characters should be rejected."""
93+
long_value = "a" * 64
94+
with pytest.raises(HTTPException) as exc_info:
95+
ensure_metadata_labels({"app": long_value})
96+
assert exc_info.value.status_code == 400
97+
assert exc_info.value.detail["code"] == SandboxErrorCodes.INVALID_METADATA_LABEL
98+
99+
100+
def test_ensure_metadata_labels_rejects_non_string_key():
101+
"""Non-string keys in metadata should be rejected."""
102+
with pytest.raises(HTTPException) as exc_info:
103+
ensure_metadata_labels({1: "value"}) # type: ignore[dict-item]
104+
assert exc_info.value.status_code == 400
105+
assert exc_info.value.detail["code"] == SandboxErrorCodes.INVALID_METADATA_LABEL
106+
107+
108+
def test_ensure_metadata_labels_rejects_key_with_empty_prefix():
109+
"""Key with an empty prefix (starts with '/') should be rejected."""
110+
with pytest.raises(HTTPException) as exc_info:
111+
ensure_metadata_labels({"/name": "value"})
112+
assert exc_info.value.status_code == 400
113+
assert exc_info.value.detail["code"] == SandboxErrorCodes.INVALID_METADATA_LABEL
114+
115+
51116
# ============================================================================
52117
# Volume Name Validation Tests
53118
# ============================================================================

0 commit comments

Comments
 (0)