Archived
KofTwentyTwo/ci-policy: ci / fork-guard (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / go-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / go-nix-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / gradle-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / gradle-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / java-docker-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / java-maven-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / manifests-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / nix-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / node-8 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / python-9 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-10 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-11 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-12 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-13 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-14 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / not-enrolled (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / content-validate-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / standards-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / node-matrix-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-15 (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / verify (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / secrets (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / gate (pull_request) Waiting for needed jobs
KofTwentyTwo/ci-policy: ci / ci-result (pull_request) Waiting for needed jobs
KofTwentyTwo/ci-policy: native / fork-deny (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-8 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-9 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-10 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-11 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-12 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-13 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-14 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-15 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-16 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-17 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-18 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-19 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / native-result (pull_request) Canceled after 4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
139 lines
7.8 KiB
Python
139 lines
7.8 KiB
Python
"""Git trust for the source check: exactly the one checkout, for that one invocation, with no host, user or inherited Git configuration involved.
|
|
|
|
The builtin checkout's owner can differ from the user a job runs as, so `git ls-files` inside the unchanged helper reports `dubious ownership`. The adapter
|
|
therefore starts the helper with command-scope Git configuration naming only that checkout's resolved path. These tests prove what the environment
|
|
contains, that hostile inherited Git variables never reach the helper, that other tools get none of it, and that real Git sees exactly that one path.
|
|
They cannot reproduce a foreign-owned checkout locally, so the live source runs on the synthetic repositories remain the proof of the failure itself.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from typing import override
|
|
|
|
from platform_ci.build_tools import BUILD_TOOLS, SOURCE_CHECK_PROGRAM
|
|
from platform_ci.configuration import parse_project_configuration
|
|
from platform_ci.execution import git_trust_environment, run_target_commands
|
|
from tests.support import event, example_configuration, plan_for
|
|
from tests.test_pool_and_source import HELPERS, Recorder, source_configuration
|
|
|
|
HOSTILE_PARENT = {
|
|
"PATH": "/usr/bin",
|
|
"HOME": "/tmp", # noqa: S108
|
|
"GIT_CONFIG_COUNT": "3",
|
|
"GIT_CONFIG_KEY_0": "safe.directory",
|
|
"GIT_CONFIG_VALUE_0": "*",
|
|
"GIT_CONFIG_KEY_1": "core.hooksPath",
|
|
"GIT_CONFIG_VALUE_1": "/evil/hooks",
|
|
"GIT_CONFIG_KEY_2": "core.fsmonitor",
|
|
"GIT_CONFIG_VALUE_2": "/evil/monitor",
|
|
"GIT_CONFIG_PARAMETERS": "'safe.directory=*'",
|
|
"GIT_CONFIG_GLOBAL": "/evil/gitconfig",
|
|
"GIT_CONFIG_SYSTEM": "/evil/system",
|
|
"GIT_DIR": "/evil/dir",
|
|
"GIT_WORK_TREE": "/evil/tree",
|
|
"GIT_SSH_COMMAND": "evil",
|
|
"GIT_EXEC_PATH": "/evil/exec",
|
|
}
|
|
|
|
|
|
class GitTrustEnvironmentTest(unittest.TestCase):
|
|
"""The environment names one exact path and nothing else."""
|
|
|
|
def test_exactly_one_command_scope_entry_for_the_resolved_path(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
checkout = Path(directory) / "checkout"
|
|
checkout.mkdir()
|
|
environment = git_trust_environment(checkout)
|
|
self.assertEqual(environment, {"GIT_CONFIG_COUNT": "1", "GIT_CONFIG_KEY_0": "safe.directory", "GIT_CONFIG_VALUE_0": checkout.resolve().as_posix()})
|
|
self.assertNotIn("\\", environment["GIT_CONFIG_VALUE_0"])
|
|
self.assertNotIn("*", environment["GIT_CONFIG_VALUE_0"])
|
|
self.assertEqual(git_trust_environment(checkout / ".." / "checkout"), environment) # a relative or dotted spelling resolves to the same single path
|
|
|
|
def test_a_different_directory_gets_a_different_path_never_a_pattern(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
first, second = Path(directory) / "a", Path(directory) / "b"
|
|
first.mkdir()
|
|
second.mkdir()
|
|
self.assertNotEqual(git_trust_environment(first)["GIT_CONFIG_VALUE_0"], git_trust_environment(second)["GIT_CONFIG_VALUE_0"])
|
|
|
|
|
|
class GitTrustInvocationTest(unittest.TestCase):
|
|
"""Only the source tool gets the trust, and inherited Git variables never reach it."""
|
|
|
|
@override
|
|
def setUp(self) -> None:
|
|
"""Create a checkout with the platform's helper directory beside it, as the workflow does."""
|
|
directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(directory.cleanup)
|
|
self.source = Path(directory.name) / "checkout"
|
|
shutil.copytree(HELPERS, self.source / ".platform-ci" / "source_checks")
|
|
self.target = plan_for(parse_project_configuration(source_configuration()), event())["targets"][0]
|
|
|
|
def test_the_source_tool_runs_with_exactly_the_one_checkout_trusted(self) -> None:
|
|
recorder = Recorder()
|
|
self.assertTrue(run_target_commands(self.target, self.source, recorder, {"PATH": "/usr/bin"}))
|
|
((command, environment),) = recorder.calls
|
|
self.assertEqual(command, ["node", SOURCE_CHECK_PROGRAM])
|
|
git_variables = {name: value for name, value in environment.items() if name.startswith("GIT_")}
|
|
self.assertEqual(git_variables, git_trust_environment(self.source))
|
|
|
|
def test_hostile_inherited_git_variables_are_dropped_and_replaced_by_the_single_entry(self) -> None:
|
|
recorder = Recorder()
|
|
run_target_commands(self.target, self.source, recorder, HOSTILE_PARENT)
|
|
((_command, environment),) = recorder.calls
|
|
self.assertEqual({name: value for name, value in environment.items() if name.startswith("GIT_")}, git_trust_environment(self.source))
|
|
for hostile in ("GIT_CONFIG_PARAMETERS", "GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM", "GIT_DIR", "GIT_WORK_TREE", "GIT_SSH_COMMAND", "GIT_EXEC_PATH", "GIT_CONFIG_KEY_1", "GIT_CONFIG_VALUE_2"):
|
|
self.assertNotIn(hostile, environment)
|
|
self.assertNotEqual(environment["GIT_CONFIG_VALUE_0"], "*")
|
|
|
|
def test_other_tools_receive_no_git_configuration_at_all(self) -> None:
|
|
self.assertEqual([name for name, tool in BUILD_TOOLS.items() if tool.inspects_checkout_with_git], ["source"])
|
|
maven = plan_for(example_configuration("maven.ci.yaml"), event())["targets"][0]
|
|
recorder = Recorder()
|
|
with tempfile.TemporaryDirectory() as home:
|
|
run_target_commands(maven, self.source, recorder, {**HOSTILE_PARENT, "HOME": home})
|
|
for _command, environment in recorder.calls:
|
|
self.assertEqual([name for name in environment if name.startswith("GIT_")], [])
|
|
|
|
def test_no_code_or_workflow_writes_global_git_state_or_a_wildcard(self) -> None:
|
|
root = Path(__file__).resolve().parent.parent
|
|
for path in (*(root / "platform_ci").rglob("*.py"), root / ".gitea" / "scoped_workflows" / "pipeline.yaml"):
|
|
text = path.read_text(encoding="utf-8")
|
|
for forbidden in ("--global", "--system", "safe.directory=*", "safe.directory *"):
|
|
self.assertNotIn(forbidden, text, path.name)
|
|
|
|
|
|
@unittest.skipUnless(shutil.which("git"), "needs git on PATH")
|
|
class RealGitSeesOnlyTheTrustedPathTest(unittest.TestCase):
|
|
"""Real Git, given the environment, reports exactly one command-scope safe.directory: the checkout itself."""
|
|
|
|
def _command_scope_values(self, directory: Path) -> list[str]:
|
|
environment = {name: value for name, value in os.environ.items() if not name.startswith("GIT_")} | git_trust_environment(directory)
|
|
done = subprocess.run(["git", "config", "--show-scope", "--get-all", "safe.directory"], cwd=directory, env=environment, capture_output=True, text=True, check=False) # noqa: S607
|
|
return [line.split("\t", 1)[1] for line in done.stdout.splitlines() if line.startswith("command\t")]
|
|
|
|
def test_exactly_the_checkout_is_trusted_and_no_other_path_matches(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
checkout = Path(directory) / "checkout"
|
|
(checkout / "child").mkdir(parents=True)
|
|
(Path(directory) / "sibling").mkdir()
|
|
self.assertEqual(self._command_scope_values(checkout), [checkout.resolve().as_posix()])
|
|
trusted = set(self._command_scope_values(checkout))
|
|
for other in (checkout / "child", Path(directory) / "sibling", Path(directory), checkout.parent.parent):
|
|
self.assertNotIn(other.resolve().as_posix(), trusted)
|
|
|
|
def test_the_trust_exists_only_in_that_environment_not_in_any_git_file(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
checkout = Path(directory)
|
|
environment = {name: value for name, value in os.environ.items() if not name.startswith("GIT_")}
|
|
done = subprocess.run(["git", "config", "--show-scope", "--get-all", "safe.directory"], cwd=checkout, env=environment, capture_output=True, text=True, check=False) # noqa: S607
|
|
self.assertNotIn(checkout.resolve().as_posix(), done.stdout) # without the environment the checkout is not trusted anywhere
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|