Files
wehub-resource-sync c3749daf48
Tests / test-linux (3.13) (push) Failing after 0s
Tests / test-linux (3.11) (push) Failing after 1s
Tests / lint (push) Failing after 0s
Tests / test-linux (3.9) (push) Failing after 1s
Docker / build (push) Failing after 1s
Docker / build-gpu (push) Failing after 2s
Tests / test-windows (push) Has been cancelled
Tests / test-macos (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:03:03 +08:00

161 lines
5.9 KiB
Python

"""Tests for the turnkey `mempalace serve` command (#1877).
These exercise the wrapper's security-relevant behavior — token autogeneration
and 0600 persistence, the secure-by-default non-loopback gate, and that the
bearer token is passed via the environment (never argv, so it can't leak via
``ps``) — without binding a real socket. ``cmd_serve`` ends by exec'ing the real
server; we intercept ``os.execve`` to capture the child invocation instead.
"""
import argparse
import os
import stat
import pytest
from mempalace import cli
class _ExecCalled(Exception):
"""Raised by the patched os.execve to stop cmd_serve at the exec boundary."""
@pytest.fixture
def isolated_home(tmp_path, monkeypatch):
"""Point ~ at a temp dir so server token state never touches the real home."""
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path)) # Windows
# Don't inherit a token from the ambient environment.
monkeypatch.delenv("MEMPALACE_MCP_HTTP_TOKEN", raising=False)
return tmp_path
@pytest.fixture
def capture_exec(monkeypatch):
"""Capture the child argv/env cmd_serve would launch, instead of running it.
cmd_serve takes the os.execve branch on POSIX and the subprocess.run branch
on Windows. Patch both (rather than forcing os.name, which breaks
Path.home() on Windows) so the test is platform-agnostic.
"""
import subprocess
captured = {}
def _capture(argv, env):
captured["argv"] = argv
captured["env"] = env
raise _ExecCalled()
monkeypatch.setattr(cli.os, "execve", lambda path, argv, env: _capture(argv, env))
monkeypatch.setattr(subprocess, "run", lambda argv, env=None, **kw: _capture(argv, env))
return captured
def _serve_args(tmp_path, **over):
base = dict(
host="127.0.0.1",
port=8765,
backend=None,
global_backend=None,
palace=str(tmp_path / "palace"),
token=None,
tls_cert=None,
tls_key=None,
read_only=False,
allow_insecure=False,
)
base.update(over)
return argparse.Namespace(**base)
def test_token_helper_creates_0600_and_reuses(isolated_home):
palace = str(isolated_home / "palace")
token1, created1 = cli._load_or_create_server_token(palace)
assert created1 is True
assert token1
path = cli._server_token_path(palace)
assert path.exists()
if os.name == "posix":
# POSIX permission bits aren't meaningful on Windows (files report 0o666).
mode = stat.S_IMODE(path.stat().st_mode)
assert mode == 0o600, oct(mode)
dir_mode = stat.S_IMODE(path.parent.stat().st_mode)
assert dir_mode == 0o700, oct(dir_mode)
token2, created2 = cli._load_or_create_server_token(palace)
assert created2 is False
assert token2 == token1 # stable across restarts
def test_loopback_serve_needs_no_token(isolated_home, capture_exec):
with pytest.raises(_ExecCalled):
cli.cmd_serve(_serve_args(isolated_home, host="127.0.0.1"))
env = capture_exec["env"]
assert "MEMPALACE_MCP_HTTP_TOKEN" not in env
assert "MEMPALACE_MCP_HTTP_ALLOW_INSECURE_NO_TOKEN" not in env
# No token persisted for a loopback bind.
assert not cli._server_token_path(str(isolated_home / "palace")).exists()
def test_non_loopback_autogenerates_token_in_env_not_argv(isolated_home, capture_exec):
with pytest.raises(_ExecCalled):
cli.cmd_serve(_serve_args(isolated_home, host="0.0.0.0"))
env = capture_exec["env"]
argv = capture_exec["argv"]
token = env.get("MEMPALACE_MCP_HTTP_TOKEN")
assert token, "a token must be generated for a network-exposed bind"
# Security: the token rides in the env, never on the command line.
assert all(token not in part for part in argv)
assert "--token" not in argv
# And it was persisted for reuse on the next start (0600 on POSIX).
path = cli._server_token_path(str(isolated_home / "palace"))
assert path.exists()
if os.name == "posix":
assert stat.S_IMODE(path.stat().st_mode) == 0o600
def test_allow_insecure_skips_token_and_sets_escape_hatch(isolated_home, capture_exec):
with pytest.raises(_ExecCalled):
cli.cmd_serve(_serve_args(isolated_home, host="0.0.0.0", allow_insecure=True))
env = capture_exec["env"]
assert env.get("MEMPALACE_MCP_HTTP_ALLOW_INSECURE_NO_TOKEN") == "1"
assert "MEMPALACE_MCP_HTTP_TOKEN" not in env
assert not cli._server_token_path(str(isolated_home / "palace")).exists()
def test_read_only_flag_forwarded_to_child(isolated_home, capture_exec):
with pytest.raises(_ExecCalled):
cli.cmd_serve(_serve_args(isolated_home, read_only=True))
assert "--read-only" in capture_exec["argv"]
def test_explicit_token_is_used_and_not_in_argv(isolated_home, capture_exec):
with pytest.raises(_ExecCalled):
cli.cmd_serve(_serve_args(isolated_home, host="0.0.0.0", token="my-secret-token"))
env = capture_exec["env"]
argv = capture_exec["argv"]
assert env["MEMPALACE_MCP_HTTP_TOKEN"] == "my-secret-token"
assert all("my-secret-token" not in part for part in argv)
# An explicitly-provided token is not persisted to the server token file.
assert not cli._server_token_path(str(isolated_home / "palace")).exists()
def test_tls_paths_forwarded_and_validated(isolated_home, capture_exec, tmp_path):
cert = tmp_path / "cert.pem"
key = tmp_path / "key.pem"
cert.write_text("x")
key.write_text("x")
with pytest.raises(_ExecCalled):
cli.cmd_serve(_serve_args(isolated_home, tls_cert=str(cert), tls_key=str(key)))
argv = capture_exec["argv"]
assert "--tls-cert" in argv and "--tls-key" in argv
def test_tls_requires_both_cert_and_key(isolated_home, capture_exec, tmp_path):
cert = tmp_path / "cert.pem"
cert.write_text("x")
with pytest.raises(SystemExit):
cli.cmd_serve(_serve_args(isolated_home, tls_cert=str(cert), tls_key=None))