From 49d30a2510038975ddddc5e56c1b9fdc44fc85fe Mon Sep 17 00:00:00 2001 From: owner Date: Tue, 8 Sep 2026 18:37:23 +0800 Subject: [PATCH] agent: fix readiness authority-git mode mischeck (group-shared repo) ServerOwnedEvidenceSource.assert_ready() applied the evidence-root strict group/world-writable check to the authority Git directory too. A valid setgid/group-shared authority repository (core.sharedRepository=group, 02775) permanently failed readiness with COLLECTOR_EVIDENCE_ROOT_MODE_DENIED. Fix: authority Git requires only exists + directory + non-symlink; the strict st_mode & 0o022 and owner checks apply to the evidence root only. Builder readiness remains infrastructure-only (independent of recipe evidence); BuildTrustedCandidateV1 stays evidence-gated and fail-closed. Add regression proving a group-shared 02775 authority is accepted while a group/world-writable evidence root is still rejected. Base: 8bf363143ec17da5231c8d4548ed3eb6aa8f8af0 (exact) Files: 2 (modified), +36/-11 Patch SHA-256: 5335025a9b193a50ad08c45e41ca6e8c7abdc77defec90c348f5149a0fe13b44 --- .../app/aios_trusted_candidate_builder.py | 18 ++++++------ tests/test_trusted_candidate_builder.py | 29 +++++++++++++++++-- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/active-runtime/app/aios_trusted_candidate_builder.py b/active-runtime/app/aios_trusted_candidate_builder.py index baf04a0..ad95808 100644 --- a/active-runtime/app/aios_trusted_candidate_builder.py +++ b/active-runtime/app/aios_trusted_candidate_builder.py @@ -367,15 +367,15 @@ class ServerOwnedEvidenceSource: raise BuilderError(code) from exc if not stat.S_ISDIR(metadata.st_mode) or path.is_symlink(): raise BuilderError(code) - if os.name != "nt" and metadata.st_mode & 0o022: - raise BuilderError("COLLECTOR_EVIDENCE_ROOT_MODE_DENIED") - if ( - path == self._evidence_root - and os.name != "nt" - and self._expected_owner_uid is not None - and metadata.st_uid != self._expected_owner_uid - ): - raise BuilderError("COLLECTOR_EVIDENCE_ROOT_OWNER_DENIED") + if path == self._evidence_root: + if os.name != "nt" and metadata.st_mode & 0o022: + raise BuilderError("COLLECTOR_EVIDENCE_ROOT_MODE_DENIED") + if ( + os.name != "nt" + and self._expected_owner_uid is not None + and metadata.st_uid != self._expected_owner_uid + ): + raise BuilderError("COLLECTOR_EVIDENCE_ROOT_OWNER_DENIED") def _digest(self, path: Path) -> str: digest = hashlib.sha256() diff --git a/tests/test_trusted_candidate_builder.py b/tests/test_trusted_candidate_builder.py index c7fe7e1..a02a739 100644 --- a/tests/test_trusted_candidate_builder.py +++ b/tests/test_trusted_candidate_builder.py @@ -323,9 +323,11 @@ def test_readiness_is_independent_from_recipe_evidence_but_build_remains_fail_cl policy = load_builder_policy(POLICY_PATH) config = TrustedCandidateBuilderConfig.from_policy(policy) evidence_root = tmp_path / "evidence" - evidence_root.mkdir(mode=0o700) + evidence_root.mkdir() + evidence_root.chmod(0o700) authority_git = tmp_path / "repo.git" - authority_git.mkdir(mode=0o700) + authority_git.mkdir() + authority_git.chmod(0o2775) source = ServerOwnedEvidenceSource( config, @@ -358,6 +360,29 @@ def test_readiness_is_independent_from_recipe_evidence_but_build_remains_fail_cl server.handle_request(build, connection=object()) +def test_readiness_keeps_evidence_root_permissions_strict_with_group_shared_authority(tmp_path) -> None: + config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH)) + evidence_root = tmp_path / "evidence" + evidence_root.mkdir() + evidence_root.chmod(0o720) + authority_git = tmp_path / "repo.git" + authority_git.mkdir() + authority_git.chmod(0o2775) + + source = ServerOwnedEvidenceSource( + config, + authority_git, + evidence_root, + expected_owner_uid=None, + git_runner=lambda *_args, **_kwargs: SimpleNamespace( + stdout="a" * 40 + "\n", returncode=0 + ), + ) + + with pytest.raises(BuilderError, match="COLLECTOR_EVIDENCE_ROOT_MODE_DENIED"): + source.assert_ready() + + def test_unix_client_applies_bounded_timeout_and_fails_closed(monkeypatch) -> None: observed = {}