diff --git a/scripts/governance_snapshot.sh b/scripts/governance_snapshot.sh index 27915efed..7baccf1d6 100755 --- a/scripts/governance_snapshot.sh +++ b/scripts/governance_snapshot.sh @@ -60,8 +60,26 @@ g() { } # Owned by the invoker and writable by no one else (-perm -g+w / -o+w: BSD find has no `/`). +# GROUP-WRITABLE IN A PRIVATE GROUP IS STILL OWNER-ONLY (pub-claude, 2026-10-23). Ubuntu gives each user a +# group of their own and a umask of 002, so every clone there is g+w: on pub the hourly snapshot refused its +# own checkout from the hour #520 landed. A group bit reaches someone else only if the group has someone +# else in it, so g+w passes when the file's group lists no member but the invoker and is no other account's +# primary group. Without getent (macOS) that cannot be asked, and g+w is refused as before; a gid with no +# group entry is refused too. The check is absence-of-evidence: it trusts getent to enumerate every account, +# which holds on files-backed hosts (pub, cbp, HUB) but not under sssd/LDAP with enumeration off. +private_group() { + command -v getent >/dev/null 2>&1 || return 1 + pg_gid="$(stat -c %g "$1" 2>/dev/null || stat -f %g "$1" 2>/dev/null)" || return 1 + pg_me="$(id -un)" + pg_entry="$(getent group "$pg_gid")" || return 1 + pg_members="$(printf '%s\n' "$pg_entry" | cut -d: -f4)" + [ -z "$pg_members" ] || [ "$pg_members" = "$pg_me" ] || return 1 + [ -z "$(getent passwd | awk -F: -v g="$pg_gid" -v me="$pg_me" '$4 == g && $1 != me')" ] +} owner_only() { - [ -e "$1" ] && [ -O "$1" ] && [ -z "$(find "$1" -maxdepth 0 \( -perm -g+w -o -perm -o+w \) 2>/dev/null || true)" ] + [ -e "$1" ] && [ -O "$1" ] || return 1 + [ -z "$(find "$1" -maxdepth 0 -perm -o+w 2>/dev/null || true)" ] || return 1 + [ -z "$(find "$1" -maxdepth 0 -perm -g+w 2>/dev/null || true)" ] || private_group "$1" } if [ "$PRIVATE" -eq 0 ]; then GITDIR="$(g rev-parse --absolute-git-dir 2>/dev/null || true)" diff --git a/scripts/tests/test_governance_snapshot.py b/scripts/tests/test_governance_snapshot.py index 46432800e..486610e1d 100644 --- a/scripts/tests/test_governance_snapshot.py +++ b/scripts/tests/test_governance_snapshot.py @@ -137,8 +137,31 @@ def test_a_deploy_step_reads_the_deploys_own_clone_at_the_deployed_build_without assert _git("-C", private, "rev-parse", "origin/main") == before # no fetch into it -def test_a_source_someone_else_can_write_is_refused(world): - os.chmod(world["src"], os.stat(world["src"]).st_mode | stat.S_IWGRP) +def _group_is_private(path): + """The script's rule, asked of Python: nobody but the invoker is in the file's group. + It restates the rule, so it proves which branch the script takes, not that the rule is right.""" + import grp, pwd + gid, me = os.stat(path).st_gid, pwd.getpwuid(os.getuid()).pw_name + try: + members = grp.getgrgid(gid).gr_mem + except KeyError: + return False + return set(members) <= {me} and not [p for p in pwd.getpwall() if p.pw_gid == gid and p.pw_name != me] + + +def test_a_source_anyone_can_write_is_refused(world): + os.chmod(world["src"], os.stat(world["src"]).st_mode | stat.S_IWOTH) r = _run(world) assert r.returncode == 3 and "not owned by" in r.stderr, (r.returncode, r.stderr) assert _snapshot(world) is None + + +def test_a_group_writable_source_is_refused_unless_the_group_is_the_invokers_alone(world): + # Ubuntu's private group + umask 002 makes every clone g+w; that bit reaches no one else. + os.chmod(world["src"], os.stat(world["src"]).st_mode | stat.S_IWGRP) + r = _run(world) + if shutil.which("getent") and _group_is_private(world["src"]): + assert r.returncode == 0 and _snapshot(world) is not None, (r.returncode, r.stderr) + else: + assert r.returncode == 3 and "not owned by" in r.stderr, (r.returncode, r.stderr) + assert _snapshot(world) is None