Files
Martin Vogel 52ab4e5d4c fix(pkg): apply the Go wrapper's archive size limits to npm and PyPI
The Go wrapper caps the compressed release download at 256 MiB and
bounds extraction to 64 archive members, 256 MiB per member and 512 MiB
expanded in total. The npm and PyPI wrappers had the download cap
parameter but never passed it for the release archive, and extracted
members without any size bound.

Both wrappers now use the same four limits as named constants. The
download rejects an over-limit Content-Length before writing (only
plain ASCII digits count as a length), stops as soon as the received
body exceeds the limit, and removes the partial file.

The PyPI extractor checks the member count and the declared sizes
before writing and copies each member through a counted loop that reads
at most one byte past the limit and compares the actual size with the
declared one.

The npm tar path keeps the system tar listing and the exact four-name
check as the member bound, then extracts only the executable through
`tar -xzOf` piped into a file the wrapper creates itself, counting
bytes: past the per-member limit the child is stopped and the file
removed; a non-zero tar exit carries tar's own diagnostic text; an
empty result is a failure. The file is removed only when this run
created it, so a pre-existing target is never deleted. The companions
are listed but never written. The PowerShell zip path counts entries
and declared sizes and copies the executable through a counted loop
instead of ExtractToFile, with its own failure text in the install
error. Any limit hit fails with a message naming the limit and leaves
no partial output behind.

Tests cover an over-limit download (declared, actual, and non-numeric
declared lengths), a member stopped in the counted writer, a
pre-existing target left untouched, tar failing mid-stream, an empty
member, too many members and over-limit declared sizes for zip, and a
normal archive installing byte-identical with the executable bit set.
The PowerShell cases run on Windows hosts only.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-02 01:10:56 +02:00

1132 lines
45 KiB
Python

