diff --git a/backend/tests/test_provisioner_request_threading.py b/backend/tests/test_provisioner_request_threading.py index 933b2ad61..676292991 100644 --- a/backend/tests/test_provisioner_request_threading.py +++ b/backend/tests/test_provisioner_request_threading.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio import inspect +import logging import threading import time from contextlib import contextmanager @@ -42,6 +43,34 @@ def test_provisioner_accepts_canonical_thread_ids(provisioner_module, thread_id: assert request.thread_id == thread_id +def test_sandbox_access_url_sanitizes_transient_error_log( + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, + provisioner_module, +) -> None: + class FailingCoreV1: + def read_namespaced_service(self, _name: str, _namespace: str): + raise ApiException(status=500, reason="upstream\nforged\rentry") + + monkeypatch.setattr(provisioner_module, "core_v1", FailingCoreV1()) + caplog.set_level(logging.WARNING, logger=provisioner_module.logger.name) + + assert ( + provisioner_module._sandbox_access_url( + "sandbox\nforged", + tolerate_read_errors=True, + ) + is None + ) + + assert len(caplog.records) == 1 + message = caplog.records[0].getMessage() + assert "sandboxforged" in message + assert "upstreamforgedentry" in message + assert "\n" not in message + assert "\r" not in message + + def test_create_rejects_mount_contract_precondition_before_k8s_io( monkeypatch: pytest.MonkeyPatch, provisioner_module, diff --git a/backend/tests/test_upload_sandbox_sync.py b/backend/tests/test_upload_sandbox_sync.py index 8ac8d6873..348fdc731 100644 --- a/backend/tests/test_upload_sandbox_sync.py +++ b/backend/tests/test_upload_sandbox_sync.py @@ -420,7 +420,7 @@ def test_failed_journal_restore_keeps_cross_process_name_reservation(tmp_path): raise OSError("directory fsync failed") return real_fsync(descriptor) - def fail_journal_recreation(path, flags, mode=0o777): + def fail_journal_recreation(path, flags, mode=0o600): if os.path.abspath(path) == os.path.abspath(journal_path) and flags & os.O_CREAT: raise OSError("journal recreation failed") return real_open(path, flags, mode) diff --git a/docker/provisioner/app.py b/docker/provisioner/app.py index 288670a36..3d76015be 100644 --- a/docker/provisioner/app.py +++ b/docker/provisioner/app.py @@ -58,6 +58,12 @@ logging.basicConfig( format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) + +def _sanitize_log_param(value: object) -> str: + """Strip line-breaking controls from values before logging them.""" + return str(value).replace("\r", "").replace("\n", "").replace("\x00", "") + + # ── Configuration (all tuneable via environment variables) ─────────────── K8S_NAMESPACE = os.environ.get("K8S_NAMESPACE", "deer-flow") @@ -1290,9 +1296,9 @@ def _sandbox_access_url( if tolerate_read_errors and exc.status not in {401, 403}: logger.warning( "Transient error reading Service %s: status=%s reason=%s", - _svc_name(sandbox_id), - exc.status, - exc.reason, + _sanitize_log_param(_svc_name(sandbox_id)), + _sanitize_log_param(exc.status), + _sanitize_log_param(exc.reason), ) return None raise