fix: enforce utf8 resolution limits
This commit is contained in:
@@ -129,3 +129,44 @@ Result:
|
|||||||
```
|
```
|
||||||
|
|
||||||
The existing `pytest_asyncio` configuration and `openpyxl` print-area warnings remain unchanged.
|
The existing `pytest_asyncio` configuration and `openpyxl` print-area warnings remain unchanged.
|
||||||
|
|
||||||
|
## UTF-8 Byte-Length Re-Review Fix
|
||||||
|
|
||||||
|
### RED
|
||||||
|
|
||||||
|
Added `test_resolution_input_rejects_multibyte_utf8_at_ascii_character_limits` before changing production code. It submits `é` repeated 512 times as `value` and 2000 times as `reason`; both are within the existing character counts but exceed the corresponding UTF-8 byte budgets.
|
||||||
|
|
||||||
|
Focused command:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python -m pytest central/tests/test_blocker_resolution_panel.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
RED result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 failed, 9 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
The failure confirmed character-count validation accepted the multibyte inputs.
|
||||||
|
|
||||||
|
### GREEN
|
||||||
|
|
||||||
|
- `value` and `reason` limits now use `len(value.encode("utf-8"))` and `len(reason.encode("utf-8"))`.
|
||||||
|
- Existing ASCII boundary inputs remain accepted.
|
||||||
|
- Multibyte inputs fail closed with explicit UTF-8 byte-limit validation errors, before persistence or audit sanitization.
|
||||||
|
- Redaction and remote-write boundaries were not changed.
|
||||||
|
|
||||||
|
Focused result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
10 passed, 1 warning
|
||||||
|
```
|
||||||
|
|
||||||
|
Full central result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
266 passed, 1 warning
|
||||||
|
```
|
||||||
|
|
||||||
|
The existing `pytest_asyncio` configuration and `openpyxl` print-area warnings remain unchanged.
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ def validate_resolution_input(payload):
|
|||||||
return None, "value must be a string"
|
return None, "value must be a string"
|
||||||
if not isinstance(payload.get("reason"), str):
|
if not isinstance(payload.get("reason"), str):
|
||||||
return None, "reason must be a string"
|
return None, "reason must be a string"
|
||||||
if len(payload["value"]) > MAX_VALUE_LENGTH:
|
if len(payload["value"].encode("utf-8")) > MAX_VALUE_LENGTH:
|
||||||
return None, f"value must be at most {MAX_VALUE_LENGTH} characters"
|
return None, f"value must be at most {MAX_VALUE_LENGTH} UTF-8 bytes"
|
||||||
if len(payload["reason"]) > MAX_REASON_LENGTH:
|
if len(payload["reason"].encode("utf-8")) > MAX_REASON_LENGTH:
|
||||||
return None, f"reason must be at most {MAX_REASON_LENGTH} characters"
|
return None, f"reason must be at most {MAX_REASON_LENGTH} UTF-8 bytes"
|
||||||
|
|
||||||
normalized, error = validate_entry(payload)
|
normalized, error = validate_entry(payload)
|
||||||
if error:
|
if error:
|
||||||
|
|||||||
@@ -161,6 +161,14 @@ def test_resolution_input_rejects_oversized_and_non_string_fields_cleanly():
|
|||||||
assert error
|
assert error
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolution_input_rejects_multibyte_utf8_at_ascii_character_limits():
|
||||||
|
base = {"scope": "PRODUCT", "scope_key": "P-001", "key": "service_cfop", "value": "5102", "reason": "reviewed"}
|
||||||
|
for field, value in (("value", "é" * 512), ("reason", "é" * 2000)):
|
||||||
|
normalized, error = validate_resolution_input({**base, field: value})
|
||||||
|
assert normalized is None
|
||||||
|
assert error and "bytes" in error
|
||||||
|
|
||||||
|
|
||||||
def test_boundary_length_audit_keeps_required_fields_instead_of_truncating_context():
|
def test_boundary_length_audit_keeps_required_fields_instead_of_truncating_context():
|
||||||
session_factory = setup_db()
|
session_factory = setup_db()
|
||||||
blocker_id, admin_id, _ = _seed_blocker(session_factory)
|
blocker_id, admin_id, _ = _seed_blocker(session_factory)
|
||||||
|
|||||||
Reference in New Issue
Block a user