From 3186bd10ebddb4d750001ba0907906ebde080119 Mon Sep 17 00:00:00 2001 From: Guillaume Flambard Date: Sun, 18 Oct 2026 09:56:08 +0200 Subject: [PATCH] fix(lease): read the owner's home from the account, not $HOME Memo Bridge runs issue_preflight.py as its ledger child with HOME set to a private gh configuration directory (server/coordinator/credentials.mjs ghChildEnv). private_owner_allowlist.allowed() built the expected policy path from Path.home(), so on the Mac runtime the owner's enrolled private repositories were always refused. Take the home from the password database for the effective uid instead; PUBLIC_ALLOWLIST is unchanged. --- scripts/private_owner_allowlist.py | 12 +++++++++++- tests/test_private_owner_allowlist.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/private_owner_allowlist.py b/scripts/private_owner_allowlist.py index 47fb428..ef555b1 100644 --- a/scripts/private_owner_allowlist.py +++ b/scripts/private_owner_allowlist.py @@ -7,6 +7,7 @@ import json import os from pathlib import Path +import pwd import re import stat import sys @@ -21,8 +22,17 @@ def allowed(repo, *, environ=None, platform=None, home=None, uid=None): """Fail closed on malformed, inaccessible or non-owner Mac runtime policy.""" env = os.environ if environ is None else environ platform = sys.platform if platform is None else platform - home = Path.home() if home is None else Path(home) uid = os.geteuid() if uid is None else uid + # The account's home from the password database, never $HOME: Memo Bridge runs this script + # as the ledger child with HOME pointed at a private gh configuration directory + # (memo-bridge server/coordinator/credentials.mjs ghChildEnv), so Path.home() would name + # that directory and the owner's policy file would never match. + if home is None: + try: + home = pwd.getpwuid(uid).pw_dir + except (KeyError, OSError): + return False + home = Path(home) if (platform != "darwin" or env.get("MEMO_BRIDGE_HOST") != "mac" or env.get("MEMO_BRIDGE_AGENT_MODE") != "runtime" or env.get("MEMO_BRIDGE_MAC_RUNTIME_ENABLED") != "enabled" or diff --git a/tests/test_private_owner_allowlist.py b/tests/test_private_owner_allowlist.py index bdfd41b..4b7b1d2 100644 --- a/tests/test_private_owner_allowlist.py +++ b/tests/test_private_owner_allowlist.py @@ -69,5 +69,19 @@ def test_refuses_wrong_schema_extra_keys_and_duplicate_repos(self): self.path.chmod(0o600) self.assertFalse(self.allowed()) + def test_home_comes_from_the_account_not_the_environment(self): + # The Memo Bridge ledger child runs with HOME set to a private gh configuration directory. + account = type("pw", (), {"pw_dir": str(self.home)})() + with patch.object(policy.pwd, "getpwuid", return_value=account) as lookup, \ + patch.dict(os.environ, {"HOME": str(self.home / "gh-config")}): + self.assertTrue(policy.allowed(CDKF, environ=self.env, platform="darwin", uid=os.geteuid())) + lookup.assert_called_once_with(os.geteuid()) + other = type("pw", (), {"pw_dir": str(self.home / "someone-else")})() + with patch.object(policy.pwd, "getpwuid", return_value=other): + self.assertFalse(policy.allowed(CDKF, environ=self.env, platform="darwin", uid=os.geteuid())) + with patch.object(policy.pwd, "getpwuid", side_effect=KeyError("no such uid")): + self.assertFalse(policy.allowed(CDKF, environ=self.env, platform="darwin", uid=os.geteuid())) + + if __name__ == "__main__": unittest.main()