fix: job_id невалидного сообщения пишется в лог через repr (M-7)
job_id из сообщения, не прошедшего контракт, — данные отправителя; без экранирования перевод строки в нём позволял подделать строку лога. Теперь repr(job_id[:64]), как в рулинге R-18 для kid токена. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7a6a7fb36b
commit
87816ef6c9
@ -560,7 +560,8 @@ def _parse_message(msg: InboundMessage) -> RecognitionResultMessage | None:
|
|||||||
return RecognitionResultMessage.model_validate(data)
|
return RecognitionResultMessage.model_validate(data)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
raw_job_id = data.get("job_id")
|
raw_job_id = data.get("job_id")
|
||||||
job_id = raw_job_id[:LOG_ID_MAX_LENGTH] if isinstance(raw_job_id, str) else None
|
# job_id невалидного сообщения — данные отправителя: repr экранирует переводы строк и управляющие символы
|
||||||
|
job_id = repr(raw_job_id[:LOG_ID_MAX_LENGTH]) if isinstance(raw_job_id, str) else None
|
||||||
errors = "; ".join(
|
errors = "; ".join(
|
||||||
f"{'.'.join(str(part) for part in error['loc']) or '<root>'}: {error['msg']}"
|
f"{'.'.join(str(part) for part in error['loc']) or '<root>'}: {error['msg']}"
|
||||||
for error in exc.errors(include_url=False, include_input=False, include_context=False)
|
for error in exc.errors(include_url=False, include_input=False, include_context=False)
|
||||||
|
|||||||
@ -238,6 +238,18 @@ async def test_invalid_message_log_carries_job_id_when_readable(logs: list[Any])
|
|||||||
assert str(_JOB) in record["message"]
|
assert str(_JOB) in record["message"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_invalid_message_job_id_is_escaped_in_log(logs: list[Any]):
|
||||||
|
"""job_id невалидного сообщения с переводом строки попадает в лог через repr: подделать строку лога нельзя (M-7)."""
|
||||||
|
forged = "not-a-uuid\n2026-10-04 | INFO | подделанная запись\r\x1b[31m"
|
||||||
|
body = {**envelope(_JOB, _FILE, "done"), "job_id": forged}
|
||||||
|
msg = fake_msg(body, "result:x:1", validate=False)
|
||||||
|
await handle_message(msg, session_factory=_UntouchableFactory()) # type: ignore[arg-type]
|
||||||
|
(record,) = _logged(logs, "отклонено", "WARNING")
|
||||||
|
message = record["message"]
|
||||||
|
assert "\n" not in message and "\r" not in message and "\x1b" not in message
|
||||||
|
assert repr(forged) in message
|
||||||
|
|
||||||
|
|
||||||
async def test_database_error_is_logged_and_not_raised(logs: list[Any]):
|
async def test_database_error_is_logged_and_not_raised(logs: list[Any]):
|
||||||
"""Ошибка БД (сервер недоступен) — `logger.exception`, без повторов, наружу не выходит."""
|
"""Ошибка БД (сервер недоступен) — `logger.exception`, без повторов, наружу не выходит."""
|
||||||
engine = create_async_engine("postgresql+asyncpg://nobody:nothing@127.0.0.1:1/none")
|
engine = create_async_engine("postgresql+asyncpg://nobody:nothing@127.0.0.1:1/none")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user