"""Config validator for Codex Patcher — validates 6 config targets. Unlike Claude/Gemini patchers (regex-based), Codex validation is state-based: checks config.toml values and environment variables. """ import os try: import tomllib except ModuleNotFoundError: try: import tomli as tomllib except ModuleNotFoundError: import re as _re class _T: @staticmethod def load(f): raw = f.read() return _T._parse(raw.decode("utf-8") if isinstance(raw, bytes) else raw) @staticmethod def _parse(text): result, cur = {}, None for line in text.split("\n"): line = line.strip() if not line or line.startswith("#"): continue m = _re.match(r'^\[([^\]]+)\]$', line) if m: keys = [k.strip() for k in m.group(1).split(".")] cur = result for k in keys: cur = cur.setdefault(k, {}) continue m = _re.match(r'^([^=]+?)\s*=\s*(.+)$', line) if m: k, v = m.group(1).strip(), m.group(2).strip() if v.startswith('"') and v.endswith('"'): v = v[1:-1] elif v == "true": v = True elif v == "false": v = False elif _re.match(r'^-?\d+$', v): v = int(v) (cur if cur is not None else result)[k] = v return result tomllib = _T() from dataclasses import dataclass from typing import Callable, Optional @dataclass class ConfigTarget: name: str description: str check_key: str # "config_toml" | "env" | "auth" PATCH_TARGETS = [ ConfigTarget( name="api_endpoint", description="Custom proxy via model_providers", check_key="config_toml", ), ConfigTarget( name="authentication", description="API key auth configured", check_key="auth", ), ConfigTarget( name="analytics_disabled", description="Analytics/telemetry disabled", check_key="config_toml", ), ConfigTarget( name="approval_bypass", description="Approval policy set to never", check_key="config_toml", ), ConfigTarget( name="sandbox_bypass", description="Sandbox set to danger-full-access", check_key="config_toml", ), ConfigTarget( name="env_vars", description="System environment variables configured", check_key="env", ), ] def _read_toml(path): """Read TOML file, return dict or empty dict.""" if not os.path.isfile(path): return None with open(path, "rb") as f: return tomllib.load(f) def _check_api_endpoint(toml_data, config): """Check Target 1: model_providers.custom with correct base_url.""" if toml_data is None: return "RED", "config.toml not found" mp = toml_data.get("model_providers", {}) custom = mp.get("custom", {}) if isinstance(mp, dict) else {} base_url = config["base_url"].rstrip("/") if not base_url.endswith("/v1"): base_url += "/v1" if not custom: return "RED", "model_providers.custom section missing" if custom.get("base_url") != base_url: return "YELLOW", f"base_url mismatch: {custom.get('base_url')} != {base_url}" if toml_data.get("model_provider") != "custom": return "YELLOW", "model_provider != 'custom'" return "GREEN", f"base_url={base_url}" def _check_auth(codex_dir, config): """Check Target 2: API key authentication.""" # Check env var env_key = os.environ.get("OPENAI_API_KEY", "") if env_key == config["api_key"]: return "GREEN", "OPENAI_API_KEY set correctly" # Check /etc/environment etc_env = "/etc/environment" if os.path.isfile(etc_env): with open(etc_env) as f: content = f.read() if config["api_key"] in content: return "GREEN", "API key in /etc/environment" if env_key: return "YELLOW", "OPENAI_API_KEY set but different value" return "RED", "OPENAI_API_KEY not set" def _check_analytics(toml_data): """Check Target 3: analytics disabled.""" if toml_data is None: return "RED", "config.toml not found" analytics = toml_data.get("analytics", {}) if not isinstance(analytics, dict): return "RED", "[analytics] section missing" if analytics.get("enabled") is False: return "GREEN", "analytics.enabled = false" if "enabled" not in analytics: return "YELLOW", "[analytics] exists but 'enabled' key missing" return "YELLOW", f"analytics.enabled = {analytics.get('enabled')}" def _check_approval(toml_data, config): """Check Target 4: approval_policy.""" if toml_data is None: return "RED", "config.toml not found" target_policy = config.get("approval_policy", "never") current = toml_data.get("approval_policy") if current == target_policy: return "GREEN", f'approval_policy = "{target_policy}"' if current is not None: return "YELLOW", f'approval_policy = "{current}" (expected "{target_policy}")' return "RED", "approval_policy not set" def _check_sandbox(toml_data, config): """Check Target 5: sandbox_mode.""" if toml_data is None: return "RED", "config.toml not found" target_mode = config.get("sandbox_mode", "danger-full-access") current = toml_data.get("sandbox_mode") if current == target_mode: return "GREEN", f'sandbox_mode = "{target_mode}"' if current is not None: return "YELLOW", f'sandbox_mode = "{current}" (expected "{target_mode}")' return "RED", "sandbox_mode not set" def _check_env_vars(config): """Check Target 6: system environment variables.""" base_url = config["base_url"].rstrip("/") if not base_url.endswith("/v1"): base_url += "/v1" etc_env = "/etc/environment" if not os.path.isfile(etc_env): return "RED", "/etc/environment not found" with open(etc_env) as f: content = f.read() has_base = "OPENAI_BASE_URL" in content has_key = "OPENAI_API_KEY" in content if has_base and has_key: return "GREEN", "OPENAI_BASE_URL + OPENAI_API_KEY set" missing = [] if not has_base: missing.append("OPENAI_BASE_URL") if not has_key: missing.append("OPENAI_API_KEY") return "YELLOW" if (has_base or has_key) else "RED", f"Missing: {', '.join(missing)}" def validate_all(codex_dir, config): """Validate all 6 targets. Returns list of (target, status, message) tuples.""" config_path = os.path.join(codex_dir, "config.toml") toml_data = _read_toml(config_path) results = [] # Target 1: API endpoint status, msg = _check_api_endpoint(toml_data, config) results.append((PATCH_TARGETS[0], status, msg)) # Target 2: Auth status, msg = _check_auth(codex_dir, config) results.append((PATCH_TARGETS[1], status, msg)) # Target 3: Analytics status, msg = _check_analytics(toml_data) results.append((PATCH_TARGETS[2], status, msg)) # Target 4: Approval status, msg = _check_approval(toml_data, config) results.append((PATCH_TARGETS[3], status, msg)) # Target 5: Sandbox status, msg = _check_sandbox(toml_data, config) results.append((PATCH_TARGETS[4], status, msg)) # Target 6: Env vars status, msg = _check_env_vars(config) results.append((PATCH_TARGETS[5], status, msg)) return results # ANSI colors GREEN_C = "\033[92m" YELLOW_C = "\033[93m" RED_C = "\033[91m" BOLD_C = "\033[1m" RESET_C = "\033[0m" STATUS_COLORS = { "GREEN": GREEN_C, "YELLOW": YELLOW_C, "RED": RED_C, } def print_validation_report(results): """Print formatted validation report.""" print(f"\n {BOLD_C}Codex Patcher — Validation Report{RESET_C}") print(" " + "─" * 50) counts = {"GREEN": 0, "YELLOW": 0, "RED": 0} for target, status, msg in results: color = STATUS_COLORS.get(status, "") print(f" {color}[{status:6s}]{RESET_C} {target.name}: {target.description}") if status != "GREEN": print(f" → {msg}") counts[status] = counts.get(status, 0) + 1 print(" " + "─" * 50) total = len(results) print(f" {GREEN_C}{counts['GREEN']}{RESET_C}/{total} GREEN " f"{YELLOW_C}{counts['YELLOW']}{RESET_C} YELLOW " f"{RED_C}{counts['RED']}{RESET_C} RED") if counts["GREEN"] == total: print(f"\n {GREEN_C}All targets configured correctly!{RESET_C}") elif counts["RED"] > 0: print(f"\n {RED_C}Critical targets missing. Run: python3 codex_patcher.py --apply{RESET_C}") return counts