import errno
import hashlib
import io
import json
import os
import stat
import subprocess
import sys
import tarfile
import tempfile
import threading
import time
import unittest
import zipfile
from pathlib import Path, PureWindowsPath
from unittest import mock
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PACKAGE_ROOT / "src"))
from codebase_memory_mcp import _cli # noqa: E402
class BinarySelectionTests(unittest.TestCase):
"""One binary ships per platform: the cached file is the executed file."""
def test_windows_executes_the_cached_binary(self):
binary = Path("cache") / "0.8.1" / _cli._WINDOWS_BINARY_NAME
self.assertEqual(_cli._execution_path(binary, "win32"), binary)
def test_non_windows_executes_the_cached_binary(self):
binary = Path("cache") / "0.8.1" / "codebase-memory-mcp"
self.assertEqual(_cli._execution_path(binary, "linux"), binary)
def test_cache_sensitive_mutation_classification_is_explicit(self):
self.assertEqual(_cli._runtime_mutation_action(["update"]), "update")
self.assertEqual(
_cli._runtime_mutation_action(["uninstall", "--yes"]),
"uninstall",
)
self.assertEqual(
_cli._runtime_mutation_action(["install", "--yes"]), "install"
)
self.assertIsNone(_cli._runtime_mutation_action(["cli", "update"]))
self.assertIsNone(_cli._runtime_mutation_action(["daemon", "update"]))
def test_wrapper_uninstall_never_defaults_to_its_cache_binary(self):
linux_home = Path("fixtures") / "linux-home"
windows_local_app_data = Path("fixtures") / "windows-local-app-data"
defaults = (
(
"linux",
linux_home / ".local" / "bin",
mock.patch.object(Path, "home", return_value=linux_home),
),
(
"win32",
windows_local_app_data
/ "Programs"
/ "codebase-memory-mcp",
mock.patch.dict(
os.environ,
{"LOCALAPPDATA": str(windows_local_app_data)},
),
),
)
for platform_name, expected, path_fixture in defaults:
with self.subTest(platform=platform_name), mock.patch.object(
_cli.sys, "platform", platform_name
), path_fixture:
self.assertEqual(
_cli._native_args(["uninstall", "--yes"]),
["uninstall", "--yes", "--dir", str(expected)],
)
explicit_dir = Path("fixtures") / "explicit-install"
for platform_name in ("linux", "win32"):
with self.subTest(
platform=platform_name, explicit_dir=True
), mock.patch.object(_cli.sys, "platform", platform_name):
self.assertEqual(
_cli._native_args(
["uninstall", "--dir", str(explicit_dir)]
),
["uninstall", "--dir", str(explicit_dir)],
)
def test_release_archives_carry_no_sidecars(self):
# The binary is self-contained; a returning sidecar would silently make
# `pip install` reject every archive against this exact-set allowlist.
for names in (_cli._WINDOWS_ARCHIVE_NAMES, _cli._UNIX_ARCHIVE_NAMES):
self.assertNotIn("cbm-integrations.json", names)
self.assertFalse([n for n in names if n.startswith("cbm-ui-")])
self.assertEqual(len(names), 4)
def test_update_guidance_uses_pip_and_names_managed_install(self):
with mock.patch.object(_cli.sys, "argv", ["cbm", "update"]), \
mock.patch.object(_cli, "_version", return_value="0.8.1"), \
mock.patch("sys.stderr") as stderr:
with self.assertRaisesRegex(SystemExit, "2"):
_cli.main()
output = "".join(call.args[0] for call in stderr.write.call_args_list)
self.assertIn(
"python -m pip install --upgrade codebase-memory-mcp", output
)
self.assertIn("codebase-memory-mcp install --yes", output)
self.assertNotIn("install.sh", output)
class ProcessLivenessTests(unittest.TestCase):
def test_windows_probe_never_calls_os_kill(self):
with mock.patch.object(_cli.os, "name", "nt"), mock.patch.object(
_cli,
"_windows_process_is_alive",
return_value=True,
create=True,
) as windows_probe, mock.patch.object(
_cli.os,
"kill",
side_effect=AssertionError("Windows must not call os.kill"),
):
self.assertTrue(_cli._process_is_alive(1234))
windows_probe.assert_called_once_with(1234)
def test_windows_wait_result_distinguishes_dead_and_live_processes(self):
cases = (
(_cli._WINDOWS_WAIT_OBJECT_0, False),
(_cli._WINDOWS_WAIT_TIMEOUT, True),
(0xFFFFFFFF, True),
)
for wait_result, expected in cases:
with self.subTest(wait_result=wait_result):
kernel32 = mock.Mock()
kernel32.OpenProcess.return_value = 123
kernel32.WaitForSingleObject.return_value = wait_result
with mock.patch.object(
_cli,
"_windows_process_api",
return_value=(kernel32, mock.Mock()),
):
self.assertEqual(
_cli._windows_process_is_alive(4567), expected
)
kernel32.OpenProcess.assert_called_once_with(
_cli._WINDOWS_SYNCHRONIZE, False, 4567
)
kernel32.WaitForSingleObject.assert_called_once_with(123, 0)
kernel32.CloseHandle.assert_called_once_with(123)
def test_windows_open_failure_only_treats_missing_pid_as_dead(self):
cases = (
(_cli._WINDOWS_ERROR_INVALID_PARAMETER, False),
(5, True),
)
for error, expected in cases:
with self.subTest(error=error):
kernel32 = mock.Mock()
kernel32.OpenProcess.return_value = 0
get_last_error = mock.Mock(return_value=error)
with mock.patch.object(
_cli,
"_windows_process_api",
return_value=(kernel32, get_last_error),
):
self.assertEqual(
_cli._windows_process_is_alive(4567), expected
)
get_last_error.assert_called_once_with()
kernel32.WaitForSingleObject.assert_not_called()
kernel32.CloseHandle.assert_not_called()
def test_windows_api_failures_are_conservatively_live(self):
kernel32 = mock.Mock()
kernel32.OpenProcess.side_effect = OSError("injected API failure")
with mock.patch.object(
_cli,
"_windows_process_api",
return_value=(kernel32, mock.Mock()),
):
self.assertTrue(_cli._windows_process_is_alive(4567))
def test_posix_probe_behavior_is_unchanged(self):
cases = (
(None, True),
(ProcessLookupError(), False),
(PermissionError(), True),
(OSError(errno.ESRCH, "missing"), False),
)
for error, expected in cases:
with self.subTest(error=type(error).__name__):
side_effect = error if error is not None else None
with mock.patch.object(_cli.os, "name", "posix"), \
mock.patch.object(
_cli.os, "kill", side_effect=side_effect
) as kill:
self.assertEqual(_cli._process_is_alive(4567), expected)
kill.assert_called_once_with(4567, 0)
class RuntimeSetTests(unittest.TestCase):
def test_candidate_probe_requires_the_binary_to_execute(self):
candidates = (
Path("/tmp/codebase-memory-mcp"),
PureWindowsPath("/tmp/codebase-memory-mcp"),
)
for candidate in candidates:
with self.subTest(candidate=candidate):
with mock.patch.object(_cli.subprocess, "run") as run:
run.return_value.returncode = 0
_cli._verify_candidate(candidate)
self.assertEqual(
run.call_args.args[0],
[str(candidate), "--version"],
)
def test_concurrent_publishers_preserve_the_first_complete_winner(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
binary_name = "codebase-memory-mcp"
def stages(tag):
staged = directory / f".stage-{tag}-{binary_name}"
staged.write_bytes(f"binary:{tag}".encode())
return {binary_name: staged}
first_stages = stages("first")
second_stages = stages("second")
first_holds_lock = threading.Event()
allow_first = threading.Event()
second_observed_lock = threading.Event()
errors = []
replace = os.replace
read_lock_owner = _cli._read_runtime_lock_owner
def pause_first(source, destination):
replace(source, destination)
if Path(destination) == directory / binary_name:
first_holds_lock.set()
if not allow_first.wait(5):
raise RuntimeError("test did not release first publisher")
def observe_waiter(lock_path):
if threading.current_thread().name == "publisher-second":
second_observed_lock.set()
return read_lock_owner(lock_path)
def publish(label, staged_paths, replace_file=None):
try:
_cli._publish_runtime_set(
staged_paths,
directory / binary_name,
binary_name,
replace_file=replace_file,
)
except Exception as exc: # surfaced on the test thread below
errors.append((label, exc))
first = threading.Thread(
name="publisher-first",
target=publish,
args=("first", first_stages, pause_first),
)
second = threading.Thread(
name="publisher-second",
target=publish,
args=("second", second_stages),
)
with mock.patch.object(
_cli,
"_read_runtime_lock_owner",
side_effect=observe_waiter,
):
first.start()
try:
self.assertTrue(first_holds_lock.wait(2))
second.start()
self.assertTrue(second_observed_lock.wait(2))
finally:
allow_first.set()
first.join(5)
if second.ident is not None:
second.join(5)
self.assertFalse(first.is_alive())
self.assertFalse(second.is_alive())
self.assertEqual(errors, [])
self.assertEqual(
(directory / binary_name).read_bytes(), b"binary:first"
)
self.assertFalse((directory / _cli._RUNTIME_LOCK_NAME).exists())
def test_killed_publisher_is_reconciled_by_locked_readiness(self):
helper_source = r"""
import os
import sys
import time
from pathlib import Path
source_root, directory_raw, staged_raw, marker_raw, crash_name = sys.argv[1:]
sys.path.insert(0, source_root)
from codebase_memory_mcp import _cli
directory = Path(directory_raw)
staged = Path(staged_raw)
marker = Path(marker_raw)
binary_name = "codebase-memory-mcp"
staged_paths = {
binary_name: staged / binary_name,
}
replace = os.replace
def pausing_replace(source, destination):
replace(source, destination)
if Path(destination) == directory / crash_name:
marker.write_text("reached\n")
while True:
time.sleep(60)
def verifier(binary):
if not Path(binary).read_text().startswith("binary:"):
raise RuntimeError("test binary failed verification")
_cli._publish_runtime_set(
staged_paths,
directory / binary_name,
binary_name,
replace_file=pausing_replace,
verifier=verifier,
)
"""
binary_name = "codebase-memory-mcp"
for crash_name, expected_ready in (
(binary_name, True),
):
with self.subTest(crash_name=crash_name), tempfile.TemporaryDirectory() as root:
root_path = Path(root)
directory = root_path / "destination"
staged = root_path / "staged"
marker = root_path / "crash-reached"
directory.mkdir()
staged.mkdir()
(directory / binary_name).write_text("corrupt:old")
(staged / binary_name).write_text("binary:candidate")
environment = os.environ.copy()
environment["PYTHONPYCACHEPREFIX"] = str(root_path / "pycache")
process = subprocess.Popen(
[
sys.executable,
"-c",
helper_source,
str(PACKAGE_ROOT / "src"),
str(directory),
str(staged),
str(marker),
crash_name,
],
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
env=environment,
)
try:
deadline = time.monotonic() + 10
while not marker.exists() and time.monotonic() < deadline:
if process.poll() is not None:
stderr = process.communicate()[1]
self.fail(
"publication helper exited before crash gate: "
f"{stderr}"
)
time.sleep(0.01)
self.assertTrue(
marker.exists(),
"publication helper did not reach crash gate",
)
backups = [
path
for path in directory.iterdir()
if path.name.startswith(".cbm-runtime-backup-")
]
self.assertEqual(len(backups), 1)
self.assertTrue(backups[0].is_dir())
self.assertTrue(
(backups[0] / ".retirement-complete").is_file()
)
process.kill()
process.wait(5)
self.assertNotEqual(process.returncode, 0)
def verifier(binary):
if not Path(binary).read_text().startswith("binary:"):
raise RuntimeError("test binary failed verification")
with mock.patch.object(
_cli, "_bin_path", return_value=directory / binary_name
), mock.patch.object(
_cli, "_verify_candidate", side_effect=verifier
):
ready = _cli._runtime_set_ready_locked("test-version")
self.assertEqual(ready, expected_ready)
if not ready:
self.assertEqual(
(directory / binary_name).read_text(),
"corrupt:old",
)
retry_staged = root_path / "retry-staged"
retry_staged.mkdir()
retry_paths = {
binary_name: retry_staged / binary_name,
}
retry_paths[binary_name].write_text("binary:candidate")
_cli._publish_runtime_set(
retry_paths,
directory / binary_name,
binary_name,
verifier=verifier,
)
self.assertEqual(
(directory / binary_name).read_text(), "binary:candidate"
)
self.assertFalse(
any(
path.name.startswith(".cbm-runtime-backup-")
for path in directory.iterdir()
)
)
self.assertFalse(
(directory / _cli._RUNTIME_LOCK_NAME).exists()
)
finally:
if process.poll() is None:
process.kill()
process.wait(5)
if process.stderr is not None:
process.stderr.close()
def test_publication_failure_restores_prior_complete_runtime_set(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
binary_name = "codebase-memory-mcp"
(directory / binary_name).write_text("binary:old")
staged_paths = {}
for name, contents in (
(binary_name, b"binary:candidate"),
):
staged = directory / f"stage-{name}"
staged.write_bytes(contents)
staged_paths[name] = staged
replace = os.replace
failed = False
retired = []
def fail_binary_publish(source, destination):
nonlocal failed
source = Path(source)
destination = Path(destination)
if destination.parent.name.startswith(".cbm-runtime-backup-"):
retired.append(source.name)
if destination == directory / binary_name and not failed:
failed = True
raise OSError("injected binary publication failure")
replace(source, destination)
with self.assertRaises(OSError):
_cli._publish_runtime_set(
staged_paths,
directory / binary_name,
binary_name,
replace_file=fail_binary_publish,
)
self.assertTrue(failed)
self.assertEqual(retired[0], binary_name)
self.assertEqual((directory / binary_name).read_text(), "binary:old")
self.assertFalse(
any(
path.name.startswith(".cbm-runtime-backup-")
for path in directory.iterdir()
)
)
def test_failure_never_deletes_a_complete_foreign_winner(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
binary_name = "codebase-memory-mcp"
(directory / binary_name).write_bytes(b"binary:old")
staged_paths = {}
for name, contents in (
(binary_name, b"binary:candidate"),
):
staged = directory / f".stage-candidate-{name}"
staged.write_bytes(contents)
staged_paths[name] = staged
replace = os.replace
injected = False
def install_foreign_winner(source, destination):
nonlocal injected
source = Path(source)
destination = Path(destination)
if destination == directory / binary_name and not injected:
injected = True
(directory / binary_name).write_bytes(b"binary:foreign")
raise OSError("simulated non-cooperating winner")
replace(source, destination)
_cli._publish_runtime_set(
staged_paths,
directory / binary_name,
binary_name,
replace_file=install_foreign_winner,
)
self.assertTrue(injected)
self.assertEqual(
(directory / binary_name).read_bytes(), b"binary:foreign"
)
self.assertFalse(
any(
path.name.startswith(".cbm-runtime-backup-")
for path in directory.iterdir()
)
)
def test_orphan_reconciliation_rejects_multiply_linked_backup_members(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
binary = directory / "codebase-memory-mcp"
backup = directory / f".cbm-runtime-backup-{'a' * 32}"
backup.mkdir()
(backup / ".retirement-complete").write_bytes(b"")
member = backup / binary.name
member.write_text("binary:old")
os.link(member, directory / "backup-hardlink-copy")
with mock.patch.object(
_cli, "_bin_path", return_value=binary
), self.assertRaisesRegex(
RuntimeError, "unsafe package-cache backup member"
):
_cli._runtime_set_ready_locked("test-version")
self.assertTrue(backup.exists(), "unsafe backup was mutated")
self.assertFalse((directory / _cli._RUNTIME_LOCK_NAME).exists())
def test_stalled_lock_creator_never_deletes_successor(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
successor = None
def publish_successor_then_abort():
nonlocal successor
_cli._runtime_lock_claim_observer = None
successor, _ = _cli._acquire_runtime_lock(directory)
raise RuntimeError("injected stalled creator abort")
with mock.patch.object(
_cli,
"_runtime_lock_claim_observer",
side_effect=publish_successor_then_abort,
), self.assertRaisesRegex(RuntimeError, "stalled creator abort"):
_cli._acquire_runtime_lock(directory)
self.assertIsNotNone(successor)
self.assertTrue(successor[0].exists())
_cli._release_runtime_lock(successor)
def test_acquire_closes_claim_descriptor_before_unlink_and_reopens(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
captured = {}
write_owner = _cli._write_runtime_lock_owner
unlink = Path.unlink
def capture_claim_descriptor(owner_fd, token):
captured["claim_fd"] = owner_fd
write_owner(owner_fd, token)
def require_closed_claim_descriptor(path, *args, **kwargs):
if f"{_cli._RUNTIME_LOCK_NAME}.claim-" in path.name:
with self.assertRaises(OSError):
os.fstat(captured["claim_fd"])
captured["closed_before_unlink"] = True
return unlink(path, *args, **kwargs)
with mock.patch.object(
_cli,
"_write_runtime_lock_owner",
side_effect=capture_claim_descriptor,
), mock.patch.object(
Path, "unlink", new=require_closed_claim_descriptor
):
lock, _ = _cli._acquire_runtime_lock(directory)
try:
self.assertTrue(captured["closed_before_unlink"])
self.assertTrue(
_cli._same_runtime_lock_object(
os.fstat(lock[2]), lock[0].lstat()
)
)
finally:
_cli._release_runtime_lock(lock)
def test_release_closes_descriptor_and_revalidates_each_lock_name(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
lock, _ = _cli._acquire_runtime_lock(directory)
original_fd = lock[2]
events = []
read_owner = _cli._read_runtime_lock_owner
rename = os.rename
def record_owner_validation(path):
events.append(("validate", Path(path).name))
return read_owner(path)
def require_closed_descriptor(source, destination):
with self.assertRaises(OSError):
os.fstat(original_fd)
events.append(("rename", Path(source).name))
return rename(source, destination)
with mock.patch.object(
_cli,
"_read_runtime_lock_owner",
side_effect=record_owner_validation,
), mock.patch.object(
_cli.os, "rename", side_effect=require_closed_descriptor
):
_cli._release_runtime_lock(lock)
self.assertEqual(
events,
[
("validate", _cli._RUNTIME_LOCK_NAME),
("validate", _cli._RUNTIME_LOCK_NAME),
("rename", _cli._RUNTIME_LOCK_NAME),
(
"validate",
f"{_cli._RUNTIME_LOCK_NAME}.released-{lock[1]}",
),
],
)
self.assertFalse(lock[0].exists())
def test_release_rejects_owner_substitution_before_rename(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
lock, _ = _cli._acquire_runtime_lock(directory)
read_owner = _cli._read_runtime_lock_owner
canonical_reads = 0
def substitute_owner_on_reopen(path):
nonlocal canonical_reads
owner = read_owner(path)
if Path(path) == lock[0]:
canonical_reads += 1
if canonical_reads == 2:
return (os.getpid(), "f" * 32, owner[2])
return owner
with mock.patch.object(
_cli,
"_read_runtime_lock_owner",
side_effect=substitute_owner_on_reopen,
):
with self.assertRaisesRegex(RuntimeError, "ownership changed"):
_cli._release_runtime_lock(lock)
self.assertTrue(lock[0].exists())
lock[0].unlink()
def test_release_rejects_object_substitution_before_rename(self):
with tempfile.TemporaryDirectory() as root:
directory = Path(root)
lock, _ = _cli._acquire_runtime_lock(directory)
close = os.close
substituted = False
def substitute_object_after_owner_close(owner_fd):
nonlocal substituted
if (
owner_fd != lock[2]
or substituted
or not lock[0].exists()
):
close(owner_fd)
return
owner_record = lock[0].read_bytes()
replacement = directory / "replacement-lock"
replacement.write_bytes(owner_record)
try:
close(owner_fd)
os.replace(replacement, lock[0])
substituted = True
finally:
replacement.unlink(missing_ok=True)
with mock.patch.object(
_cli.os,
"close",
side_effect=substitute_object_after_owner_close,
):
with self.assertRaisesRegex(RuntimeError, "ownership changed"):
_cli._release_runtime_lock(lock)
self.assertTrue(substituted)
self.assertTrue(lock[0].exists())
lock[0].unlink()
def test_expired_lease_never_reclaims_live_owner(self):
with tempfile.TemporaryDirectory() as root:
lock_path = Path(root) / _cli._RUNTIME_LOCK_NAME
owner = {
"pid": os.getpid(),
"token": "a" * 32,
"lease_expires_ms": int((time.time() - 1) * 1000),
}
owner_record = f"{json.dumps(owner)}\n"
lock_path.write_text(owner_record)
before = lock_path.stat()
with mock.patch.object(_cli, "_process_is_alive", return_value=True):
self.assertEqual(
_cli._try_reclaim_runtime_lock(lock_path, "b" * 32),
(False, False),
)
after = lock_path.stat()
self.assertEqual(lock_path.read_text(), owner_record)
self.assertEqual(json.loads(lock_path.read_text())["token"], owner["token"])
self.assertEqual(after.st_dev, before.st_dev)
self.assertEqual(after.st_ino, before.st_ino)
def test_stale_legacy_lock_never_reclaims_live_owner(self):
with tempfile.TemporaryDirectory() as root:
lock_path = Path(root) / _cli._RUNTIME_LOCK_NAME
owner_record = json.dumps(
{"pid": os.getpid(), "token": "a" * 32}
) + "\n"
lock_path.mkdir()
(lock_path / "owner.json").write_text(owner_record)
stale = time.time() - _cli._RUNTIME_LEGACY_STALE_SECONDS - 1
os.utime(lock_path, (stale, stale))
before = lock_path.stat()
with mock.patch.object(_cli, "_process_is_alive", return_value=True):
self.assertEqual(
_cli._try_reclaim_runtime_lock(lock_path, "b" * 32),
(False, False),
)
after = lock_path.stat()
self.assertEqual((lock_path / "owner.json").read_text(), owner_record)
self.assertEqual(after.st_dev, before.st_dev)
self.assertEqual(after.st_ino, before.st_ino)
def test_stale_ownerless_lock_is_reclaimed(self):
with tempfile.TemporaryDirectory() as root:
lock_path = Path(root) / _cli._RUNTIME_LOCK_NAME
lock_path.write_text("")
stale = time.time() - _cli._RUNTIME_OWNERLESS_STALE_SECONDS - 1
os.utime(lock_path, (stale, stale))
self.assertEqual(
_cli._try_reclaim_runtime_lock(lock_path, "b" * 32),
(True, False),
)
self.assertFalse(lock_path.exists())
def test_unix_tar_rejects_an_unexpected_root_member(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "release.tar.gz"
with tarfile.open(archive, "w:gz") as tf:
for name in (*_cli._UNIX_ARCHIVE_NAMES, "unexpected-root-file"):
data_path = Path(root) / f"fixture-{len(tf.getmembers())}"
data_path.write_bytes(name.encode())
tf.add(data_path, arcname=name)
destination = Path(root) / "extract"
destination.mkdir()
with tarfile.open(archive) as tf, self.assertRaises(SystemExit):
_cli._safe_extract_tar(
tf,
str(destination),
_cli._UNIX_ARCHIVE_NAMES,
("codebase-memory-mcp",),
)
def test_unix_tar_rejects_hardlink_members(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "release.tar.gz"
with tarfile.open(archive, "w:gz") as tf:
info = tarfile.TarInfo("codebase-memory-mcp")
info.type = tarfile.LNKTYPE
info.linkname = "LICENSE"
tf.addfile(info)
destination = Path(root) / "extract"
destination.mkdir()
with tarfile.open(archive) as tf, self.assertRaises(SystemExit):
_cli._safe_extract_tar(
tf,
str(destination),
("codebase-memory-mcp",),
("codebase-memory-mcp",),
False,
)
def test_windows_zip_rejects_symlink_metadata(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "release.zip"
with zipfile.ZipFile(archive, "w") as zf:
info = zipfile.ZipInfo(_cli._WINDOWS_BINARY_NAME)
info.create_system = 3
info.external_attr = (stat.S_IFLNK | 0o777) << 16
zf.writestr(info, "LICENSE")
destination = Path(root) / "extract"
destination.mkdir()
with zipfile.ZipFile(archive) as zf, self.assertRaises(SystemExit):
_cli._safe_extract_zip(
zf,
str(destination),
(_cli._WINDOWS_BINARY_NAME,),
(_cli._WINDOWS_BINARY_NAME,),
False,
)
class _FakeHttpsResponse:
"""Minimal stand-in for the opener's response: headers, geturl, read."""
def __init__(self, body: bytes, headers=None):
self._body = body
self._offset = 0
self.headers = dict(headers or {})
def __enter__(self):
return self
def __exit__(self, *exc_info):
return False
def geturl(self):
return "https://example.invalid/release.tar.gz"
def read(self, size=-1):
if size is None or size < 0:
size = len(self._body) - self._offset
chunk = self._body[self._offset : self._offset + size]
self._offset += len(chunk)
return chunk
def _limits_with(**overrides):
return _cli._DEFAULT_ARCHIVE_LIMITS._replace(**overrides)
def _write_tar_gz(archive, entries):
with tarfile.open(archive, "w:gz") as tf:
for name, data in entries:
info = tarfile.TarInfo(name)
info.size = len(data)
tf.addfile(info, io.BytesIO(data))
def _write_zip(archive, entries):
with zipfile.ZipFile(archive, "w") as zf:
for name, data in entries:
zf.writestr(name, data)
def _release_entries(names):
return [(name, f"payload of {name}".encode()) for name in names]
class ArchiveLimitTests(unittest.TestCase):
URL = "https://example.invalid/release.tar.gz"
def _download(self, response, destination, max_bytes):
with mock.patch.object(_cli._HTTPS_OPENER, "open", return_value=response):
_cli._download_https(self.URL, destination, max_bytes)
def test_download_rejects_a_declared_length_over_the_limit(self):
with tempfile.TemporaryDirectory() as root:
destination = os.path.join(root, "release.tar.gz")
response = _FakeHttpsResponse(b"", {"Content-Length": "9"})
with self.assertRaises(RuntimeError) as raised:
self._download(response, destination, 8)
self.assertIn("8-byte safety limit", str(raised.exception))
self.assertFalse(os.path.exists(destination))
def test_download_rejects_a_body_over_the_limit_and_removes_the_partial_file(
self,
):
with tempfile.TemporaryDirectory() as root:
destination = os.path.join(root, "release.tar.gz")
response = _FakeHttpsResponse(b"x" * 1024)
with self.assertRaises(RuntimeError) as raised:
self._download(response, destination, 8)
self.assertIn("8-byte safety limit", str(raised.exception))
self.assertFalse(os.path.exists(destination))
def test_download_within_the_limit_stores_the_body(self):
with tempfile.TemporaryDirectory() as root:
destination = os.path.join(root, "release.tar.gz")
response = _FakeHttpsResponse(b"payload", {"Content-Length": "7"})
self._download(response, destination, 1024)
with open(destination, "rb") as stored:
self.assertEqual(stored.read(), b"payload")
def test_download_counts_bytes_when_the_declared_length_is_not_a_number(self):
# A superscript two passes str.isdigit but not int(); such values and
# plain junk must fall through to the byte counter, never crash.
for declared in ("²", "abc", "", "1e3", "-1"):
with self.subTest(declared=declared), tempfile.TemporaryDirectory() as root:
destination = os.path.join(root, "release.tar.gz")
response = _FakeHttpsResponse(b"x" * 1024, {"Content-Length": declared})
with self.assertRaises(RuntimeError) as raised:
self._download(response, destination, 8)
self.assertIn("8-byte safety limit", str(raised.exception))
self.assertFalse(os.path.exists(destination))
with tempfile.TemporaryDirectory() as root:
destination = os.path.join(root, "release.tar.gz")
response = _FakeHttpsResponse(b"payload", {"Content-Length": "²"})
self._download(response, destination, 1024)
with open(destination, "rb") as stored:
self.assertEqual(stored.read(), b"payload")
def test_unix_tar_rejects_more_members_than_the_limit(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "members.tar.gz"
_write_tar_gz(archive, [("one", b"a"), ("two", b"b"), ("three", b"c")])
destination = Path(root) / "extract"
destination.mkdir()
with tarfile.open(archive) as tf, self.assertRaises(SystemExit) as raised:
_cli._safe_extract_tar(
tf,
str(destination),
("one", "two", "three"),
("one",),
limits=_limits_with(members=2),
)
self.assertIn("2-member safety limit", str(raised.exception))
self.assertFalse((destination / "one").exists())
def test_unix_tar_rejects_a_member_declared_over_the_limit(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "member.tar.gz"
_write_tar_gz(archive, [("codebase-memory-mcp", b"x" * 65)])
destination = Path(root) / "extract"
destination.mkdir()
with tarfile.open(archive) as tf, self.assertRaises(SystemExit) as raised:
_cli._safe_extract_tar(
tf,
str(destination),
("codebase-memory-mcp",),
("codebase-memory-mcp",),
limits=_limits_with(member_bytes=64),
)
self.assertIn(
"'codebase-memory-mcp' exceeds the 64-byte expanded safety limit",
str(raised.exception),
)
self.assertFalse((destination / "codebase-memory-mcp").exists())
def test_unix_tar_rejects_a_declared_aggregate_over_the_limit(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "aggregate.tar.gz"
_write_tar_gz(archive, [("one", b"a" * 40), ("two", b"b" * 40)])
destination = Path(root) / "extract"
destination.mkdir()
with tarfile.open(archive) as tf, self.assertRaises(SystemExit) as raised:
_cli._safe_extract_tar(
tf,
str(destination),
("one", "two"),
("one",),
limits=_limits_with(member_bytes=64, expanded_bytes=64),
)
self.assertIn(
"64-byte aggregate expanded safety limit", str(raised.exception)
)
def test_unix_tar_within_the_limits_extracts_only_the_executable(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "release.tar.gz"
_write_tar_gz(archive, _release_entries(_cli._UNIX_ARCHIVE_NAMES))
destination = Path(root) / "extract"
destination.mkdir()
with tarfile.open(archive) as tf:
extracted = _cli._safe_extract_tar(
tf,
str(destination),
_cli._UNIX_ARCHIVE_NAMES,
("codebase-memory-mcp",),
)
self.assertEqual(extracted, ("codebase-memory-mcp",))
self.assertEqual(
(destination / "codebase-memory-mcp").read_bytes(),
b"payload of codebase-memory-mcp",
)
self.assertEqual(os.listdir(destination), ["codebase-memory-mcp"])
def test_archive_copy_stops_a_member_that_yields_more_than_the_limit(self):
source = io.BytesIO(b"x" * 100)
output = io.BytesIO()
with self.assertRaises(SystemExit) as raised:
_cli._copy_archive_member_within_limits(
source,
output,
"member",
100,
0,
_limits_with(member_bytes=64, expanded_bytes=1024),
)
self.assertIn(
"'member' exceeds the 64-byte actual expanded safety limit",
str(raised.exception),
)
# Reading stopped one byte past the limit; nothing beyond it was kept.
self.assertEqual(source.tell(), 65)
self.assertLessEqual(len(output.getvalue()), 64)
def test_archive_copy_rejects_a_member_that_yields_more_than_it_declares(self):
source = io.BytesIO(b"x" * 100)
with self.assertRaises(SystemExit) as raised:
_cli._copy_archive_member_within_limits(
source,
io.BytesIO(),
"member",
10,
0,
_limits_with(member_bytes=1024, expanded_bytes=1024),
)
self.assertIn(
"'member' actual size 100 does not match declared size 10",
str(raised.exception),
)
def test_archive_copy_rejects_an_aggregate_over_the_actual_limit(self):
with self.assertRaises(SystemExit) as raised:
_cli._copy_archive_member_within_limits(
io.BytesIO(b"abcd"),
io.BytesIO(),
"member",
4,
3,
_limits_with(member_bytes=10, expanded_bytes=5),
)
self.assertIn(
"5-byte aggregate actual expanded safety limit", str(raised.exception)
)
def test_archive_member_write_removes_the_partial_file_on_failure(self):
with tempfile.TemporaryDirectory() as root:
target = os.path.join(root, "codebase-memory-mcp")
with self.assertRaises(SystemExit):
_cli._write_archive_member_within_limits(
io.BytesIO(b"x" * 100),
target,
"codebase-memory-mcp",
10,
0,
_limits_with(member_bytes=1024, expanded_bytes=1024),
)
self.assertFalse(os.path.exists(target))
def test_windows_zip_rejects_more_members_than_the_limit(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "members.zip"
_write_zip(archive, [("one", b"a"), ("two", b"b"), ("three", b"c")])
destination = Path(root) / "extract"
destination.mkdir()
with zipfile.ZipFile(archive) as zf, self.assertRaises(SystemExit) as raised:
_cli._safe_extract_zip(
zf,
str(destination),
("one", "two", "three"),
("one",),
limits=_limits_with(members=2),
)
self.assertIn("2-member safety limit", str(raised.exception))
def test_windows_zip_rejects_a_member_declared_over_the_limit(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "member.zip"
_write_zip(archive, [(_cli._WINDOWS_BINARY_NAME, b"x" * 65)])
destination = Path(root) / "extract"
destination.mkdir()
with zipfile.ZipFile(archive) as zf, self.assertRaises(SystemExit) as raised:
_cli._safe_extract_zip(
zf,
str(destination),
(_cli._WINDOWS_BINARY_NAME,),
(_cli._WINDOWS_BINARY_NAME,),
limits=_limits_with(member_bytes=64),
)
self.assertIn("64-byte expanded safety limit", str(raised.exception))
self.assertFalse((destination / _cli._WINDOWS_BINARY_NAME).exists())
def test_windows_zip_within_the_limits_extracts_only_the_executable(self):
with tempfile.TemporaryDirectory() as root:
archive = Path(root) / "release.zip"
_write_zip(archive, _release_entries(_cli._WINDOWS_ARCHIVE_NAMES))
destination = Path(root) / "extract"
destination.mkdir()
with zipfile.ZipFile(archive) as zf:
extracted = _cli._safe_extract_zip(
zf,
str(destination),
_cli._WINDOWS_ARCHIVE_NAMES,
(_cli._WINDOWS_BINARY_NAME,),
)
self.assertEqual(extracted, (_cli._WINDOWS_BINARY_NAME,))
self.assertEqual(
(destination / _cli._WINDOWS_BINARY_NAME).read_bytes(),
b"payload of codebase-memory-mcp.exe",
)
self.assertEqual(os.listdir(destination), [_cli._WINDOWS_BINARY_NAME])