ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
git_environment.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Keep nested Git fixtures independent from the invoking hook repository.
5
6Git exports repository-local environment variables while running hooks. A
7``git -C <temporary-directory>`` command does not override those variables, so
8an otherwise isolated selftest can read, stage, or commit the caller's index.
9Every child Git writer and fixture must enter :func:`isolated_git_environment`
10or pass :func:`sanitized_git_environment` before its first Git command.
11Read-only real-tree queries that deliberately judge the caller's index may
12inherit that routing; writers never may.
13"""
14
15from __future__ import annotations
16
17import argparse
18import hashlib
19import os
20import shlex
21import stat
22import subprocess
23import sys
24import tempfile
25from collections.abc import Iterator, Mapping, Sequence
26from contextlib import contextmanager
27from pathlib import Path
28
29TRUSTED_GIT_PATH = Path("/usr/bin/git")
30PUSH_CAPTURE_FIELDS = 4
31
32LOCAL_GIT_ENVIRONMENT = (
33 "GIT_ALTERNATE_OBJECT_DIRECTORIES",
34 "GIT_COMMON_DIR",
35 "GIT_CONFIG",
36 "GIT_CONFIG_COUNT",
37 "GIT_CONFIG_PARAMETERS",
38 "GIT_DIR",
39 "GIT_GRAFT_FILE",
40 "GIT_IMPLICIT_WORK_TREE",
41 "GIT_INDEX_FILE",
42 "GIT_INTERNAL_SUPER_PREFIX",
43 "GIT_NO_REPLACE_OBJECTS",
44 "GIT_OBJECT_DIRECTORY",
45 "GIT_PREFIX",
46 "GIT_REPLACE_REF_BASE",
47 "GIT_SHALLOW_FILE",
48 "GIT_WORK_TREE",
49)
50
51
52class GitEnvironmentError(RuntimeError):
53 """A nested Git fixture escaped its repository boundary."""
54
55
56def _fail(message: str) -> None:
57 """Raise a fixture-boundary error with caller-provided detail."""
58 raise GitEnvironmentError(message)
59
60
61def trusted_git_executable() -> str:
62 """Return the one absolute Git executable allowed for control-plane work."""
63 configured = os.environ.get("RA8_TRUSTED_GIT", str(TRUSTED_GIT_PATH))
64 if configured != str(TRUSTED_GIT_PATH):
65 _fail(f"refusing non-authority Git executable: {configured}")
66 try:
67 info = TRUSTED_GIT_PATH.lstat()
68 except OSError as exc:
69 message = "trusted /usr/bin/git is unavailable"
70 raise GitEnvironmentError(message) from exc
71 if not stat.S_ISREG(info.st_mode) or TRUSTED_GIT_PATH.is_symlink():
72 _fail("trusted /usr/bin/git is not a regular non-symlink executable")
73 if not os.access(TRUSTED_GIT_PATH, os.X_OK):
74 _fail("trusted /usr/bin/git is not executable")
75 return str(TRUSTED_GIT_PATH)
76
77
78def sanitized_git_environment(
79 source: Mapping[str, str] | None = None,
80) -> dict[str, str]:
81 """Return a noninteractive environment isolated from caller Git policy.
82
83 Repository-local routing is only one way a nested Git command can escape
84 its fixture. Global or system configuration can select an attributes file,
85 and attributes can execute clean/smudge filters. Every inherited ``GIT_*``
86 selector and executable-helper variable is therefore removed here, then
87 the supported noninteractive controls are rebound to safe values.
88 """
89 environment = os.environ if source is None else source
90 helper_environment = frozenset(
91 {
92 "DIFF",
93 "EDITOR",
94 "LESS",
95 "LV",
96 "MERGE_TOOL",
97 "PAGER",
98 "SSH_ASKPASS",
99 "SUDO_ASKPASS",
100 "VISUAL",
101 "BASH_ENV",
102 "ENV",
103 "PYTHONHOME",
104 "PYTHONPATH",
105 }
106 )
107 clean = {
108 name: value
109 for name, value in environment.items()
110 if not name.startswith(("GIT_", "BASH_FUNC_")) and name not in helper_environment
111 }
112 clean.update(
113 {
114 "GIT_ATTR_NOSYSTEM": "1",
115 "GIT_CONFIG_GLOBAL": os.devnull,
116 "GIT_CONFIG_NOSYSTEM": "1",
117 "GIT_CONFIG_SYSTEM": os.devnull,
118 "GIT_EDITOR": "false",
119 "GIT_OPTIONAL_LOCKS": "0",
120 "GIT_PAGER": "cat",
121 "RA8_TRUSTED_GIT": trusted_git_executable(),
122 "GIT_SEQUENCE_EDITOR": "false",
123 "GIT_SSH_COMMAND": "false",
124 "GIT_TERMINAL_PROMPT": "0",
125 "GIT_CONFIG_COUNT": "3",
126 "GIT_CONFIG_KEY_0": "core.hooksPath",
127 "GIT_CONFIG_VALUE_0": os.devnull,
128 "GIT_CONFIG_KEY_1": "core.fsmonitor",
129 "GIT_CONFIG_VALUE_1": "false",
130 "GIT_CONFIG_KEY_2": "core.attributesFile",
131 "GIT_CONFIG_VALUE_2": os.devnull,
132 "PAGER": "cat",
133 "TERM": "dumb",
134 }
135 )
136 return clean
137
138
139def _network_git_environment(source: Mapping[str, str]) -> dict[str, str]:
140 """Keep operator transport policy while removing every other Git selector."""
141 transport_names = {
142 "GIT_ASKPASS",
143 "GIT_CONFIG_GLOBAL",
144 "GIT_CONFIG_NOSYSTEM",
145 "GIT_CONFIG_SYSTEM",
146 "GIT_SSH",
147 "GIT_SSH_COMMAND",
148 "GIT_SSH_VARIANT",
149 "GIT_TERMINAL_PROMPT",
150 }
151 clean = {name: value for name, value in source.items() if not name.startswith("GIT_")}
152 clean.update({name: source[name] for name in transport_names if name in source})
153 clean.update({"GIT_OPTIONAL_LOCKS": "0", "GIT_PAGER": "cat", "PAGER": "cat", "TERM": "dumb"})
154 return clean
155
156
157@contextmanager
158def isolated_git_environment() -> Iterator[None]:
159 """Temporarily install the hardened child environment, then restore all bytes."""
160 original = dict(os.environ)
161 os.environ.clear()
162 os.environ.update(sanitized_git_environment(original))
163 try:
164 yield
165 finally:
166 os.environ.clear()
167 os.environ.update(original)
168
169
170def _git(root: Path, *args: str, clean: bool = True) -> bytes:
171 """Run Git for the helper's synthetic fixture."""
172 environment = sanitized_git_environment() if clean else os.environ.copy()
173 proc = subprocess.run( # noqa: S603 -- fixed executable and selftest argv
174 [trusted_git_executable(), "-C", str(root), *args],
175 env=environment,
176 capture_output=True,
177 check=False,
178 )
179 if proc.returncode != 0:
180 detail = os.fsdecode(proc.stderr).strip()
181 message = f"git {' '.join(args)} failed: {detail}"
182 _fail(message)
183 return proc.stdout
184
185
186def _local_config_values(root: Path, key: str) -> tuple[str, ...]:
187 """Return every local repository config value, preserving cardinality."""
188 proc = subprocess.run( # noqa: S603 -- fixed Git executable and validated config key
189 [
190 trusted_git_executable(),
191 "-C",
192 str(root),
193 "config",
194 "--local",
195 "--null",
196 "--get-all",
197 key,
198 ],
199 env=sanitized_git_environment(),
200 capture_output=True,
201 check=False,
202 )
203 if proc.returncode == 1:
204 return ()
205 if proc.returncode != 0:
206 detail = os.fsdecode(proc.stderr).strip()
207 _fail(f"cannot read local Git config {key}: {detail}")
208 return tuple(os.fsdecode(value) for value in proc.stdout.split(b"\0") if value)
209
210
211def _attribute_text_tokens(text: str) -> list[str]:
212 """Return the governed tokens from decoded Git attribute text."""
213 tokens: list[str] = []
214 for raw_line in text.splitlines():
215 line = raw_line.lstrip()
216 if line and not line.startswith("#"):
217 tokens.extend(line.split()[1:])
218 return tokens
219
220
221def _attribute_tokens(path: Path) -> list[str]:
222 """Return attribute tokens from one real, bounded UTF-8 attribute file."""
223 try:
224 info = path.lstat()
225 except FileNotFoundError:
226 return []
227 if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):
228 _fail(f"refusing non-regular Git attribute file: {path}")
229 try:
230 return _attribute_text_tokens(path.read_text(encoding="utf-8"))
231 except (OSError, UnicodeError) as exc:
232 message = f"refusing unreadable Git attribute file: {path}"
233 raise GitEnvironmentError(message) from exc
234
235
236def _trusted_attribute(token: str) -> bool:
237 """Return whether an executable-shaped token matches the fixed tree policy."""
238 name, separator, value = token.partition("=")
239 if not separator:
240 return False
241 return (name == "diff" and value in {"c", "cpp", "lfs"}) or (
242 name == "filter" and value == "lfs"
243 )
244
245
246def _validate_local_driver_config(root: Path) -> None:
247 """Reject unexpected filter definitions and executable diff drivers."""
248 expected = {
249 "filter.lfs.clean": "git-lfs clean -- %f",
250 "filter.lfs.process": "git-lfs filter-process",
251 "filter.lfs.required": "true",
252 "filter.lfs.smudge": "git-lfs smudge -- %f",
253 }
254 proc = subprocess.run( # noqa: S603 -- fixed Git executable and config query
255 [
256 trusted_git_executable(),
257 "-C",
258 str(root),
259 "config",
260 "--local",
261 "--null",
262 "--name-only",
263 "--get-regexp",
264 "^(filter|diff)\\.",
265 ],
266 env=sanitized_git_environment(),
267 capture_output=True,
268 check=False,
269 )
270 if proc.returncode not in {0, 1}:
271 _fail(f"cannot inventory local Git drivers: {os.fsdecode(proc.stderr).strip()}")
272 names = {os.fsdecode(item) for item in proc.stdout.split(b"\0") if item}
273 unexpected_filters = sorted(
274 name for name in names if name.startswith("filter.") and name not in expected
275 )
276 executable_diffs = sorted(
277 name
278 for name in names
279 if name == "diff.external" or name.endswith((".command", ".textconv"))
280 )
281 if unexpected_filters or executable_diffs:
282 bad = ", ".join([*unexpected_filters, *executable_diffs])
283 _fail(f"refusing untrusted local Git driver config: {bad}")
284 actual = {key: _local_config_values(root, key) for key in expected}
285 exact = {key: (value,) for key, value in expected.items()}
286 if not (all(not values for values in actual.values()) or actual == exact):
287 _fail("refusing drifted partial filter.lfs local configuration")
288
289
290def _shell_contract(
291 source: Mapping[str, str], *, network: bool = False
292) -> tuple[tuple[str, str, str], ...]:
293 """Describe the strict child environment as shell-safe structured rows."""
294 clean = _network_git_environment(source) if network else sanitized_git_environment(source)
295 rows = [("unset", name, "") for name in sorted(source) if name not in clean]
296 governed = {name for name in clean if name.startswith("GIT_") or name in {"PAGER", "TERM"}}
297 rows.extend(("set", name, clean[name]) for name in sorted(governed))
298 return tuple(rows)
299
300
301def _commit_attribute_sources(root: Path, commit: str) -> list[tuple[str, list[str]]]:
302 """Return every .gitattributes token set from one exact commit tree."""
303 resolved = os.fsdecode(_git(root, "rev-parse", "--verify", f"{commit}^{{commit}}")).strip()
304 raw_paths = _git(root, "ls-tree", "-r", "--name-only", "-z", resolved)
305 sources: list[tuple[str, list[str]]] = []
306 for raw_path in raw_paths.split(b"\0"):
307 if not raw_path:
308 continue
309 relative = os.fsdecode(raw_path)
310 if Path(relative).name != ".gitattributes":
311 continue
312 source = f"{resolved}:{relative}"
313 try:
314 text = _git(root, "show", source).decode("utf-8")
315 except UnicodeError as exc:
316 message = f"refusing non-UTF-8 Git attribute blob: {source}"
317 raise GitEnvironmentError(message) from exc
318 sources.append((source, _attribute_text_tokens(text)))
319 return sources
320
321
322def _worktree_attribute_sources(root: Path) -> list[tuple[str, list[str]]]:
323 """Return every live worktree .gitattributes token set."""
324 sources: list[tuple[str, list[str]]] = []
325 for directory, names, files in os.walk(root, followlinks=False):
326 names[:] = [name for name in names if name != ".git"]
327 if ".gitattributes" in files:
328 path = Path(directory) / ".gitattributes"
329 sources.append((str(path), _attribute_tokens(path)))
330 return sources
331
332
333def reject_untrusted_executable_attributes(root: Path, commit: str | None = None) -> None:
334 """Refuse novel filter/diff attributes before a nested checkout runs.
335
336 The repository's exact built-in C/C++ diff drivers and locally configured
337 Git-LFS boundary are intentional. Any other driver name, or drift in those
338 local definitions, is executable policy and fails closed.
339 """
340 root = root.resolve()
341 common = Path(os.fsdecode(_git(root, "rev-parse", "--git-common-dir")).strip())
342 git_dir = Path(os.fsdecode(_git(root, "rev-parse", "--git-dir")).strip())
343 common = common if common.is_absolute() else (root / common).resolve()
344 git_dir = git_dir if git_dir.is_absolute() else (root / git_dir).resolve()
345 sources = (
346 _commit_attribute_sources(root, commit)
347 if commit is not None
348 else _worktree_attribute_sources(root)
349 )
350 info_paths = dict.fromkeys([common / "info/attributes", git_dir / "info/attributes"])
351 sources.extend((str(path), _attribute_tokens(path)) for path in info_paths)
352 for source, tokens in sources:
353 for token in tokens:
354 name = token.partition("=")[0]
355 if name in {"diff", "filter"} and not _trusted_attribute(token):
356 _fail(f"refusing untrusted Git attribute {token!r} from {source}")
357 _validate_local_driver_config(root)
358
359
360def _tree_digest(root: Path) -> str:
361 """Hash worktree paths, modes, link targets, and regular-file bytes."""
362 digest = hashlib.sha256()
363 for path in sorted(root.rglob("*"), key=lambda item: os.fsencode(str(item))):
364 if ".git" in path.relative_to(root).parts:
365 continue
366 rel = os.fsencode(path.relative_to(root).as_posix())
367 mode = stat.S_IMODE(path.lstat().st_mode)
368 digest.update(rel + b"\0" + str(mode).encode("ascii") + b"\0")
369 if path.is_symlink():
370 digest.update(b"L" + os.fsencode(path.readlink()))
371 elif path.is_file():
372 digest.update(b"F" + path.read_bytes())
373 else:
374 digest.update(b"D")
375 return digest.hexdigest()
376
377
378def _outer_snapshot(root: Path) -> tuple[bytes, bytes, bytes, bytes, str, str]:
379 """Capture the repository state a nested fixture must not mutate."""
380 return (
381 _git(root, "rev-parse", "HEAD"),
382 (root / ".git" / "index").read_bytes(),
383 (root / ".git" / "config").read_bytes(),
384 _git(root, "status", "--porcelain=v1", "-z"),
385 _tree_digest(root),
386 _tree_digest(root / ".git" / "objects"),
387 )
388
389
390def _init_outer(root: Path) -> tuple[bytes, bytes, bytes, bytes, str, str]:
391 """Create and snapshot a synthetic repository representing a hook caller."""
392 root.mkdir()
393 _git(root, "init", "--quiet")
394 _git(root, "config", "user.email", "selftest@invalid")
395 _git(root, "config", "user.name", "selftest")
396 (root / "sentinel.txt").write_text("outer sentinel\n", encoding="ascii")
397 _git(root, "add", "sentinel.txt")
398 _git(root, "commit", "--quiet", "-m", "outer sentinel")
399 return _outer_snapshot(root)
400
401
402def _exercise_nested_repo(outer: Path, inner: Path) -> None:
403 """Prove the hostile environment routes unsanitized Git to ``outer``."""
404 os.environ["GIT_DIR"] = str(outer / ".git")
405 os.environ["GIT_WORK_TREE"] = str(outer)
406 os.environ["GIT_INDEX_FILE"] = str(outer / ".git" / "index")
407 resolved = os.fsdecode(_git(inner, "rev-parse", "--show-toplevel", clean=False)).strip()
408 if Path(resolved).resolve() != outer.resolve():
409 _fail("hostile Git environment did not reproduce outer routing")
410 with isolated_git_environment():
411 _git(inner, "init", "--quiet", clean=False)
412 _git(inner, "config", "user.email", "selftest@invalid", clean=False)
413 _git(inner, "config", "user.name", "selftest", clean=False)
414 (inner / "fixture.txt").write_text("inner fixture\n", encoding="ascii")
415 _git(inner, "add", "fixture.txt", clean=False)
416 _git(inner, "commit", "--quiet", "-m", "inner fixture", clean=False)
417
418
419def _hostile_config_environment(root: Path) -> tuple[dict[str, str], Path]:
420 """Create global/system config whose attributes execute a byte-preserving filter."""
421 root.mkdir()
422 marker = root / "filter-executed"
423 helper = root / "filter-helper.sh"
424 helper.write_text(
425 f"#!/bin/sh\nprintf x >> {shlex.quote(str(marker))}\ncat\n",
426 encoding="ascii",
427 )
428 helper.chmod(0o755)
429 attributes = root / "global-attributes"
430 attributes.write_text("* filter=ra8-hostile\n", encoding="ascii")
431 system_config = root / "system.config"
432 system_config.write_text(
433 f"[core]\n\tattributesFile = {attributes}\n",
434 encoding="ascii",
435 )
436 global_config = root / "global.config"
437 global_config.write_text(
438 f'[filter "ra8-hostile"]\n\tclean = {helper}\n\tsmudge = {helper}\n\trequired = true\n',
439 encoding="ascii",
440 )
441 return (
442 {
443 "GIT_ATTR_NOSYSTEM": "0",
444 "GIT_CONFIG_GLOBAL": str(global_config),
445 "GIT_CONFIG_NOSYSTEM": "0",
446 "GIT_CONFIG_SYSTEM": str(system_config),
447 },
448 marker,
449 )
450
451
452def _hostile_process_environment(root: Path) -> tuple[dict[str, str], tuple[Path, ...]]:
453 """Build PATH, shell-startup, exported-function, and Python-startup attacks."""
454 root.mkdir()
455 git_marker = root / "git-authority-executed"
456 bash_marker = root / "bash-startup-executed"
457 python_marker = root / "python-startup-executed"
458 fake_bin = root / "source/.venv/bin"
459 fake_bin.mkdir(parents=True)
460 fake_git = fake_bin / "git"
461 fake_git.write_text(
462 f'#!/bin/sh\nprintf x >> {shlex.quote(str(git_marker))}\nexec /usr/bin/git "$@"\n',
463 encoding="ascii",
464 )
465 fake_git.chmod(0o755)
466 bash_env = root / "bash-env"
467 bash_env.write_text(f"printf x >> {shlex.quote(str(bash_marker))}\n", encoding="ascii")
468 python_path = root / "python-path"
469 python_path.mkdir()
470 home = root / "home"
471 home.mkdir()
472 (python_path / "sitecustomize.py").write_text(
473 "from pathlib import Path\n"
474 f"Path({str(python_marker)!r}).write_text('x', encoding='ascii')\n",
475 encoding="ascii",
476 )
477 function = f'() {{ printf x >> {shlex.quote(str(git_marker))}; /usr/bin/git "$@"; }}'
478 environment = {
479 "BASH_ENV": str(bash_env),
480 "BASH_FUNC_git%%": function,
481 "ENV": str(bash_env),
482 "HOME": str(home),
483 "PATH": f"{fake_bin}:{os.environ.get('PATH', '')}",
484 "PYTHONPATH": str(python_path),
485 "RA8_NON_GIT_SENTINEL": "preserved",
486 }
487 return environment, (git_marker, bash_marker, python_marker)
488
489
490def _prove_hostile_process_attacks_execute(
491 hostile: Mapping[str, str], markers: Sequence[Path]
492) -> None:
493 """Prove every interpreter/executable attack is live before strict boundaries."""
494 environment = os.environ.copy()
495 for name in ("SSH_CLIENT", "SSH_CONNECTION", "SSH_TTY"):
496 environment.pop(name, None)
497 environment.update(hostile)
498 subprocess.run(
499 ["/bin/bash", "-c", "git --version >/dev/null"],
500 env=environment,
501 check=True,
502 )
503 subprocess.run(
504 [sys.executable, "-c", "pass"],
505 env=environment,
506 check=True,
507 )
508 missing = [str(path) for path in markers if not path.exists()]
509 if missing:
510 _fail(f"hostile executable/interpreter probes did not fire: {missing}")
511 for path in markers:
512 path.unlink()
513
514
515def _prove_hostile_config_executes(root: Path, hostile: Mapping[str, str], marker: Path) -> None:
516 """Prove the hostile configuration is live before repaired code suppresses it."""
517 probe = root / "config-probe"
518 probe.mkdir()
519 _git(probe, "init", "--quiet")
520 (probe / "probe.txt").write_text("probe\n", encoding="ascii")
521 environment = sanitized_git_environment()
522 for name in tuple(environment):
523 if name == "GIT_CONFIG_COUNT" or name.startswith(("GIT_CONFIG_KEY_", "GIT_CONFIG_VALUE_")):
524 environment.pop(name)
525 environment.update(hostile)
526 proc = subprocess.run( # noqa: S603 -- fixed Git executable and selftest argv
527 [trusted_git_executable(), "-C", str(probe), "add", "probe.txt"],
528 env=environment,
529 capture_output=True,
530 check=False,
531 )
532 if proc.returncode != 0 or not marker.is_file():
533 detail = os.fsdecode(proc.stderr).strip()
534 _fail(f"hostile config/attribute probe did not execute its filter: {detail}")
535 marker.unlink()
536
537
538def _prove_inherited_global_config_remains_available(root: Path) -> None:
539 """Prove direct real-tree callers may still inherit harmless global policy."""
540 config = root / "harmless-global.config"
541 config.write_text("[ra8]\n\tharmless = visible\n", encoding="ascii")
542 direct = os.environ.copy()
543 direct.update(
544 {
545 "GIT_CONFIG_GLOBAL": str(config),
546 "GIT_CONFIG_NOSYSTEM": "1",
547 "GIT_CONFIG_SYSTEM": os.devnull,
548 }
549 )
550 proc = subprocess.run( # noqa: S603 -- fixed Git executable and fixed config query
551 [trusted_git_executable(), "config", "--global", "--get", "ra8.harmless"],
552 env=direct,
553 capture_output=True,
554 check=False,
555 )
556 if proc.returncode != 0 or proc.stdout != b"visible\n":
557 _fail("direct inherited Git environment lost harmless global config")
558 direct["RA8_NON_GIT_SENTINEL"] = "preserved"
559 clean = sanitized_git_environment(direct)
560 if clean.get("RA8_NON_GIT_SENTINEL") != "preserved":
561 _fail("sanitizer removed an unrelated non-Git environment variable")
562 if clean.get("GIT_CONFIG_GLOBAL") != os.devnull or clean.get("GIT_CONFIG_SYSTEM") != os.devnull:
563 _fail("sanitizer did not bind global/system Git configuration to safe files")
564
565
566def _python_selftest(
567 repo_root: Path, label: str, relative: str, *args: str
568) -> tuple[str, tuple[str, ...]]:
569 """Build one isolated, absolute-interpreter selftest command."""
570 return label, (sys.executable, "-I", str(repo_root / relative), *args)
571
572
573def _registered_fixture_commands(repo_root: Path) -> tuple[tuple[str, tuple[str, ...]], ...]:
574 """Return the exact selftest suites protected by the nested-Git boundary."""
575 return (
576 _python_selftest(
577 repo_root,
578 "init-order",
579 "scripts/checks/check_init_order_freshness.py",
580 "--selftest",
581 ),
582 _python_selftest(
583 repo_root,
584 "roadmap-dashboard",
585 "scripts/checks/check_roadmap_dashboard_freshness.py",
586 "--selftest",
587 ),
588 _python_selftest(
589 repo_root,
590 "markdown-references",
591 "scripts/checks/check_markdown_references.py",
592 "--selftest",
593 ),
594 _python_selftest(
595 repo_root,
596 "python-lock-policy",
597 "scripts/checks/check_python_lock_policy.py",
598 "--selftest",
599 ),
600 _python_selftest(repo_root, "work-harness", "scripts/dev/work/src/work.py", "--selftest"),
601 _python_selftest(
602 repo_root,
603 "workspace-lifecycle",
604 "scripts/dev/work/tests/test_workspace_lifecycle.py",
605 ),
606 _python_selftest(
607 repo_root,
608 "pre-commit-bootstrap",
609 "scripts/checks/check_hook_parity.py",
610 "--selftest",
611 ),
612 _python_selftest(
613 repo_root, "candidate-assembly", "scripts/dev/assemble_candidate.py", "--selftest"
614 ),
615 )
616
617
618def _run_registered_fixture(
619 outer: Path,
620 hostile: Mapping[str, str],
621 markers: Sequence[Path],
622 label: str,
623 argv: tuple[str, ...],
624) -> None:
625 """Run one suite under hostile routing/config and prove no outer mutation."""
626 repo_root = Path(__file__).resolve().parents[2]
627 before = _outer_snapshot(outer)
628 environment = os.environ.copy()
629 for name in ("SSH_CLIENT", "SSH_CONNECTION", "SSH_TTY"):
630 environment.pop(name, None)
631 environment.update(
632 {
633 "GIT_INDEX_FILE": str(outer / ".git" / "index"),
634 "GIT_OBJECT_DIRECTORY": str(outer / ".git" / "objects"),
635 "PYTHONDONTWRITEBYTECODE": "1",
636 **hostile,
637 }
638 )
639 proc = subprocess.run( # noqa: S603 -- fixed interpreter and audited selftest paths
640 argv,
641 cwd=repo_root,
642 env=environment,
643 capture_output=True,
644 check=False,
645 timeout=180,
646 )
647 if proc.returncode != 0:
648 detail = os.fsdecode(proc.stderr or proc.stdout).strip()
649 _fail(f"{label} failed under hostile Git routing: {detail}")
650 if before != _outer_snapshot(outer):
651 _fail(f"{label} mutated hostile outer Git state")
652 fired = [str(path) for path in markers if path.exists()]
653 if fired:
654 _fail(f"{label} executed hostile inherited policy: {fired}")
655
656
657def _exercise_registered_fixture_selftests(outer: Path) -> None:
658 """Run every repaired fixture suite under routing and config/filter attacks."""
659 repo_root = Path(__file__).resolve().parents[2]
660 git_hostile, git_marker = _hostile_config_environment(outer.parent / "config-attack")
661 process_hostile, process_markers = _hostile_process_environment(outer.parent / "process-attack")
662 hostile = {**git_hostile, **process_hostile}
663 markers = (git_marker, *process_markers)
664 _prove_hostile_config_executes(outer.parent, git_hostile, git_marker)
665 _prove_hostile_process_attacks_execute(process_hostile, process_markers)
666 for label, argv in _registered_fixture_commands(repo_root):
667 _run_registered_fixture(outer, hostile, markers, label, argv)
668
669
670def _prepare_snapshot_source(base: Path) -> tuple[Path, tuple[Path, Path, Path]]:
671 """Create a committed EOL/filter tree with executable local Git policy."""
672 source = base / "snapshot-source"
673 source.mkdir()
674 _git(source, "init", "--quiet")
675 local_marker = base / "source-local-filter"
676 fsmonitor_marker = base / "source-local-fsmonitor"
677 template_marker = base / "source-local-template-executed"
678 local_helper = base / "source-local-helper.sh"
679 local_helper.write_text(
680 f"#!/bin/sh\nprintf x >> {shlex.quote(str(local_marker))}\ncat\n",
681 encoding="ascii",
682 )
683 local_helper.chmod(0o755)
684 fsmonitor = base / "source-local-fsmonitor.sh"
685 fsmonitor.write_text(
686 f"#!/bin/sh\nprintf x >> {shlex.quote(str(fsmonitor_marker))}\nprintf '\\n'\n",
687 encoding="ascii",
688 )
689 fsmonitor.chmod(0o755)
690 template = base / "source-local-template-dir"
691 (template / "hooks").mkdir(parents=True)
692 template_hook = template / "hooks/reference-transaction"
693 template_hook.write_text(
694 f"#!/bin/sh\nprintf x >> {shlex.quote(str(template_marker))}\n",
695 encoding="ascii",
696 )
697 template_hook.chmod(0o755)
698 (source / ".gitattributes").write_text(
699 "sentinel.txt text eol=crlf\nevil.bin filter=evil\n", encoding="ascii"
700 )
701 (source / "sentinel.txt").write_text("snapshot sentinel\n", encoding="ascii")
702 (source / "evil.bin").write_bytes(b"raw fixture\n")
703 _git(source, "add", ".gitattributes", "sentinel.txt", "evil.bin")
704 _git(
705 source,
706 "-c",
707 "user.email=selftest@invalid",
708 "-c",
709 "user.name=selftest",
710 "commit",
711 "--quiet",
712 "-m",
713 "snapshot",
714 )
715 _git(source, "config", "--local", "filter.evil.clean", str(local_helper))
716 _git(source, "config", "--local", "filter.evil.smudge", str(local_helper))
717 _git(source, "config", "--local", "filter.evil.required", "true")
718 _git(source, "config", "--local", "core.fsmonitor", str(fsmonitor))
719 _git(source, "config", "--local", "init.templateDir", str(template))
720 return source, (local_marker, fsmonitor_marker, template_marker)
721
722
723def _exercise_shell_snapshot_boundary(outer: Path) -> None:
724 """Prove fresh checkout keeps EOL semantics without source Git helpers."""
725 repo_root = Path(__file__).resolve().parents[2]
726 hostile, marker = _hostile_config_environment(outer.parent / "shell-config-attack")
727 _prove_hostile_config_executes(outer.parent / "shell-config-attack", hostile, marker)
728 source, local_markers = _prepare_snapshot_source(outer.parent)
729 output = outer.parent / "snapshot-output"
730 output.mkdir()
731 before = _outer_snapshot(outer)
732 environment = os.environ.copy()
733 environment.update(
734 {
735 "GIT_INDEX_FILE": str(outer / ".git" / "index"),
736 "GIT_OBJECT_DIRECTORY": str(outer / ".git" / "objects"),
737 **hostile,
738 }
739 )
740 script = """
741set -euo pipefail
742source "$1/scripts/dev/git_environment.sh"
743source "$1/scripts/ci/lib/snapshot.sh"
744REPO_ROOT="$2"
745materialise_head_snapshot "$3"
746git -C "$3" ls-files --error-unmatch sentinel.txt >/dev/null
747"""
748 proc = subprocess.run( # noqa: S603 -- fixed Bash executable and audited fixture argv
749 [
750 "/bin/bash",
751 "-p",
752 "-c",
753 script,
754 "shell-snapshot",
755 str(repo_root),
756 str(source),
757 str(output),
758 ],
759 env=environment,
760 capture_output=True,
761 check=False,
762 timeout=60,
763 )
764 if proc.returncode != 0:
765 detail = os.fsdecode(proc.stderr or proc.stdout).strip()
766 _fail(f"shell snapshot boundary failed under hostile Git policy: {detail}")
767 if before != _outer_snapshot(outer):
768 _fail("shell snapshot boundary mutated hostile outer Git state")
769 if any(path.exists() for path in (marker, *local_markers)):
770 _fail("shell snapshot boundary executed inherited or source-local Git policy")
771 if (output / "sentinel.txt").read_bytes() != b"snapshot sentinel\r\n":
772 _fail("strict snapshot lost committed CRLF checkout semantics")
773 if (output / "evil.bin").read_bytes() != b"raw fixture\n":
774 _fail("strict snapshot changed an unconfigured filtered blob")
775
776
777def _exercise_shell_push_transport(root: Path) -> None:
778 """Prove bounded push keeps SSH policy but cannot inherit repo routing."""
779 adapter = Path(__file__).with_suffix(".sh")
780 capture = root / "push-environment"
781 capture_helper = root / "capture-push-environment"
782 capture_helper.write_text(
783 "#!/bin/sh\n"
784 "printf '%s\\n%s\\n%s\\n%s\\n' \"${GIT_SSH_COMMAND-unset}\" "
785 '"${GIT_DIR-unset}" "${GIT_CONFIG_COUNT-unset}" "$*" >"$RA8_PUSH_CAPTURE"\n',
786 encoding="ascii",
787 )
788 capture_helper.chmod(0o755)
789 environment = os.environ.copy()
790 environment.update(
791 {
792 "GIT_DIR": str(root / "hostile.git"),
793 "GIT_CONFIG_COUNT": "1",
794 "GIT_CONFIG_KEY_0": "core.hooksPath",
795 "GIT_CONFIG_VALUE_0": str(root / "hostile-hooks"),
796 "GIT_SSH_COMMAND": "ssh -F operator-config",
797 "RA8_PUSH_CAPTURE": str(capture),
798 "RA8_PUSH_HELPER": str(capture_helper),
799 }
800 )
801 script = (
802 'source "$1"\n'
803 "run_git_network_with_inherited_transport -c "
804 "'alias.ra8-capture=! \"$RA8_PUSH_HELPER\"' "
805 "ra8-capture push origin gh-pages\n"
806 )
807 proc = subprocess.run( # noqa: S603 -- fixed Bash and controlled fixture argv
808 ["/bin/bash", "-p", "-c", script, "shell-push", str(adapter)],
809 env=environment,
810 capture_output=True,
811 check=False,
812 timeout=30,
813 )
814 if proc.returncode != 0:
815 _fail(f"shell push transport boundary failed: {os.fsdecode(proc.stderr).strip()}")
816 lines = capture.read_text(encoding="ascii").splitlines()
817 expected_tail = ["unset", "push origin gh-pages"]
818 hostile_dir = str(root / "hostile.git")
819 if (
820 len(lines) != PUSH_CAPTURE_FIELDS
821 or lines[0] != "ssh -F operator-config"
822 or lines[1] == hostile_dir
823 or lines[2:] != expected_tail
824 ):
825 _fail(f"shell push transport selected the wrong environment/argv: {lines!r}")
826
827
828def _exercise_shell_command_authority(root: Path) -> None:
829 """Prove aliases, functions, hashes, PATH, and source tools cannot select Git."""
830 adapter = Path(__file__).with_suffix(".sh")
831 script = r"""
832set -euo pipefail
833mode="$1"
834helper="$2"
835marker="$3"
836adapter="$4"
837case "$mode" in
838 alias)
839 shopt -s expand_aliases
840 alias git="'$helper'"
841 eval 'git control'
842 ;;
843 function)
844 git() { "$helper" "$@"; }
845 git control
846 ;;
847 hash)
848 hash -p "$helper" git
849 git control
850 ;;
851 path | source-venv)
852 PATH="$(dirname "$helper"):$PATH"
853 git control
854 ;;
855 *) exit 64 ;;
856esac
857[[ -s "$marker" ]]
858rm -f -- "$marker"
859source "$adapter"
860run_sanitized_git --version >/dev/null
861[[ ! -e "$marker" ]]
862"""
863 for mode in ("alias", "function", "hash", "path", "source-venv"):
864 directory = root / ("source/.venv/bin" if mode == "source-venv" else f"{mode}-bin")
865 directory.mkdir(parents=True)
866 marker = root / f"{mode}.fired"
867 helper = directory / "git"
868 helper.write_text(f"#!/bin/sh\nprintf x >>{shlex.quote(str(marker))}\n", encoding="ascii")
869 helper.chmod(0o755)
870 proc = subprocess.run( # noqa: S603 -- fixed Bash and private authority fixture
871 [
872 "/bin/bash",
873 "-p",
874 "-c",
875 script,
876 "authority",
877 mode,
878 str(helper),
879 str(marker),
880 str(adapter),
881 ],
882 capture_output=True,
883 check=False,
884 timeout=30,
885 )
886 if proc.returncode != 0 or marker.exists():
887 detail = os.fsdecode(proc.stderr or proc.stdout).strip()
888 _fail(f"shell Git authority failed for {mode}: {detail}")
889
890
891def _prove_hostile_index_and_object_routing(outer: Path, inner: Path) -> None:
892 """Prove the external index/object environment would mutate without isolation."""
893 _init_outer(outer)
894 inner.mkdir()
895 _git(inner, "init", "--quiet")
896 (inner / "probe.txt").write_text("hostile routing probe\n", encoding="ascii")
897 index_before = (outer / ".git" / "index").read_bytes()
898 objects_before = _tree_digest(outer / ".git" / "objects")
899 with isolated_git_environment():
900 os.environ["GIT_INDEX_FILE"] = str(outer / ".git" / "index")
901 os.environ["GIT_OBJECT_DIRECTORY"] = str(outer / ".git" / "objects")
902 _git(inner, "add", "probe.txt", clean=False)
903 if (outer / ".git" / "index").read_bytes() == index_before:
904 _fail("hostile GIT_INDEX_FILE probe did not redirect the fixture index")
905 if _tree_digest(outer / ".git" / "objects") == objects_before:
906 _fail("hostile GIT_OBJECT_DIRECTORY probe did not redirect fixture objects")
907
908
909def run_selftest() -> int:
910 """Verify nested fixture activity cannot alter a hostile outer repository."""
911 original = dict(os.environ)
912 try:
913 with tempfile.TemporaryDirectory(prefix="ra8-git-environment-") as temp:
914 base = Path(temp)
915 outer = base / "outer"
916 inner = base / "inner"
917 before = _init_outer(outer)
918 inner.mkdir()
919 _exercise_nested_repo(outer, inner)
920 after = _outer_snapshot(outer)
921 if before != after:
922 _fail("nested fixture mutated outer Git or worktree state")
923 if os.fsdecode(_git(inner, "status", "--porcelain=v1")).strip():
924 _fail("nested fixture repository is not clean")
925 _exercise_registered_fixture_selftests(outer)
926 _exercise_shell_snapshot_boundary(outer)
927 _exercise_shell_push_transport(base)
928 _exercise_shell_command_authority(base)
929 _prove_hostile_index_and_object_routing(base / "probe-outer", base / "probe-inner")
930 _prove_inherited_global_config_remains_available(base)
931 except (GitEnvironmentError, OSError) as exc:
932 print(f"SELFTEST FAIL: {exc}")
933 return 1
934 finally:
935 os.environ.clear()
936 os.environ.update(original)
937 print(
938 "selftest: hostile routing/config/filter environment, shell snapshot/push, and "
939 "8 registered fixture suites stay isolated: OK"
940 )
941 return 0
942
943
944def main() -> int:
945 """Print the shared variable list or run its mutation regression."""
946 parser = argparse.ArgumentParser(description=__doc__)
947 group = parser.add_mutually_exclusive_group(required=True)
948 group.add_argument("--names", action="store_true")
949 group.add_argument("--shell-contract", action="store_true")
950 group.add_argument("--network-shell-contract", action="store_true")
951 group.add_argument("--selftest", action="store_true")
952 group.add_argument("--check-attributes", metavar="ROOT")
953 parser.add_argument("--commit")
954 args = parser.parse_args()
955 if args.commit is not None and args.check_attributes is None:
956 parser.error("--commit requires --check-attributes")
957 if args.names:
958 print("\n".join(LOCAL_GIT_ENVIRONMENT))
959 return 0
960 if args.shell_contract or args.network_shell_contract:
961 for action, name, value in _shell_contract(os.environ, network=args.network_shell_contract):
962 print(f"{action}\t{name}\t{value}")
963 return 0
964 if args.selftest:
965 return run_selftest()
966 try:
967 reject_untrusted_executable_attributes(Path(args.check_attributes), args.commit)
968 except (GitEnvironmentError, OSError) as exc:
969 print(f"Git attribute policy: FAIL: {exc}", file=sys.stderr)
970 return 1
971 print("Git attribute policy: PASS")
972 return 0
973
974
975if __name__ == "__main__":
976 raise SystemExit(main())
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298