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()