Add shared development execpolicy rules

This commit is contained in:
Adam Perry
2026-06-22 22:50:37 +00:00
parent 9f06cf1a09
commit 96d6717fc5
6 changed files with 317 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
# Shared rules only allow commands that are generally safe for every project
# developer. Prompt and denial policies belong in user config so the repository
# does not impose those flows globally.
prefix_rule(
pattern = [
"just",
[
"fmt",
"fmt-check",
"test",
"clippy",
"fix",
"bench",
"bench-smoke",
"bazel-test",
"bazel-clippy",
"bazel-lock-check",
"bazel-lock-update",
"bazel-argument-comment-lint",
"argument-comment-lint",
"build-for-release",
],
],
decision = "allow",
justification = """
Needed: host caches or nested sandbox capabilities.
Safe: fixed non-publishing recipes only operate on the trusted checkout.
""",
)
prefix_rule(
pattern = ["cargo", ["build", "check", "clippy", "test"]],
decision = "allow",
justification = """
Needed: Cargo uses host registry, Git, and cache state.
Safe: these commands only build, check, lint, or test the trusted workspace.
""",
)
prefix_rule(
pattern = ["cargo", "nextest", "run"],
decision = "allow",
justification = """
Needed: Nextest needs host runtime and nested sandboxes.
Safe: it only runs tests built from the trusted workspace.
""",
)
prefix_rule(
pattern = ["bazel", ["build", "test", "query", "aquery", "cquery"]],
decision = "allow",
justification = """
Needed: Bazel uses its host output base and caches.
Safe: these commands only build, test, or inspect trusted checkout targets.
""",
)
prefix_rule(
pattern = ["pnpm", ["format", "test"]],
decision = "allow",
justification = """
Needed: pnpm uses its host store and metadata cache.
Safe: checked-in scripts exclude install, exec, and publish operations.
""",
)
prefix_rule(
pattern = ["pnpm", "run", ["format", "test"]],
decision = "allow",
justification = """
Needed: pnpm uses its host store and metadata cache.
Safe: checked-in scripts exclude install, exec, and publish operations.
""",
)
@@ -0,0 +1,15 @@
# Pin Codex development tool paths
Pinning prevents trusted rules from matching an unexpected executable with the same name.
From the development shell's repository root:
```sh
mkdir -p ~/.codex/rules
python3 scripts/setup_dev_execpolicy.py \
> ~/.codex/rules/codex_dev_tool_paths.rules
```
This pins binaries for the repository's
[development rules](../../.codex/rules/development.rules). Rules load when a
Codex session starts.
+1
View File
@@ -72,6 +72,7 @@ def buildifier_formatter_group(*, check: bool) -> FormatterGroup:
or name.endswith((".BUILD.bazel", ".MODULE.bazel", ".bzl", ".sky"))
or ".bzl." in name
or ".sky." in name
or (path.parent == Path(".codex/rules") and path.suffix == ".rules")
):
buildifier_files.append(path.as_posix())
buildifier_files.sort()
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Print host executable pins for Codex development commands."""
from collections.abc import Callable
from collections.abc import Mapping
from collections.abc import Sequence
import json
import os
from pathlib import Path
import shutil
import sys
# Keep this explicit: justfile recipes can delegate to scripts that invoke other tools.
HOST_EXECUTABLES = ("cargo", "just", "bazel", "pnpm")
GENERATED_HEADER = "# Generated by scripts/setup_dev_execpolicy.py. Do not edit."
WINDOWS_EXECUTABLE_SUFFIXES = (".exe", ".cmd", ".bat", ".com")
def executable_lookup_name(path: Path, *, windows: bool = os.name == "nt") -> str:
name = path.name
if not windows:
return name
name = name.lower()
for suffix in WINDOWS_EXECUTABLE_SUFFIXES:
if name.endswith(suffix):
return name[: -len(suffix)]
return name
def absolute_without_resolving(path: str) -> Path:
return Path(os.path.abspath(os.path.expanduser(path)))
def discover_host_executables(
names: Sequence[str] = HOST_EXECUTABLES,
*,
which: Callable[[str], str | None] = shutil.which,
) -> dict[str, Path]:
executables: dict[str, Path] = {}
missing: list[str] = []
for name in names:
raw_path = which(name)
if raw_path is None:
missing.append(name)
continue
path = absolute_without_resolving(raw_path)
if executable_lookup_name(path) != executable_lookup_name(Path(name)):
raise RuntimeError(
f"resolved path for {name!r} must retain that executable name: {path}"
)
executables[name] = path
if missing:
raise RuntimeError(
"missing required host executables on PATH: " + ", ".join(missing)
)
return executables
def render_host_executable_rules(executables: Mapping[str, Path]) -> str:
blocks = []
for name, path in executables.items():
blocks.append(
"\n".join(
[
"host_executable(",
f" name = {json.dumps(name)},",
f" paths = [{json.dumps(str(path))}],",
")",
]
)
)
return f"{GENERATED_HEADER}\n\n" + "\n\n".join(blocks) + "\n"
def main() -> int:
try:
executables = discover_host_executables()
except RuntimeError as error:
print(f"error: {error}", file=sys.stderr)
return 1
sys.stdout.write(render_host_executable_rules(executables))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import importlib.util
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
FORMAT_SCRIPT_PATH = Path(__file__).with_name("format.py")
FORMAT_SCRIPT_SPEC = importlib.util.spec_from_file_location(
"codex_format_script",
FORMAT_SCRIPT_PATH,
)
if FORMAT_SCRIPT_SPEC is None or FORMAT_SCRIPT_SPEC.loader is None:
raise RuntimeError(f"could not load {FORMAT_SCRIPT_PATH}")
format_script = importlib.util.module_from_spec(FORMAT_SCRIPT_SPEC)
sys.modules[FORMAT_SCRIPT_SPEC.name] = format_script
FORMAT_SCRIPT_SPEC.loader.exec_module(format_script)
class BuildifierFormatterGroupTest(unittest.TestCase):
def test_includes_top_level_codex_rule_files(self) -> None:
repository_files = b"\0".join(
[
b".codex/rules/z.rules",
b".codex/rules/a.rules",
b".codex/rules/nested/ignored.rules",
b"elsewhere.rules",
]
)
with patch.object(
format_script.subprocess,
"check_output",
return_value=repository_files,
):
group = format_script.buildifier_formatter_group(check=True)
self.assertEqual(
group.commands[0].args[4:],
(".codex/rules/a.rules", ".codex/rules/z.rules"),
)
if __name__ == "__main__":
unittest.main()
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
from contextlib import redirect_stdout
import io
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parent))
import setup_dev_execpolicy
class SetupDevExecpolicyTest(unittest.TestCase):
def test_discovers_absolute_paths_without_resolving_them(self) -> None:
paths = {
"cargo": "/tools/bin/cargo",
"just": "/tools/bin/just",
}
self.assertEqual(
setup_dev_execpolicy.discover_host_executables(
tuple(paths),
which=paths.get,
),
{
"cargo": Path("/tools/bin/cargo"),
"just": Path("/tools/bin/just"),
},
)
def test_reports_all_missing_executables(self) -> None:
with self.assertRaisesRegex(
RuntimeError,
"missing required host executables on PATH: just, pnpm",
):
setup_dev_execpolicy.discover_host_executables(
("cargo", "just", "pnpm"),
which=lambda name: "/tools/bin/cargo" if name == "cargo" else None,
)
def test_renders_host_executable_rules(self) -> None:
self.assertEqual(
setup_dev_execpolicy.render_host_executable_rules(
{
"cargo": Path("/tools/bin/cargo"),
"bazel": Path("/tools/bin/bazel"),
}
),
"""# Generated by scripts/setup_dev_execpolicy.py. Do not edit.
host_executable(
name = "cargo",
paths = ["/tools/bin/cargo"],
)
host_executable(
name = "bazel",
paths = ["/tools/bin/bazel"],
)
""",
)
def test_main_prints_rules(self) -> None:
executables = {"cargo": Path("/tools/bin/cargo")}
stdout = io.StringIO()
with (
patch.object(
setup_dev_execpolicy,
"discover_host_executables",
return_value=executables,
),
redirect_stdout(stdout),
):
result = setup_dev_execpolicy.main()
self.assertEqual(result, 0)
self.assertEqual(
stdout.getvalue(),
setup_dev_execpolicy.render_host_executable_rules(executables),
)
if __name__ == "__main__":
unittest.main()