ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
hil_convergence_safety_v9.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Semantic checks for public startup and live bench capabilities."""
4
5from __future__ import annotations
6
7import ast
8import json
9import os
10import pwd
11import re
12import shutil
13import subprocess
14import sys
15import tempfile
16from pathlib import Path
17
18import hil_convergence_safety_image_lock_receipts as image_lock_receipts
19import hil_convergence_safety_policy as policy
20import hil_convergence_safety_roles as roles
21import yaml
22from check_shebangs import (
23 PINNED_INTERPRETER_BOUNDARIES,
24 PRIVILEGED_BODY_CLOSE,
25 PRIVILEGED_BODY_PREFIX,
26)
27
28CONTEXT_INPUT_FIELD_COUNT = 2
29DEV_DIR = Path(__file__).resolve().parents[1] / "dev"
30HIL_LIB = Path(__file__).resolve().parents[1] / "hil/lib"
31for module_dir in (DEV_DIR, HIL_LIB):
32 sys.path.insert(0, str(module_dir))
33
34import bench_lock_broker as broker # noqa: E402 -- DEV_DIR is inserted above
35import bench_lock_capability as capability # noqa: E402 -- DEV_DIR is inserted above
36import bench_lock_verify as lock_verify # noqa: E402 -- HIL_LIB is inserted above
37import fleet_transaction_auth as transaction # noqa: E402 -- DEV_DIR is inserted above
38
39
40def _lock_verifier_errors(source: str) -> list[str]:
41 """Require nonblocking descriptor classification and its live selftest."""
42 try:
43 tree = ast.parse(source)
44 except SyntaxError:
45 return ["bench lock verifier: invalid Python"]
46 digest = next(
47 (
48 node
49 for node in tree.body
50 if isinstance(node, ast.FunctionDef) and node.name == "_has_script_digest"
51 ),
52 None,
53 )
54 expected = ast.parse("if not stat.S_ISREG(before.st_mode):\n continue").body[0]
55 matches = (
56 []
57 if digest is None
58 else [node for node in ast.walk(digest) if ast.dump(node) == ast.dump(expected)]
59 )
60 errors = [] if len(matches) == 1 else ["bench lock verifier: non-regular fds may block"]
61 run = next(
62 (
63 node
64 for node in tree.body
65 if isinstance(node, ast.FunctionDef) and node.name == "run_selftest"
66 ),
67 None,
68 )
69 calls = [
70 node
71 for node in (() if run is None else ast.walk(run))
72 if isinstance(node, ast.Call)
73 and isinstance(node.func, ast.Name)
74 and node.func.id == "_live_descriptor_selftest"
75 ]
76 if len(calls) != 1:
77 errors.append("bench lock verifier: live descriptor selftest is not load-bearing")
78 return errors
79
80
81def _canonical_context_inputs(source: str) -> set[str]:
82 """Return exact paths from the image builder's canonical root-input table."""
83 pattern = re.compile(
84 r"(?ms)^ canonical_root_context_inputs\‍(\‍) \{\n"
85 r" cat <<'EOF'\n(.*?)^EOF\n \}"
86 )
87 matches = pattern.findall(source)
88 if len(matches) != 1:
89 return set()
90 rows = [line.split(maxsplit=1) for line in matches[0].splitlines() if line]
91 if any(len(row) != CONTEXT_INPUT_FIELD_COUNT or not row[0].isdigit() for row in rows):
92 return set()
93 return {row[1] for row in rows}
94
95
96def _context_scope_errors(
97 defaults_source: str, transaction_source: str, image_source: str
98) -> list[str]:
99 """Require every asserted provisioning input to enter the exact archive."""
100 try:
101 defaults = yaml.safe_load(defaults_source)
102 tasks = yaml.safe_load(transaction_source)
103 except yaml.YAMLError:
104 return ["dev box context: malformed defaults or transaction"]
105 scopes = defaults.get("dev_box_context_paths") if isinstance(defaults, dict) else None
106 if (
107 not isinstance(scopes, list)
108 or not scopes
109 or any(not isinstance(scope, str) or not scope for scope in scopes)
110 or not isinstance(tasks, list)
111 ):
112 return ["dev box context: archive scopes are missing or malformed"]
113 matches = [
114 task
115 for task in tasks
116 if isinstance(task, dict) and task.get("name") == "Assert the staged context arrived"
117 ]
118 required = matches[0].get("loop") if len(matches) == 1 else None
119 if not isinstance(required, list) or any(not isinstance(path, str) for path in required):
120 return ["dev box context: authoritative input census is missing or malformed"]
121 consumed = _canonical_context_inputs(image_source)
122 if not consumed:
123 return ["dev box context: image root-input authority is missing or malformed"]
124
125 def covered(path: str) -> bool:
126 return any(path == scope or path.startswith(scope.rstrip("/") + "/") for scope in scopes)
127
128 errors: list[str] = []
129 missing = sorted(path for path in {*required, *consumed} if not covered(path))
130 if missing:
131 errors.append(
132 "dev box context: required paths escape archive scopes: " + ", ".join(missing)
133 )
134 unasserted = sorted(consumed - set(required))
135 if unasserted:
136 errors.append(
137 "dev box context: consumed root inputs escape the assertion: " + ", ".join(unasserted)
138 )
139 return errors
140
141
142def _image_lock_tasks(
143 tasks: list[object], names: tuple[str, ...]
144) -> tuple[dict[str, dict[str, object]], list[str]]:
145 """Return each required image-lock task exactly once."""
146 found: dict[str, dict[str, object]] = {}
147 errors: list[str] = []
148 for name in names:
149 matches = [task for task in tasks if isinstance(task, dict) and task.get("name") == name]
150 if len(matches) == 1:
151 found[name] = matches[0]
152 else:
153 errors.append(f"dev box image lock: task is not unique: {name}")
154 return found, errors
155
156
157def _normalized_conditions(task: dict[str, object]) -> set[str] | None:
158 """Return an assert task's whitespace-normalized conditions."""
159 assertion = task.get("ansible.builtin.assert")
160 conditions = assertion.get("that") if isinstance(assertion, dict) else None
161 if not isinstance(conditions, list) or not all(isinstance(item, str) for item in conditions):
162 return None
163 return {" ".join(item.split()) for item in conditions}
164
165
166def _stat_task_is_exact(task: dict[str, object], path: str, register: str) -> bool:
167 """Return whether a stat task pins its path, no-follow behavior, and result."""
168 stat = task.get("ansible.builtin.stat")
169 return (
170 task.get("become") is True
171 and isinstance(stat, dict)
172 and stat.get("path") == path
173 and stat.get("follow") is False
174 and task.get("register") == register
175 )
176
177
178def _image_lock_directory_errors(named: dict[str, dict[str, object]]) -> list[str]:
179 """Require refusal, convergence, and post-proof for the managed directory."""
180 errors: list[str] = []
181 path = "{{ dev_box_image_lock_dir }}"
182 inspect_name = "Inspect the managed image lock directory without following links"
183 if not _stat_task_is_exact(named[inspect_name], path, "dev_box_image_lock_dir_before"):
184 errors.append("dev box image lock: directory inspection may follow links")
185 refuse_name = "Refuse an unsafe managed image lock directory"
186 required_refusal = {
187 "not dev_box_image_lock_dir_before.stat.exists or "
188 "(dev_box_image_lock_dir_before.stat.isdir and "
189 "not dev_box_image_lock_dir_before.stat.islnk and "
190 "dev_box_image_lock_dir_before.stat.uid == 0 and "
191 "dev_box_image_lock_dir_before.stat.gid "
192 "== (dev_box_image_lock_gid.stdout | int) and "
193 "dev_box_image_lock_dir_before.stat.mode == '0750')"
194 }
195 if _normalized_conditions(named[refuse_name]) != required_refusal:
196 errors.append("dev box image lock: unsafe directory refusal drifted")
197 expected_directory = {
198 "path": path,
199 "state": "directory",
200 "owner": "root",
201 "group": "{{ dev_box_image_lock_gid.stdout }}",
202 "mode": "0750",
203 }
204 create_name = "Create the managed image lock directory"
205 if named[create_name].get("ansible.builtin.file") != expected_directory or not named[
206 create_name
207 ].get("become"):
208 errors.append("dev box image lock: directory ownership or mode drifted")
209 post_name = "Reinspect the converged managed image lock directory"
210 if (
211 not _stat_task_is_exact(named[post_name], path, "dev_box_image_lock_dir_after")
212 or named[post_name].get("when") != "not ansible_check_mode"
213 ):
214 errors.append("dev box image lock: directory post-stat may follow links")
215 proof_name = "Prove the managed image lock directory identity and permissions"
216 required_proof = {
217 "dev_box_image_lock_dir_after.stat.isdir",
218 "not dev_box_image_lock_dir_after.stat.islnk",
219 "dev_box_image_lock_dir_after.stat.uid == 0",
220 "dev_box_image_lock_dir_after.stat.gid == (dev_box_image_lock_gid.stdout | int)",
221 "dev_box_image_lock_dir_after.stat.mode == '0750'",
222 }
223 if (
224 _normalized_conditions(named[proof_name]) != required_proof
225 or named[proof_name].get("when") != "not ansible_check_mode"
226 ):
227 errors.append("dev box image lock: directory post-proof drifted")
228 return errors
229
230
231def _image_lock_file_errors(named: dict[str, dict[str, object]]) -> list[str]:
232 """Require the stable single-link file authority and post-proof."""
233 errors: list[str] = []
234 lock_path = "{{ dev_box_image_lock_dir }}/devcontainer-image.lock"
235 inspect_name = "Inspect the managed image lock file without following links"
236 if not _stat_task_is_exact(named[inspect_name], lock_path, "dev_box_image_lock_before"):
237 errors.append("dev box image lock: file inspection may follow links")
238 refuse_name = "Refuse an unsafe managed image lock file"
239 required_refusal = {
240 "not dev_box_image_lock_before.stat.exists or "
241 "(dev_box_image_lock_before.stat.isreg and "
242 "not dev_box_image_lock_before.stat.islnk and "
243 "dev_box_image_lock_before.stat.nlink == 1 and "
244 "dev_box_image_lock_before.stat.uid == 0 and "
245 "dev_box_image_lock_before.stat.gid "
246 "== (dev_box_image_lock_gid.stdout | int) and "
247 "dev_box_image_lock_before.stat.mode == '0660')"
248 }
249 if _normalized_conditions(named[refuse_name]) != required_refusal:
250 errors.append("dev box image lock: unsafe file refusal drifted")
251 expected_lock = {
252 "path": lock_path,
253 "state": "touch",
254 "follow": False,
255 "owner": "root",
256 "group": "{{ dev_box_image_lock_gid.stdout }}",
257 "mode": "0660",
258 "access_time": "preserve",
259 "modification_time": "preserve",
260 }
261 create_name = "Create the stable managed image lock file"
262 if named[create_name].get("ansible.builtin.file") != expected_lock or not named[
263 create_name
264 ].get("become"):
265 errors.append("dev box image lock: file ownership, mode, or inode stability drifted")
266 post_name = "Reinspect the converged managed image lock"
267 if (
268 not _stat_task_is_exact(named[post_name], lock_path, "dev_box_image_lock_after")
269 or named[post_name].get("when") != "not ansible_check_mode"
270 ):
271 errors.append("dev box image lock: file post-stat may follow links")
272 proof_name = "Prove the managed image lock identity and permissions"
273 required_conditions = {
274 "dev_box_image_lock_after.stat.isreg",
275 "not dev_box_image_lock_after.stat.islnk",
276 "dev_box_image_lock_after.stat.nlink == 1",
277 "dev_box_image_lock_after.stat.uid == 0",
278 "dev_box_image_lock_after.stat.gid == (dev_box_image_lock_gid.stdout | int)",
279 "dev_box_image_lock_after.stat.gid == dev_box_image_lock_dir_after.stat.gid",
280 "dev_box_image_lock_after.stat.mode == '0660'",
281 }
282 if (
283 _normalized_conditions(named[proof_name]) != required_conditions
284 or named[proof_name].get("when") != "not ansible_check_mode"
285 ):
286 errors.append("dev box image lock: post-convergence identity proof drifted")
287 return errors
288
289
290def _image_lock_gid_errors(named: dict[str, dict[str, object]]) -> list[str]:
291 """Require the non-root numeric primary-group authority."""
292 resolve = named["Resolve the managed image lock numeric primary group"]
293 command = resolve.get("ansible.builtin.command")
294 expected = {"argv": ["/usr/bin/id", "-g", "--", "{{ dev_box_user }}"]}
295 errors: list[str] = []
296 if (
297 resolve.get("become") is not True
298 or command != expected
299 or resolve.get("register") != "dev_box_image_lock_gid"
300 or resolve.get("changed_when") is not False
301 or resolve.get("check_mode") is not False
302 ):
303 errors.append("dev box image lock: numeric group resolution drifted")
304 required = {
305 "dev_box_image_lock_gid.stdout is regex('^[0-9]+$')",
306 "(dev_box_image_lock_gid.stdout | int) > 0",
307 }
308 refusal = named["Refuse an unsafe managed image lock numeric primary group"]
309 if _normalized_conditions(refusal) != required:
310 errors.append("dev box image lock: unsafe numeric group refusal drifted")
311 return errors
312
313
314def _image_lock_marker_errors(named: dict[str, dict[str, object]]) -> list[str]:
315 """Require the root-owned immutable numeric-group marker authority."""
316 marker = "{{ dev_box_image_lock_dir }}/devcontainer-image.gid"
317 return [
318 *_image_lock_marker_inspection_errors(named, marker),
319 *_image_lock_marker_convergence_errors(named, marker),
320 *_image_lock_marker_proof_errors(named),
321 ]
322
323
324def _image_lock_marker_inspection_errors(
325 named: dict[str, dict[str, object]], marker: str
326) -> list[str]:
327 """Require no-follow inspection and exact refusal of an unsafe marker."""
328 errors: list[str] = []
329 inspect = named["Inspect the managed image lock group marker without following links"]
330 inspect_stat = inspect.get("ansible.builtin.stat")
331 if (
332 not _stat_task_is_exact(inspect, marker, "dev_box_image_lock_gid_marker_before")
333 or not isinstance(inspect_stat, dict)
334 or inspect_stat.get("get_checksum") is not True
335 or inspect_stat.get("checksum_algorithm") != "sha256"
336 ):
337 errors.append("dev box image lock: group marker inspection may follow links")
338 required_refusal = {
339 "not dev_box_image_lock_gid_marker_before.stat.exists or "
340 "(dev_box_image_lock_gid_marker_before.stat.isreg and "
341 "not dev_box_image_lock_gid_marker_before.stat.islnk and "
342 "dev_box_image_lock_gid_marker_before.stat.nlink == 1 and "
343 "dev_box_image_lock_gid_marker_before.stat.uid == 0 and "
344 "dev_box_image_lock_gid_marker_before.stat.gid == 0 and "
345 "dev_box_image_lock_gid_marker_before.stat.mode == '0444' and "
346 "dev_box_image_lock_gid_marker_before.stat.checksum "
347 "== ((dev_box_image_lock_gid.stdout ~ '\\n') | hash('sha256')))"
348 }
349 refuse = named["Refuse an unsafe managed image lock group marker"]
350 if _normalized_conditions(refuse) != required_refusal:
351 errors.append("dev box image lock: unsafe group marker refusal drifted")
352 return errors
353
354
355def _image_lock_marker_convergence_errors(
356 named: dict[str, dict[str, object]], marker: str
357) -> list[str]:
358 """Require atomic marker convergence and an exact post-stat."""
359 errors: list[str] = []
360 expected_copy = {
361 "dest": marker,
362 "content": "{{ dev_box_image_lock_gid.stdout }}\n",
363 "owner": "root",
364 "group": "root",
365 "mode": "0444",
366 "unsafe_writes": False,
367 }
368 create = named["Create the managed image lock numeric group marker atomically"]
369 if (
370 create.get("become") is not True
371 or create.get("ansible.builtin.copy") != expected_copy
372 or create.get("when")
373 != "not ansible_check_mode or dev_box_image_lock_dir_before.stat.exists"
374 ):
375 errors.append("dev box image lock: group marker atomic convergence drifted")
376 post = named["Reinspect the converged managed image lock group marker"]
377 post_stat = post.get("ansible.builtin.stat")
378 if (
379 not _stat_task_is_exact(post, marker, "dev_box_image_lock_gid_marker_after")
380 or not isinstance(post_stat, dict)
381 or post_stat.get("get_checksum") is not True
382 or post_stat.get("checksum_algorithm") != "sha256"
383 or post.get("when") != "not ansible_check_mode"
384 ):
385 errors.append("dev box image lock: group marker post-stat drifted")
386 return errors
387
388
389def _image_lock_marker_proof_errors(
390 named: dict[str, dict[str, object]],
391) -> list[str]:
392 """Require the marker, directory, and lock to share the approved identity."""
393 required_proof = {
394 "dev_box_image_lock_gid_marker_after.stat.isreg",
395 "not dev_box_image_lock_gid_marker_after.stat.islnk",
396 "dev_box_image_lock_gid_marker_after.stat.nlink == 1",
397 "dev_box_image_lock_gid_marker_after.stat.uid == 0",
398 "dev_box_image_lock_gid_marker_after.stat.gid == 0",
399 "dev_box_image_lock_gid_marker_after.stat.mode == '0444'",
400 "dev_box_image_lock_gid_marker_after.stat.checksum "
401 "== ((dev_box_image_lock_gid.stdout ~ '\\n') | hash('sha256'))",
402 "dev_box_image_lock_dir_after.stat.gid == (dev_box_image_lock_gid.stdout | int)",
403 "dev_box_image_lock_after.stat.gid == (dev_box_image_lock_gid.stdout | int)",
404 }
405 proof = named["Prove the managed image lock group marker identity and content"]
406 if (
407 _normalized_conditions(proof) != required_proof
408 or proof.get("when") != "not ansible_check_mode"
409 ):
410 return ["dev box image lock: group marker identity proof drifted"]
411 return []
412
413
414def _image_lock_environment_errors(named: dict[str, dict[str, object]]) -> list[str]:
415 """Require both root image commands to consume the managed path explicitly."""
416 expected_environment = {
417 "RA8_CI_IMAGE": "{{ dev_box_ci_image }}",
418 "RA8_IMAGE_LOCK_DIR": "{{ dev_box_image_lock_dir }}",
419 }
420 command_names = (
421 "Prove the staleness check itself works, before trusting its verdict",
422 "Build the gate image unless the cached one matches this context",
423 )
424 return [
425 f"dev box image lock: explicit environment drifted: {name}"
426 for name in command_names
427 if named[name].get("environment") != expected_environment
428 ]
429
430
431def _image_lock_order_errors(tasks: list[object]) -> list[str]:
432 """Require refusal-before-repair and selftest-before-use task ordering."""
433 positions = {
434 task["name"]: index
435 for index, task in enumerate(tasks)
436 if isinstance(task, dict) and isinstance(task.get("name"), str)
437 }
438 refusals = (
439 "Refuse an unsafe managed image lock directory",
440 "Refuse an unsafe managed image lock file",
441 "Refuse an unsafe managed image lock group marker",
442 )
443 creations = (
444 "Create the managed image lock directory",
445 "Create the stable managed image lock file",
446 "Create the managed image lock numeric group marker atomically",
447 )
448 required_pairs = (
449 (
450 "Resolve the managed image lock numeric primary group",
451 "Refuse an unsafe managed image lock numeric primary group",
452 ),
453 (
454 "Refuse an unsafe managed image lock numeric primary group",
455 creations[0],
456 ),
457 *((refusal, creation) for refusal in refusals for creation in creations),
458 (
459 "Prove the managed image lock group marker identity and content",
460 "Prove the staleness check itself works, before trusting its verdict",
461 ),
462 (
463 "Prove the staleness check itself works, before trusting its verdict",
464 "Build the gate image unless the cached one matches this context",
465 ),
466 )
467 return [
468 f"dev box image lock: required task order drifted: {before} before {after}"
469 for before, after in required_pairs
470 if positions.get(before, len(tasks)) >= positions.get(after, -1)
471 ]
472
473
474def _image_lock_profile_errors(tasks: list[object]) -> list[str]:
475 """Refuse a profile lock authority now that canonical discovery is exact."""
476 name = "Set the container runtime and the shared compiler cache for every shell"
477 matches = [task for task in tasks if isinstance(task, dict) and task.get("name") == name]
478 copy = matches[0].get("ansible.builtin.copy") if len(matches) == 1 else None
479 content = copy.get("content") if isinstance(copy, dict) else None
480 if not isinstance(content, str):
481 return ["dev box image lock: shell profile task is missing"]
482 if "RA8_IMAGE_LOCK_DIR" in content:
483 return ["dev box image lock: shell profile is a second lock authority"]
484 return []
485
486
487def _image_lock_task_errors(tasks: list[object]) -> list[str]:
488 """Require exact Ansible ownership, identity, and caller environments."""
489 names = (
490 "Resolve the managed image lock numeric primary group",
491 "Refuse an unsafe managed image lock numeric primary group",
492 "Inspect the managed image lock directory without following links",
493 "Refuse an unsafe managed image lock directory",
494 "Create the managed image lock directory",
495 "Reinspect the converged managed image lock directory",
496 "Prove the managed image lock directory identity and permissions",
497 "Inspect the managed image lock file without following links",
498 "Refuse an unsafe managed image lock file",
499 "Create the stable managed image lock file",
500 "Reinspect the converged managed image lock",
501 "Prove the managed image lock identity and permissions",
502 "Inspect the managed image lock group marker without following links",
503 "Refuse an unsafe managed image lock group marker",
504 "Create the managed image lock numeric group marker atomically",
505 "Reinspect the converged managed image lock group marker",
506 "Prove the managed image lock group marker identity and content",
507 "Prove the staleness check itself works, before trusting its verdict",
508 "Build the gate image unless the cached one matches this context",
509 )
510 named, errors = _image_lock_tasks(tasks, names)
511 if errors:
512 return errors
513 return [
514 *_image_lock_gid_errors(named),
515 *_image_lock_directory_errors(named),
516 *_image_lock_file_errors(named),
517 *_image_lock_marker_errors(named),
518 *_image_lock_environment_errors(named),
519 *_image_lock_order_errors(tasks),
520 *_image_lock_profile_errors(tasks),
521 ]
522
523
524def _image_lock_required_source_tokens() -> tuple[str, ...]:
525 """Return the exact production image-lock authority tokens."""
526 return (
527 'ra8_bound_entry="${RA8_SELFTEST_BOUND_ENTRY-}"',
528 "unset -v RA8_SELFTEST_BOUND_ENTRY",
529 '[[ "${BASH_SOURCE[0]}" =~ ^/proc/self/fd/[0-9]+$ &&',
530 '"$ra8_bound_entry" == /*/devcontainer_image.sh &&',
531 '-f "$ra8_bound_entry" && ! -L "$ra8_bound_entry" &&',
532 '"${BASH_SOURCE[0]}" -ef "$ra8_bound_entry"',
533 '[[ "$ra8_bound_entry" == "$SCRIPT_DIR/devcontainer_image.sh" ]] || {',
534 '"${BASH_SOURCE[0]}" -ef "$main_path"',
535 'CANONICAL_IMAGE_LOCK_DIR="/var/cache/ra8-devcontainer-image-lock"',
536 'IMAGE_LOCK_GROUP_GID=""',
537 "validate_managed_image_lock_group_marker() {",
538 "fd_size() {",
539 "validate_managed_image_lock_dir() {",
540 "validate_image_lock_file() {",
541 "validate_opened_image_lock() {",
542 "\n resolve_image_lock() {",
543 ' elif [[ -e "$canonical_dir" || -L "$canonical_dir" ||\n'
544 ' -e "$canonical_dir/devcontainer-image.lock" ||\n'
545 ' -L "$canonical_dir/devcontainer-image.lock" ||\n'
546 ' -e "$canonical_dir/devcontainer-image.gid" ||\n'
547 ' -L "$canonical_dir/devcontainer-image.gid" ]]; then',
548 '\n lock_dir="$canonical_dir"\n IMAGE_LOCK_MANAGED=1\n',
549 '[[ "$links" == "1" && "$owner" == "0" && "$group" == "0" && "$mode" == "444" ]]',
550 '[[ "$marker_gid" =~ ^[0-9]+$ && "$marker_gid" != "0" ]]',
551 "if IFS= read -r -n 1 extra <&7; then",
552 'size="$(fd_size 7)"',
553 "((size == ${#marker_gid} + 1)) ||\n"
554 ' die "managed image lock group marker must contain exactly one numeric gid line"',
555 '[[ "$opened" == "$identity" && "$current" == "$opened" ]] ||\n'
556 ' die "managed image lock group marker changed while opening it"',
557 '[[ "$owner" == "0" && "$mode" == "750" ]]',
558 '[[ "$group" == "$IMAGE_LOCK_GROUP_GID" ]] ||\n'
559 ' die "managed image lock directory gid does not match its marker: $group"',
560 '[[ "$group" == "$IMAGE_LOCK_GROUP_GID" ]] ||\n'
561 ' die "managed image lock gid does not match its marker: $group"',
562 'exec 9<"$IMAGE_LOCK_FILE"',
563 'build_locked "$want" forced "" 1',
564 "\n managed_image_lock_preflight\n",
565 'dispatch_image_lock_selftest "$@"',
566 "\n load_image_lock_selftest\n selftest_case_signal_child ",
567 'export IMAGE_LOCK_RECEIPTS_RAW_SHA256="',
568 '"$helper" == "$SCRIPT_DIR/devcontainer_image_lock_receipts.bash" ||',
569 '\n [[ "$IMAGE_LOCK_MANAGED" == "0" ]] ||\n'
570 ' die "flock is required for the managed image lock"',
571 )
572
573
574def _image_lock_required_case_tokens() -> tuple[str, ...]:
575 """Return the exact receipt-first image-lock loader tokens."""
576 return (
577 "load_image_lock_selftest() {",
578 'local receipts="$SCRIPT_DIR/devcontainer_image_lock_receipts.bash"',
579 'output="$(/bin/bash -p -- "$receipts" 2>&1)"',
580 '"$output" == "error: devcontainer image lock receipt helper is source-only"',
581 'source_approved_selftest_helper "$receipts" "$IMAGE_LOCK_RECEIPTS_RAW_SHA256"\n'
582 ' source_approved_selftest_helper "$helper" "$IMAGE_LOCK_SELFTEST_RAW_SHA256"',
583 "declare -F expected_image_lock_suite_receipts scenario_receipt_value",
584 "require_controller_cleanup_receipt_file dispatch_image_lock_selftest >/dev/null",
585 '\n dispatch_image_lock_selftest suite "$tmp"\n',
586 )
587
588
589def _image_lock_script_errors(image_source: str, cases_source: str) -> list[str]:
590 """Require discovery, no-create locking, and force serialization."""
591 required_source = _image_lock_required_source_tokens()
592 required_cases = _image_lock_required_case_tokens()
593 errors = []
594 if any(image_source.count(token) != 1 for token in required_source) or any(
595 cases_source.count(token) != 1 for token in required_cases
596 ):
597 errors.append("dev box image lock: fail-closed helper or selftest is not load-bearing")
598 expected_open_validations = 2
599 open_validation = "\n validate_opened_image_lock 9\n"
600 if image_source.count(open_validation) != expected_open_validations:
601 errors.append("dev box image lock: opened inode is not checked before and after flock")
602 if "/var/cache/ra8-tools" in image_source or "RA8_TOOLS_CACHE_DIR" in image_source:
603 errors.append("dev box image lock: unmanaged path still uses the sticky shared cache")
604 if 'exec 9>>"$IMAGE_LOCK_FILE"' in image_source or 'exec 9>"$IMAGE_LOCK_FILE"' in image_source:
605 errors.append("dev box image lock: managed open may recreate the lock")
606 if 'while [[ ! -e "$ready" ]]' in image_source:
607 errors.append("dev box image lock: force-contention readiness wait is unbounded")
608 return errors
609
610
611def _image_lock_selftest_errors(
612 source: str,
613 receipt_source: str,
614 cases_source: str,
615 signal_source: str,
616) -> list[str]:
617 """Require bounded process-group cleanup and every semantic attack."""
618 required_once = policy.image_lock_selftest_required_tokens()
619 receipt_required_once = policy.image_lock_receipt_required_tokens()
620 cases_required_once = policy.image_lock_cases_required_tokens()
621 signal_required_once = policy.image_lock_signal_required_tokens()
622 required_present = (
623 '"$SELFTEST_CASE_DIR/ready.status"',
624 '"$SELFTEST_CASE_DIR/done.status"',
625 "exec 8>&-",
626 )
627 errors = []
628 if (
629 any(source.count(token) != 1 for token in required_once)
630 or any(token not in source for token in required_present)
631 or any(receipt_source.count(token) != 1 for token in receipt_required_once)
632 or any(cases_source.count(token) != 1 for token in cases_required_once)
633 or any(signal_source.count(token) != 1 for token in signal_required_once)
634 ):
635 errors.append("dev box image lock: bounded selftest harness is not load-bearing")
636 forbidden = (
637 'kill -KILL "$SELFTEST_WORKER_PID"',
638 'kill -KILL "$controller"',
639 'wait "$SELFTEST_WORKER_PID" || true',
640 )
641 if any(
642 token in source
643 or token in receipt_source
644 or token in cases_source
645 or token in signal_source
646 for token in forbidden
647 ):
648 errors.append("dev box image lock: selftest contains an unbounded wait")
649 return errors
650
651
652def _image_lock_authority_errors(inputs: dict[str, str]) -> list[str]:
653 """Require one managed cross-user image lock and a private local default."""
654 defaults_source = inputs["dev_defaults"]
655 transaction_entry_source = inputs["dev_transaction"]
656 transaction_source = inputs["dev_main"]
657 image_source = inputs["devcontainer_image"]
658 image_receipt_source = inputs["devcontainer_image_lock_receipts"]
659 image_selftest_source = inputs["devcontainer_image_lock_selftest"]
660 image_cases_source = inputs["devcontainer_image_selftest_cases"]
661 image_signal_source = inputs["devcontainer_image_signal_selftest"]
662 include = (
663 "- name: Converge the managed devcontainer image lock authority\n"
664 " ansible.builtin.include_tasks: image_lock.yml\n"
665 )
666 try:
667 defaults = yaml.safe_load(defaults_source)
668 tasks = yaml.safe_load(transaction_source)
669 except yaml.YAMLError:
670 return ["dev box image lock: malformed defaults or transaction"]
671 if not isinstance(defaults, dict) or not isinstance(tasks, list):
672 return ["dev box image lock: defaults or transaction has the wrong shape"]
673 errors = _image_lock_task_errors(tasks)
674 if transaction_entry_source.count(include) != 1:
675 errors.append("dev box image lock: transaction include boundary drifted")
676 authority = "/var/cache/ra8-devcontainer-image-lock"
677 if defaults.get("dev_box_image_lock_dir") != authority:
678 errors.append("dev box image lock: managed directory authority drifted")
679 if image_source.count(f'CANONICAL_IMAGE_LOCK_DIR="{authority}"') != 1:
680 errors.append("dev box image lock: Ansible and script authorities diverged")
681 return [
682 *errors,
683 *_image_lock_script_errors(image_source, image_cases_source),
684 *_image_lock_selftest_errors(
685 image_selftest_source,
686 image_receipt_source,
687 image_cases_source,
688 image_signal_source,
689 ),
690 *image_lock_receipts.errors(
691 image_source,
692 image_receipt_source,
693 image_selftest_source,
694 image_cases_source,
695 image_signal_source,
696 ),
697 ]
698
699
700def semantic_errors() -> list[str]:
701 """Execute offline selftests for each new authentication boundary."""
702 failures = [
703 *capability.run_selftest(),
704 *broker.run_selftest(),
705 *lock_verify.run_selftest(),
706 *transaction.run_selftest(),
707 ]
708 return [f"HIL convergence v9 semantic selftest: {failure}" for failure in failures]
709
710
711def _semantic_authority_lines(lines: tuple[str, ...]) -> list[str]:
712 """Normalize wrapper presentation indentation for semantic comparison."""
713 return [line.strip() for line in lines]
714
715
716def startup_authority_selftest() -> list[str]:
717 """Prove an indented canonical wrapper retains the same semantics."""
718 return roles.startup_authority_selftest(PRIVILEGED_BODY_PREFIX)
719
720
721def _public_script_errors(source: str, rel: str, safe_path: str) -> list[str]:
722 """Require sanitation using check_shebangs' authority, never a drifting copy."""
723 expected_preamble = list(PINNED_INTERPRETER_BOUNDARIES[rel])
724 lines = source.splitlines()
725 if lines[: len(expected_preamble)] != expected_preamble:
726 return [f"{rel}: interpreter boundary is not exact"]
727 executable = [
728 line.strip()
729 for line in lines[len(expected_preamble) :]
730 if line.strip() and not line.lstrip().startswith("#")
731 ]
732 prefix = _semantic_authority_lines(PRIVILEGED_BODY_PREFIX)
733 suffix = _semantic_authority_lines(PRIVILEGED_BODY_CLOSE)
734 sanitizer = "unset PYTHONHOME PYTHONPATH RA8_TOOL_VENV"
735 if rel == "scripts/dev/provision_dev_box_toolchain.sh":
736 sanitizer += " TMPDIR"
737 expected = [
738 "set -euo pipefail",
739 "export BASH_ENV=/dev/null ENV=/dev/null PYTHONNOUSERSITE=1",
740 sanitizer,
741 f"PATH={safe_path}",
742 "export PATH",
743 ]
744 wrapper_ok = executable[: len(prefix)] == prefix and executable[-len(suffix) :] == suffix
745 body = executable[len(prefix) : -len(suffix)] if wrapper_ok else []
746 errors = [] if body[:5] == expected else [f"{rel}: startup sanitizer moved"]
747 if any(re.search(r"(?<![/\.\w-])bash\b", line) for line in executable):
748 errors.append(f"{rel}: child Bash resolves through caller PATH")
749 return errors
750
751
752def _installer_errors(inputs: dict[str, str]) -> list[str]:
753 """Check direct public setup and tool-provision entrypoint boundaries."""
754 return (
755 _public_script_errors(
756 inputs["setup_ansible"], "scripts/dev/setup_ansible.sh", "/usr/bin:/bin"
757 )
758 + _public_script_errors(
759 inputs["provision_toolchain"],
760 "scripts/dev/provision_dev_box_toolchain.sh",
761 "/usr/local/bin:/usr/bin:/bin",
762 )
763 + _public_script_errors(inputs["infra_bootstrap"], "infra/bootstrap.sh", "/usr/bin:/bin")
764 )
765
766
767def _infra_boundary_errors(infra_just: str, infra_sh: str) -> list[str]:
768 """Bind the dependency-free public boundary probe to sanitized infra."""
769 recipe = "_boundary_selftest:\n {{ infra }} --selftest-boundary"
770 endpoint = """if [[ "${1:-}" == --selftest-boundary ]]; then
771 if (($# != 1)); then
772 echo "error: infrastructure boundary selftest takes no arguments" >&2
773 exit 1
774 fi
775 exit 0
776fi"""
777 sanitizer = (
778 '[[ -z "${BASH_ENV:-}" && -z "${ENV:-}" && -z "${PYTHONPATH:-}"'
779 ' && -z "${PYTHONHOME:-}" ]] || {\n'
780 ' echo "error: infrastructure startup environment was not sanitized" >&2\n'
781 " exit 1\n}"
782 )
783 errors = []
784 if infra_just.count(recipe) != 1:
785 errors.append("just/infra.just: public boundary selftest recipe is not exact")
786 if infra_sh.count(endpoint) != 1:
787 errors.append("scripts/dev/infra.sh: boundary selftest endpoint is not exact")
788 if infra_sh.count(sanitizer) != 1:
789 errors.append("scripts/dev/infra.sh: startup sanitizer is not exact")
790 elif infra_sh.find(endpoint) < infra_sh.find(sanitizer) + len(sanitizer):
791 errors.append("scripts/dev/infra.sh: boundary selftest precedes startup sanitation")
792 return errors
793
794
795def _hil_shell_errors(raw_sources: str) -> list[str]:
796 """Require fixed privileged Bash at every HIL-owned shell boundary."""
797 try:
798 sources = json.loads(raw_sources)
799 except json.JSONDecodeError:
800 return ["HIL shell boundary inventory is malformed"]
801 if not isinstance(sources, dict) or not sources:
802 return ["HIL shell boundary inventory is empty"]
803 errors: list[str] = []
804 for path, source in sources.items():
805 if not isinstance(path, str) or not isinstance(source, str):
806 errors.append("HIL shell boundary inventory entry is malformed")
807 continue
808 shebangs = [line.strip() for line in source.splitlines() if line.lstrip().startswith("#!")]
809 if not shebangs or any(line != "#!/bin/bash -p" for line in shebangs):
810 errors.append(f"{path}: shell entry is not fixed privileged Bash")
811 executable = [line for line in source.splitlines() if not line.lstrip().startswith("#")]
812 if any(re.search(r"(?<!/)\bbash\b", line) for line in executable):
813 errors.append(f"{path}: active Bash invocation resolves through caller PATH")
814 if (
815 path == "scripts/ci/monitor.sh"
816 and source.count("ExecStart=/bin/bash -p $self daemon") != 1
817 ):
818 errors.append("scripts/ci/monitor.sh: generated service Bash argv is not exact")
819 return errors
820
821
822def _write_executable(path: Path, marker: Path) -> None:
823 """Write one hostile startup executable used only in the temp fixture."""
824 path.parent.mkdir(parents=True, exist_ok=True)
825 path.write_text(f"#!/bin/sh\nprintf x >>'{marker}'\nexit 91\n", encoding="utf-8")
826 path.chmod(0o755)
827
828
829def _hostile_environment(fixture: Path, marker: Path) -> dict[str, str]:
830 """Create one hostile caller environment without executing its payloads."""
831 startup = fixture / "startup.sh"
832 startup.write_text(f"printf x >>'{marker}'\n", encoding="utf-8")
833 site = fixture / "site"
834 site.mkdir()
835 (site / "sitecustomize.py").write_text(
836 f"from pathlib import Path\nPath({str(marker)!r}).write_text('x')\n",
837 encoding="utf-8",
838 )
839 fake_bin = fixture / "bin"
840 for name in ("bash", "dirname", "python3"):
841 _write_executable(fake_bin / name, marker)
842 fake_venv = fixture / "venv"
843 for name in ("python3", "ansible-playbook"):
844 _write_executable(fake_venv / f"bin/{name}", marker)
845 account = pwd.getpwuid(os.getuid())
846 return {
847 "BASH_ENV": str(startup),
848 "ENV": str(startup),
849 "HOME": account.pw_dir,
850 "LANG": "C.UTF-8",
851 "LC_ALL": "C.UTF-8",
852 "LOGNAME": account.pw_name,
853 "PATH": f"{fake_bin}:/usr/bin:/bin",
854 "PYTHONHOME": str(fixture / "python-home"),
855 "PYTHONPATH": str(site),
856 "RA8_TOOL_VENV": str(fake_venv),
857 "USER": account.pw_name,
858 }
859
860
861def _run_boundary(argv: list[str], root: Path, environment: dict[str, str]) -> int:
862 """Run one offline public boundary with fixed process controls."""
863 result = subprocess.run( # noqa: S603 -- reviewed fixed test argv
864 argv,
865 cwd=root,
866 env=environment,
867 capture_output=True,
868 timeout=60,
869 check=False,
870 )
871 return result.returncode
872
873
874def _just_boundary_errors(
875 just: str, root: Path, fixture: Path, marker: Path, environment: dict[str, str]
876) -> list[str]:
877 """Exercise the infra and HIL Just public entrypoints offline."""
878 prefix = [str(Path(just).resolve()), "--justfile", str(root / "justfile")]
879 hostile_tool_env = dict(environment)
880 clean_tool_env = dict(environment)
881 del clean_tool_env["RA8_TOOL_VENV"]
882 hostile_rc = _run_boundary([*prefix, "infra::list"], root, hostile_tool_env)
883 errors = []
884 if marker.exists() or hostile_rc == 0:
885 errors.append("caller-selected tool environment did not fail closed")
886 marker.unlink(missing_ok=True)
887 infra_rc = _run_boundary([*prefix, "infra::_boundary_selftest"], root, clean_tool_env)
888 if marker.exists() or infra_rc != 0:
889 errors.append("hostile startup executed at the public infra entry")
890 marker.unlink(missing_ok=True)
891 hil_rc = _run_boundary(
892 [*prefix, "hil::preflash_check", str(fixture / "absent.elf")],
893 root,
894 clean_tool_env,
895 )
896 if marker.exists():
897 errors.append("hostile startup executed at the public HIL entry")
898 if hil_rc == 0:
899 errors.append("offline invalid-image HIL entry unexpectedly succeeded")
900 return errors
901
902
903def _installer_boundary_selftest(
904 root: Path, marker: Path, environment: dict[str, str]
905) -> list[str]:
906 """Exercise the three direct setup/provision boundaries offline."""
907 errors = []
908 for relative in (
909 "scripts/dev/setup_ansible.sh",
910 "scripts/dev/provision_dev_box_toolchain.sh",
911 "infra/bootstrap.sh",
912 ):
913 marker.unlink(missing_ok=True)
914 argv = ["/bin/bash", "-p", str(root / relative), "--selftest-boundary"]
915 if _run_boundary(argv, root, environment) != 0 or marker.exists():
916 errors.append(f"{relative}: hostile startup boundary failed")
917 return errors
918
919
920def public_boundary_selftest(root: Path) -> list[str]:
921 """Run real public entries with every supported startup injection hostile."""
922 just = shutil.which("just")
923 if just is None:
924 return ["public infra boundary selftest cannot find Just"]
925 with tempfile.TemporaryDirectory(prefix="ra8-infra-boundary-") as raw:
926 fixture = Path(raw)
927 marker = fixture / "executed"
928 environment = _hostile_environment(fixture, marker)
929 return _just_boundary_errors(
930 just, root, fixture, marker, environment
931 ) + _installer_boundary_selftest(root, marker, environment)
932
933
934def errors(inputs: dict[str, str]) -> list[str]:
935 """Return structural v9 findings for the supplied source bytes."""
936 return (
937 _lock_verifier_errors(inputs["bench_lock_verify"])
938 + _context_scope_errors(
939 inputs["dev_defaults"], inputs["dev_main"], inputs["devcontainer_image"]
940 )
941 + _image_lock_authority_errors(inputs)
942 + roles.pin_and_shell_authority_errors(
943 inputs["dev_main"], inputs["dockerfile"], inputs["root_justfile"]
944 )
945 + roles.dev_box_shell_boundary_errors(inputs["dev_main"], inputs["hil_just"])
946 + _installer_errors(inputs)
947 + _infra_boundary_errors(inputs["infra_just"], inputs["infra_sh"])
948 + _hil_shell_errors(inputs["hil_shells"])
949 )