fix(issue-45): review round 2 R3 - the .mam fallback obeys the same no-overwrite rules

The fallback wrote <.mam>/herdr-server-config.toml unchecked, so it could
replace a foreign file there, the source itself, or a link to it. It now goes
through output_path like the normal path (source identity incl. aliases,
generated-for-this-source ownership, then a per-source name) and writes
nothing when no name is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-04 11:49:08 +00:00
1 parent 7c544c06ab
commit 35935f9e8a
3 files changed
+82 -11

No files matched your search

+68
View File
@@ -170,11 +170,79 @@ def test_unwritable_config_dir_falls_back_to_mam_dir(tmp_path):
assert path == str(mam / hc.FALLBACK_NAME)
assert any("relative paths" in w for w in warnings), warnings
assert tomllib.loads(Path(path).read_text())["ui"]["confirm_close"] is False
if HERDR:
assert hc.herdr_rejects(path, HERDR) is None
assert hc.write_server_config(_env(tmp_path))[0] is None # no fallback given
finally:
cfg_dir.chmod(0o755)
# --- the fallback obeys the same rules (review round 2, R3) ----------------
def _tag(source: Path) -> str:
import hashlib
return hashlib.sha256(os.path.realpath(source).encode()).hexdigest()[:12]
@pytest.mark.skipif(os.geteuid() == 0, reason="root ignores directory permissions")
def test_a_foreign_fallback_file_is_kept(tmp_path):
cfg_dir = tmp_path / "cfg"
cfg_dir.mkdir()
source = cfg_dir / "config.toml"
source.write_text("[ui]\nconfirm_close = false\n")
cfg_dir.chmod(0o555)
mam = tmp_path / ".mam"
mam.mkdir()
foreign = mam / hc.FALLBACK_NAME
foreign.write_bytes(b"# user-owned\n[ui]\nconfirm_close = true\n")
try:
path, warnings = hc.write_server_config({"HERDR_CONFIG_PATH": str(source)}, str(mam), HERDR)
finally:
cfg_dir.chmod(0o755)
assert foreign.read_bytes() == b"# user-owned\n[ui]\nconfirm_close = true\n"
assert path == str(mam / f"herdr-server-config-{_tag(source)}.toml"), warnings
assert tomllib.loads(Path(path).read_text())["ui"]["confirm_close"] is False
@pytest.mark.parametrize("alias", [False, True], ids=["same-path", "symlink-alias"])
def test_the_fallback_is_never_the_source(tmp_path, alias):
mam = tmp_path / ".mam"
mam.mkdir()
body = b"[ui]\nconfirm_close = false\n"
if alias:
source = tmp_path / "real.toml"
source.write_bytes(body)
(mam / hc.FALLBACK_NAME).symlink_to(source)
source_dir = tmp_path
else:
source = mam / hc.FALLBACK_NAME
source.write_bytes(body)
source_dir = mam
# both names beside the source are someone else's, so the fallback is used
for name in (hc.OUTPUT_NAME, f"mam-server-config-{_tag(source)}.toml"):
(source_dir / name).write_bytes(b"# foreign\n")
path, _ = hc.write_server_config({"HERDR_CONFIG_PATH": str(source)}, str(mam), HERDR)
assert source.read_bytes() == body
if alias: # os.replace would swap out the user's link itself
assert (mam / hc.FALLBACK_NAME).is_symlink()
assert path and not os.path.samefile(path, source)
assert tomllib.loads(Path(path).read_text())["ui"]["confirm_close"] is False
def test_no_write_when_every_name_is_taken(tmp_path):
mam = tmp_path / ".mam"
mam.mkdir()
source = tmp_path / "config.toml"
source.write_text("[ui]\nconfirm_close = false\n")
taken = [tmp_path / hc.OUTPUT_NAME, tmp_path / f"mam-server-config-{_tag(source)}.toml",
mam / hc.FALLBACK_NAME, mam / f"herdr-server-config-{_tag(source)}.toml"]
for f in taken:
f.write_bytes(b"# foreign\n")
path, warnings = hc.write_server_config({"HERDR_CONFIG_PATH": str(source)}, str(mam))
assert path is None and any("no free output name" in w for w in warnings), warnings
assert all(f.read_bytes() == b"# foreign\n" for f in taken)
def test_cli_prints_the_path(tmp_path):
env = dict(os.environ, HOME=str(tmp_path), PYTHONPATH=str(REPO_ROOT / ".agents" / "skills"))
env.pop("HERDR_CONFIG_PATH", None)