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.
|
||||
|
||||
## 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"
|
||||
if not isinstance(payload.get("reason"), str):
|
||||
return None, "reason must be a string"
|
||||
if len(payload["value"]) > MAX_VALUE_LENGTH:
|
||||
return None, f"value must be at most {MAX_VALUE_LENGTH} characters"
|
||||
if len(payload["reason"]) > MAX_REASON_LENGTH:
|
||||
return None, f"reason must be at most {MAX_REASON_LENGTH} characters"
|
||||
if len(payload["value"].encode("utf-8")) > MAX_VALUE_LENGTH:
|
||||
return None, f"value must be at most {MAX_VALUE_LENGTH} UTF-8 bytes"
|
||||
if len(payload["reason"].encode("utf-8")) > MAX_REASON_LENGTH:
|
||||
return None, f"reason must be at most {MAX_REASON_LENGTH} UTF-8 bytes"
|
||||
|
||||
normalized, error = validate_entry(payload)
|
||||
if error:
|
||||
|
||||
@@ -161,6 +161,14 @@ def test_resolution_input_rejects_oversized_and_non_string_fields_cleanly():
|
||||
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():
|
||||
session_factory = setup_db()
|
||||
blocker_id, admin_id, _ = _seed_blocker(session_factory)
|
||||
|
||||
Reference in New Issue
Block a user