From c324f351ee0a3161d4f4550ea6477807ce3f4601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=87=E5=AE=B6=E8=89=AF?= <102887115+wjl8636@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:00:01 +0800 Subject: [PATCH] fix(sandbox): truncate quarantine path keys to stay under Windows MAX_PATH (#6880) * fix(sandbox): truncate quarantine path keys to stay under Windows MAX_PATH The quarantine nests three hashed path components (scope, sandbox id, container id). Three full 64-char SHA-256 hexdigests exceed Windows' 260-char MAX_PATH once the quarantine root lives under a long path, so mark() fails with ENOENT creating the generation record. Truncate the digest to 16 hex chars, matching the sandbox-naming convention used elsewhere in this module. * test(sandbox): pin quarantine key truncation to 16 hex chars Guard against a regression that reverts the key digest to a full 64-char SHA-256 hexdigest: three nested 64-char components exceed Windows' 260-char MAX_PATH and re-break record creation, while Linux CI still passes. --------- Co-authored-by: Willem Jiang --- .../deerflow/community/aio_sandbox/quarantine.py | 8 +++++++- backend/tests/test_sandbox_quarantine.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/packages/harness/deerflow/community/aio_sandbox/quarantine.py b/backend/packages/harness/deerflow/community/aio_sandbox/quarantine.py index 202c7a5c8f..9908bce300 100644 --- a/backend/packages/harness/deerflow/community/aio_sandbox/quarantine.py +++ b/backend/packages/harness/deerflow/community/aio_sandbox/quarantine.py @@ -21,7 +21,13 @@ class SandboxQuarantine: @staticmethod def _key(value: str) -> str: - return hashlib.sha256(value.encode("utf-8")).hexdigest() + # Truncate to 16 hex chars (64 bits). The quarantine nests three hashed + # components (scope, sandbox id, container id); three full 64-char + # SHA-256 hexdigests exceed Windows' 260-char MAX_PATH when the root + # lives under a long path, failing record creation with ENOENT. 64 bits + # is far beyond what non-adversarial sandbox/container ids need, and + # matches the sandbox-naming convention elsewhere in this module. + return hashlib.sha256(value.encode("utf-8")).hexdigest()[:16] def _directory(self, info: SandboxInfo) -> Path: return self._root / self._key(info.sandbox_id) diff --git a/backend/tests/test_sandbox_quarantine.py b/backend/tests/test_sandbox_quarantine.py index dfe9f087c5..eda32e1e21 100644 --- a/backend/tests/test_sandbox_quarantine.py +++ b/backend/tests/test_sandbox_quarantine.py @@ -90,3 +90,14 @@ def test_retire_replaced_generations_keeps_unversioned_crash_fence(tmp_path): store.retire_replaced_generations(SandboxInfo("thread-sandbox", "http://sandbox", container_id="new-generation")) assert (directory / "unknown").exists() + + +def test_quarantine_keys_are_truncated_for_windows_max_path(): + # The quarantine nests three hashed path components (scope, sandbox id, + # container id). Three full 64-char SHA-256 hexdigests exceed Windows' + # 260-char MAX_PATH under a long root, so each key is truncated to 16 hex + # chars (64 bits). Pin that bound so a revert can't silently re-break + # Windows record creation while Linux CI still passes. + assert len(SandboxQuarantine._key("thread-sandbox")) == 16 + assert len(SandboxQuarantine._key("provisioner-a")) == 16 + assert len(SandboxQuarantine._key("old-generation")) == 16