Codex enterprise governance: requirements.toml, access and audit
PLCodex enterprise governance rests on two controls. ChatGPT workspace settings decide who may use Codex at all, and an admin-owned requirements.toml constrains sign-in, approval policies, sandbox modes, web search, MCP servers, plugin marketplaces and command rules on every machine. Users cannot override those constraints, and codex doctor names the file that enforced each one.
This page is for the tech lead or platform engineer who owns Codex for more than one team, and the CTO who signs off on the rollout. Your security review asks for proof that no laptop runs Codex with full disk access, an unapproved MCP server or a personal account. A wiki page that says “we don’t allow that” is not proof. You need a file every machine obeys and a check that fails when it stops being obeyed.
Scope: this page covers Codex only. For one policy rendered into Claude Code, Codex, Cursor and GitHub Copilot, see enforcing one policy across every coding agent. Shared team defaults (.codex/config.toml, AGENTS.md, rules files, skills) live in Codex team workflows. Read Codex configuration first if config.toml layering is new to you.
What you’ll walk away with from Codex governance
Section titled “What you’ll walk away with from Codex governance”- A map of which Codex settings users can override and which they cannot.
- A
requirements.tomlbaseline that loads cleanly in codex-cli 0.157.1, with the traps in each key. - A six-step rollout order, from the policy table to fleet distribution.
- A CI canary that proves the policy is enforced, without a person inspecting a single laptop.
- An audit-evidence map: which question each log or report answers, and how far each one is verified.
- Three copy-paste prompts that draft the policy, build the canary, and report drift.
Which Codex settings can a developer override?
Section titled “Which Codex settings can a developer override?”Codex separates defaults from constraints. OpenAI’s own guidance, in the documentation skill bundled with Codex, reads: “Keep config.toml defaults, requirements.toml constraints, and managed or administrator policy separate.” The table shows where each layer lives and who wins.
| Layer | Where it lives | Owner | Can a developer override it? | Use it for |
|---|---|---|---|---|
| ChatGPT workspace settings | The workspace admin console | Workspace admins | No | Who may use Codex locally and in the cloud; cloud environments; connectors |
requirements.toml | /etc/codex/requirements.toml on Linux, macOS MDM managed preferences, or an enterprise-managed layer delivered from the workspace | Security or platform team | No. A disallowed value falls back to the first allowed value, with a startup warning | Floors: sign-in, approvals, sandbox, web search, MCP, marketplaces, command rules |
System config.toml | /etc/codex/config.toml | Platform team | Yes | Fleet defaults such as the [otel] exporter |
User and project config.toml | ~/.codex/config.toml, .codex/config.toml in a trusted repository | Developers and teams | Yes | Preferences and team conventions |
Two gaps shape the rest of this page. In codex-cli 0.157.1, requirements.toml has no model-name allowlist and no [otel] key (checked against config_requirements.rs at tag rust-v0.157.1 on 2026-09-26). So telemetry is a default a developer can switch off, and model choice is governed by the workspace or a gateway, as described in where the model runs. You can still force the route: model_provider in requirements.toml pins the provider (for example, your gateway) and overrides user and session config, and model_catalog_json pins the model catalog, checked in config_requirements.rs at rust-v0.157.1. [models.new_thread] in requirements.toml sets the default model for new threads; it does not restrict the picker.
Roll out Codex governance in six steps
Section titled “Roll out Codex governance in six steps”The order matters. Access control comes before the policy file, because a policy file cannot constrain someone who signs in with an API key outside your workspace. Verification comes before distribution, because a broken requirements.toml stops Codex on every machine that receives it.
-
Write the policy as a table before writing any TOML. One row per control: the risk it addresses, the risk class from governance and autonomy, the enforcement point, and the owner. Controls with no enforcement point are trust-based, and the table should say so. This table is what the CTO signs off, and it is the input to the first prompt below.
Control Risk class Enforced by Owner Only workspace accounts may sign in High allowed_login_methods,allowed_chatgpt_workspacesSecurity No full disk access on laptops High allowed_sandbox_modesPlatform No unattended runs without approvals Medium allowed_approval_policiesPlatform Only reviewed MCP servers High [mcp_servers.*.identity]Security Plugins only from the internal marketplace Medium [marketplaces]Platform No force-push or package publish by the agent High [rules] prefix_rulesPlatform Session telemetry reaches the collector Medium System config.toml(a default, not enforced)Platform -
Restrict who can use Codex in the workspace. In the ChatGPT workspace admin settings, enable Codex locally and in the cloud only for the groups in your pilot, and widen access group by group. OpenAI’s admin pages could not be read for this revision (2026-09-26), so this page does not name the toggles. Check the current labels in OpenAI’s enterprise admin guide.
-
Pin sign-in to your workspace. Set
allowed_login_methods = ["chatgpt"]andallowed_chatgpt_workspacesto your workspace ID. Without these two keys, a developer can sign in with a personal API key, and that session runs outside your workspace settings. -
Write
requirements.tomlfrom the table. Start from the baseline in the next section, keep it in a policy repository, and put a version and an owner in the first comment line. -
Prove the file in CI before it ships. Run the canary described in how to prove the policy is enforced on every change to the policy repository. Security approves the pull request; the canary decides whether it can merge.
-
Distribute the file and watch for drift. Push it with your device management tool: a file at
/etc/codex/requirements.tomlon Linux, managed preferences on macOS, or the enterprise-managed layer from the workspace where your plan provides it. When several sources set requirements, Codex composes them, and every warning or error names the source that set the value. A weekly drift report (the third prompt) catches machines where the file is missing or outdated.
A requirements.toml baseline that loads in Codex 0.157.1
Section titled “A requirements.toml baseline that loads in Codex 0.157.1”This file loads without errors in codex-cli 0.157.1. Replace CHATGPT_WORKSPACE_ID with your workspace ID and acme with your plugin marketplace.
# /etc/codex/requirements.toml (agent-policy v1.0, owner: platform-security)allowed_login_methods = ["chatgpt"]allowed_chatgpt_workspaces = ["CHATGPT_WORKSPACE_ID"]
# The first entry is the fallback when a user's config asks for anything else.allowed_approval_policies = ["on-request", "untrusted"]allowed_sandbox_modes = ["read-only", "workspace-write"]allowed_web_search_modes = ["cached"]
[mcp_servers.github.identity]url = "https://api.githubcopilot.com/mcp/"
[mcp_servers.linear.identity]url = "https://mcp.linear.app/mcp"
[marketplaces]restrict_to_allowed_sources = true
[marketplaces.allowed_sources.acme]source = "git"url = "https://github.com/acme/agent-plugins.git"
[rules]prefix_rules = [ { pattern = [{ token = "git" }, { token = "push" }, { token = "--force" }], decision = "forbidden", justification = "Force-push is for humans" }, { pattern = [{ token = "npm" }, { token = "publish" }], decision = "forbidden", justification = "Releases go through CI" }, { pattern = [{ token = "terraform" }, { token = "apply" }], decision = "prompt", justification = "Infrastructure changes need a named approver" },]What each key does in practice, checked against the 0.157.1 binary and source on 2026-09-26:
- List order is policy. When a user’s config asks for a disallowed value, Codex falls back to the first entry in the allowed list. With
["on-request", "untrusted"],approval_policy = "never"becomeson-request; reverse the list and it becomesuntrusted. allowed_sandbox_modesmust includeread-only, or Codex rejects the requirement. The constraint also applies to permission profiles, not only to the legacy--sandboxflag. A request fordanger-full-accessfalls back toread-only.- Web search
disabledis always allowed.allowed_web_search_modes = ["cached"]permitscachedanddisabledand blockslive. - MCP servers match on name and identity. A server that is not listed shows
disabled: requirementsincodex mcp list. The approved name matters as much as the URL, so publish the exact names developers must use. - Rules accept only
promptorforbidden. Codex merges requirement rules with user and project rules and keeps the most restrictive result, sodecision = "allow"is refused. - A refused file stops Codex. One
allowrule, or any other invalid entry, turns intoError loading config.toml: invalid rules in requirements (set by /etc/codex/requirements.toml), and Codex does not start. This is why step 5 comes before step 6.
Two stricter keys belong only in the policy you ship to machines (or device groups) that work on regulated data. allow_managed_hooks_only = true ignores user, project and session hooks, which also switches off the hooks your teams use as quality gates. enforce_residency = "us" pins data residency, and us is the only value 0.157.1 accepts. Model defaults stay out of this file unless you have a reason: Codex ships with GPT-6.1 Sol as its default (since CLI 0.159.1), and the models hub is where model choices and prices are compared.
How do you prove the policy is enforced without inspecting laptops?
Section titled “How do you prove the policy is enforced without inspecting laptops?”A policy is enforced when a hostile configuration fails to get past it. Build that configuration once, run it against every change to the policy, and let CI give the verdict. codex doctor needs no credentials for these checks: it reads the config layers, applies the requirements and prints every startup warning with its source.
A developer config that tries every forbidden setting:
approval_policy = "never"sandbox_mode = "danger-full-access"web_search = "live"
[mcp_servers.unapproved-server]command = "npx"args = ["-y", "some-mcp"]The check, which passes only when the approval, sandbox, web-search and MCP constraints hold, and fails if the file does not load:
#!/usr/bin/env bash# policy/codex/check.sh — runs in CI; needs no Codex credentialsset -euo pipefailhere="$(cd "$(dirname "$0")" && pwd)"export CODEX_HOME="$(mktemp -d)"cp "$here/tests/hostile-config.toml" "$CODEX_HOME/config.toml"
report="$(codex doctor --no-color 2>&1 || true)"if grep -q "config could not be loaded" <<<"$report"; then echo "FAIL: requirements.toml does not load"; exit 1fifor key in approval_policy sandbox_mode web_search_mode; do grep -q "invalid value for \`$key\`" <<<"$report" \ || { echo "FAIL: $key is not constrained"; exit 1; }donecodex mcp list 2>/dev/null | grep -Eq '^unapproved-server .*disabled: requirements' \ || { echo "FAIL: unapproved MCP server is not disabled"; exit 1; }echo "PASS: policy enforced"The login, marketplace and rules keys are proven to load but are not exercised by this canary. List them in GAPS.md, or test them by hand at pilot: sign in with a personal account, add a plugin from an unlisted marketplace, and run a forbidden command.
Run it on every pull request to the policy repository. The job needs no secrets, checks out with read-only permissions, and pins the Codex version so a CLI upgrade is its own reviewed change:
name: codex-policyon: pull_request: paths: ["policy/codex/**"]permissions: contents: readjobs: canary: runs-on: ubuntu-latest steps: - uses: actions/checkout@<full-commit-sha> # v7 with: persist-credentials: false - run: npm install -g @openai/codex@0.157.1 - run: sudo install -D -m 0644 policy/codex/requirements.toml /etc/codex/requirements.toml - run: bash policy/codex/check.shIn the policy repository, pin every action by its full commit SHA, not by a tag, because a tag such as v7 can be moved to different code. <full-commit-sha> above is a placeholder: replace it with the 40-character SHA of the actions/checkout release you reviewed, and keep the tag as a comment (# v7) so a reader can see which release it is.
The same script fails when the file is missing, so it doubles as a smoke test on a freshly imaged laptop. On a developer machine, /debug-config in the Codex TUI shows the config layer stack and the requirements in force, and codex doctor --json produces a redacted report whose config.load check lists the startup warnings. Your device management tool can collect that report, plus a sha256 of /etc/codex/requirements.toml, for the drift check.
Sign-off follows the table in step 1: security approves the policy pull request, the canary decides the merge, platform owns distribution, and the tech lead reviews the weekly drift report. Nobody reads a laptop’s config by hand.
The canary only decides the merge if a pull request cannot rewrite it. On the pull_request trigger GitHub runs the workflow, check.sh and the hostile config from the pull request itself, so a contributor can weaken all three in the same change. Put /.github/, policy/codex/check.sh and policy/codex/tests/ under CODEOWNERS owned by security, and require that review through branch protection or a ruleset; otherwise the check is advisory.
Where the audit evidence comes from
Section titled “Where the audit evidence comes from”Auditors ask four questions. Each has a different source, and the sources are not equally verified.
| Question | Source | Verification status (2026-09-26) |
|---|---|---|
| What policy was in force on this machine? | The versioned requirements.toml in git, plus codex doctor --json from the machine | Verified in codex-cli 0.157.1 |
| What did the sessions do? | OpenTelemetry logs, traces and metrics from the [otel] table, sent to your collector. log_user_prompt defaults to false | Verified in the Codex source; setup in agent telemetry |
| Who used Codex, where, and how much? | The Codex analytics dashboard and Analytics API for Enterprise workspaces | Secondary: help.openai.com, seen in search extracts on 2026-09-26; endpoint paths not verified |
| What were the prompts and responses, for eDiscovery or DLP? | The Compliance API | Secondary: help.openai.com, seen in search extracts on 2026-09-26; fields and endpoints not verified |
Treat the secondary rows as leads to confirm with your OpenAI account team before an auditor relies on them. The OpenTelemetry route is the one you control end to end, with the gap already noted: it is a default, not a constraint, so compare collector coverage against workspace seat counts every month. For the organization-wide audit pipeline and retention, see observing the agents. For per-tool data retention and zero data retention terms, see data privacy and enterprise policies.
Copy-paste prompts for Codex governance
Section titled “Copy-paste prompts for Codex governance”Run these in Codex, in the policy repository.
When Codex governance breaks, and how to recover
Section titled “When Codex governance breaks, and how to recover”- Codex stops starting on every machine after a policy push. The file has an invalid entry, most often
decision = "allow"in[rules]or an unknown key inside a strict table such as[marketplaces]. Roll back through your device management tool, then add the canary so the next broken file fails in CI instead. - A constraint silently does nothing. A misspelled top-level key, such as
allowed_sandbox_modewithout the finals, is ignored in 0.157.1: the file loads and the constraint is simply absent. Only a test that tries the forbidden value catches this, which is what the canary does for the keys it exercises. - A developer reports “I set
neverand it still asks for approval.” The policy is working. The startup warning shows the value Codex fell back to and the file that forced it. Publish the policy table where developers can read it, so the fallback is not a surprise. - An approved MCP server shows
disabled: requirements. The developer registered it under a different name from the one inrequirements.toml. Publish the exact names, or ship the server entries in the systemconfig.tomlso the names match by default. - Web search or full access still works on some laptops. Those machines do not have the current file. Check the drift report for a missing or mismatched policy hash, and fix device management coverage before adding more rules.
- Telemetry goes quiet for one user.
[otel]is a default, and a user config can override it; 0.157.1 cannot enforce it. Compare collector coverage with workspace seats monthly, and treat a gap as a conversation, not a breach. - Your regulated-data policy broke the team’s hooks.
allow_managed_hooks_only = trueignores every project hook, including test and lint gates. Move the gates you need into managed hooks, or ship the stricter file only to the device group that handles regulated repositories. - A cloud task did something the local policy forbids. The local file does not reach Codex cloud. Tighten workspace access and the environment’s settings, as described in Codex cloud environments.
Where to go next with Codex governance
Section titled “Where to go next with Codex governance”- Enforcing one policy across every coding agent: the same controls rendered for Claude Code, Cursor and GitHub Copilot, with a parity test.
- Codex team workflows: the shared defaults, rules files and skills that sit under this policy.
- MCP registries and gateways: how to decide which MCP servers go on the allowlist.
- Codex cost management: budgets and usage limits once access is governed.
- Agent telemetry: the
[otel]setup behind the audit-evidence table. - Governance and autonomy: the risk classes that fill the policy table.