From 62386a20df4ebfb7a1b73f573d37c6c82eea7c5d Mon Sep 17 00:00:00 2001 From: Austin Tucker <220209011+austinkennethtucker@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:41:48 -0400 Subject: [PATCH] Track project Codex config safely --- .codex/config.toml | 4 ++++ .gitignore | 8 +++++++- scripts/check_repo_hygiene.py | 19 +++++++++++++++++- tests/test_repo_hygiene.py | 37 +++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 .codex/config.toml create mode 100644 tests/test_repo_hygiene.py diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..513292a --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,4 @@ +# Project-local Codex config for this repository. +# +# Keep credentials and runtime state out of this file. Codex/ChatGPT auth for +# the provider container belongs in data/codex-home, mounted at /root/.codex. diff --git a/.gitignore b/.gitignore index 776c4e0..be87a03 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,12 @@ temp/ auth.json codex-home/ -.codex/ sessions/ history.jsonl + +# Track reviewed project-level Codex config without exposing Codex home state. +.codex/* +!.codex/config.toml +!.codex/rules/ +.codex/rules/* +!.codex/rules/*.rules diff --git a/scripts/check_repo_hygiene.py b/scripts/check_repo_hygiene.py index deaeb2f..e1d26e8 100644 --- a/scripts/check_repo_hygiene.py +++ b/scripts/check_repo_hygiene.py @@ -5,12 +5,15 @@ from pathlib import Path +ALLOWED_CODEX_PATHS = { + ".codex/config.toml", +} + FORBIDDEN_STAGEABLE_PATHS = ( ".env", "data/", ".venv/", "venv/", - ".codex/", "codex-home/", ) @@ -28,6 +31,18 @@ } +def is_allowed_codex_path(path: str) -> bool: + if path in ALLOWED_CODEX_PATHS: + return True + + rules_prefix = ".codex/rules/" + if path.startswith(rules_prefix): + rule_name = path.removeprefix(rules_prefix) + return "/" not in rule_name and rule_name.endswith(".rules") + + return False + + def fail(message: str) -> None: print(f"FAIL: {message}", file=sys.stderr) raise SystemExit(1) @@ -53,6 +68,8 @@ def check_paths(paths: list[str]) -> None: fail(f"forbidden path is stageable: {path}") if any(normalized.startswith(prefix) for prefix in FORBIDDEN_STAGEABLE_PATHS if prefix.endswith("/")): fail(f"forbidden path is stageable: {path}") + if normalized == ".codex" or (normalized.startswith(".codex/") and not is_allowed_codex_path(normalized)): + fail(f"forbidden Codex state/config path is stageable: {path}") if Path(normalized).name in FORBIDDEN_FILENAMES: fail(f"forbidden credential/state filename is stageable: {path}") diff --git a/tests/test_repo_hygiene.py b/tests/test_repo_hygiene.py new file mode 100644 index 0000000..576fa23 --- /dev/null +++ b/tests/test_repo_hygiene.py @@ -0,0 +1,37 @@ +import pytest + +from scripts.check_repo_hygiene import check_paths, is_allowed_codex_path + + +def test_codex_project_config_is_stageable(): + assert is_allowed_codex_path(".codex/config.toml") + check_paths([".codex/config.toml"]) + + +def test_codex_rules_files_are_stageable(): + assert is_allowed_codex_path(".codex/rules/default.rules") + check_paths([".codex/rules/default.rules"]) + + +@pytest.mark.parametrize( + "path", + [ + ".codex/auth.json", + ".codex/history.jsonl", + ".codex/sessions/2026/session.jsonl", + ".codex/cache/state.json", + ".codex/state.sqlite", + ".codex/rules/nested/default.rules", + ".codex/local-environment.json", + ], +) +def test_codex_state_and_unknown_files_are_not_stageable(path): + assert not is_allowed_codex_path(path) + with pytest.raises(SystemExit): + check_paths([path]) + + +@pytest.mark.parametrize("path", [".env", "data/codex-home/auth.json", "codex-home/auth.json"]) +def test_existing_forbidden_paths_stay_forbidden(path): + with pytest.raises(SystemExit): + check_paths([path])