Test a Codex Least-Privilege Profile for Source Edits

Conceptual cutaway of a source folder, sealed amber secret envelopes, a green route to one endpoint and red protective barriers.

This tutorial shows how to prepare a disposable local repository for testing ordinary source edits, denial of reads from dummy .env files and sandboxed command access to one genuinely required public destination. Permission profiles are Beta: OpenAI’s permissions documentation says they are under active development and may change. Treat every later result as conditional on the recorded operating system, Codex version, active profile, loaded configuration layers and network-proxy state. Revalidate the policy after a client upgrade or any change to those conditions.

Conceptual cutaway of a source folder, sealed amber secret envelopes, a green route to one endpoint and red protective barriers.
Original conceptual illustration of a local policy separating ordinary source work from secret and destination boundaries.

Evidence checkpoints

Documented point: Because permission profiles are Beta and may change, a local least-privilege profile should be revalidated after a Codex client upgrade rather than presented as a stable contract. [OpenAI documentation: Permissions.md]

Documented point: For sandboxed local commands, a permission profile is a named policy combining filesystem rules with network destination rules, so the tutorial must test both kinds of boundary separately. [OpenAI documentation: Permissions.md]

Documented point: If sandbox_mode appears in any loaded configuration, Codex uses the older sandbox settings instead of default_permissions, so configuration layers and flags must be audited before profile results are interpreted. [OpenAI documentation: Permissions.md]

Documented point: A documented source-editing pattern extends :workspace, keeps matching .env files denied and allows requests to a named host, but the profile name remains a local user choice. [OpenAI documentation: Permissions.md]

Documented point: Within an active profile, a narrower deny rule remains effective even where a broader path is readable or writable, but that rule does not protect copied data, history or traffic outside the local command sandbox. [OpenAI documentation: Permissions.md]

Documented point: On Linux, Windows Subsystem for Linux (WSL)A Windows feature that lets you run a Linux environment on a Windows machine without a separate virtual machine or dual booting. Open glossary entry and native Windows, an unbounded recursive deny-read pattern such as **/*.env may require bounded pre-expansion, so the fixture should include root and nested files and use an appropriate scan depth where needed. Deeper scans can add startup work. [OpenAI documentation: Permissions.md]

Documented point: Setting network.enabled to true permits command network access but does not itself restrict domains; the named-domain test is meaningful only when the network proxy is active or applicable managed networking is enforcing it. [OpenAI documentation: Permissions.md]

Documented point: A sandbox test should use the matching Codex command-line sandbox helper for the relevant operating system and retain the command exit or denial evidence; a wildcard covering subdomains must not be assumed to permit the apex domain. [OpenAI documentation: Agent approvals and security]

Define the boundary before you write a policy

A permission profile is not a general security boundary around every Codex capability. OpenAI documents it as a named policy combining filesystem rules—which determine what sandboxed local commands may read or write—with network-destination rules—which determine what destinations those commands may reach. The useful distinction is therefore between local command permissions and the wider set of places where information might already exist or travel.

Start with a deliberately narrow task: permit a source edit under one disposable repository, deny access to root and nested dummy environment files, and permit command traffic to exactly one named public host. Do not begin by copying a broad policy and attempting to remove privileges afterwards. First list the resources the edit actually needs, then represent everything else as either prohibited or outside this test’s coverage.

Use a synthetic teaching fixture named harbour-map. It is an example repository layout, not a report of an executed test or a guaranteed product result:

harbour-map/
├── .git/
├── .env
├── nested/
│   └── .env
└── src/
    └── probe.txt

Place visibly fake text in each file. For example, src/probe.txt could contain harbour=west; the root file could contain DUMMY_ROOT_VALUE=not-a-secret; and the nested file could contain DUMMY_NESTED_VALUE=not-a-secret. Never insert a real token, credential, customer record, internal hostname or private endpoint. Version control supplies a reviewable before-and-after state, but it does not make sensitive fixture data safe.

Make the repository disposable by creating it in a temporary working area, initialising a new version-control history and committing only the synthetic baseline. Inspect the staged content before the commit. If any value came from a shell environment, password manager, production configuration or copied log, delete the repository and start again rather than trying to sanitise it in place. If a reviewer cannot tell from the value itself that it is dummy data, it does not belong in this fixture.

This design separates the positive and negative filesystem cases. A permitted operation targets src/probe.txt; prohibited operations target both .env and nested/.env. Testing only the root file would leave nested matching unexamined, while testing only a nested file would not establish behaviour at the repository root. The two fixtures are intentionally redundant at the data level but distinct at the path-matching level.

Choose one exact public host only after identifying a command that genuinely requires it. For a planning example, suppose a harmless documentation check needs api.openai.com; that host, drawn from OpenAI’s documented example, illustrates how to record a requirement. It is not a general recommendation; actual reachability must be checked in the test environment. Choose a different, clearly unapproved public host for the negative case. Avoid a global wildcard. The exact-host rule and the separate wildcard test are set out under “Test exact hosts, not convenient wildcards”.

The trade-off is precision versus maintenance. An exact host minimises authority and produces a clear test expectation, but a legitimate service change may require an explicit policy update. A broad wildcard reduces updates but silently authorises more destinations. For a least-privilege source-edit test, prefer the exact host and treat any service migration as a reviewed change.

Separate what the profile governs from what it cannot contain

The profile’s filesystem rules apply to paths reached by commands running under the local sandbox. Within an active profile, OpenAI documents that a narrower deny remains effective even where a broader path is readable or writable. That makes an ordinary workspace-edit baseline compatible with a more specific denial for matching .env paths. It does not, however, erase information that has already escaped those paths.

Model the protected asset as the file access operation, not as an abstract secret. A path denial cannot recover a value copied into src/probe.txt, a committed revision, a generated artefact, command output or telemetry. It also cannot retroactively remove data from Git history. Before testing, search the disposable repository’s current tree and history for the dummy markers. This is an example hygiene check rather than a product guarantee: its purpose is to ensure that a later denied read is not undermined by another copy within the fixture.

Apply the same boundary discipline to network behaviour. The network proxy filters traffic from local commands running inside the sandbox. According to OpenAI’s permissions documentation, its profile allowlist does not govern web search, apps and connectors, Model Context Protocol (MCP)A protocol for connecting artificial intelligence applications with tools and data sources through defined interfaces. Open glossary entry servers, Browser and Computer Use, or Codex service traffic. Those are separate surfaces and require separate assessment. Do not use success or failure in any of them as evidence about command-sandbox enforcement.

A practical scoping procedure is to write two columns in the test record. Under “in scope”, list sandboxed command reads, writes and command-originated requests. Under “out of scope”, list copied values, repository history, generated outputs, telemetry and every non-command surface relevant to your environment. Add an owner or follow-up control for each consequential out-of-scope item. For example, repository history may require a separate history review, while organisational egress may require independently managed network controls.

The decision rule is that profile evidence supports only the operation and surface actually observed. A denied read of nested/.env is evidence about that attempted path under the recorded conditions; it is not proof that no copy exists elsewhere. A denied connection from a sandboxed command, when proxy enforcement is active, is evidence about that command request; it is not proof of global egress control. Require a human reviewer to reject conclusions that expand beyond those boundaries.

Resolve profile selection before interpreting evidence

Permission profiles and legacy sandbox settings are alternative mechanisms, not additive layers. OpenAI states that permission profiles do not compose with older sandbox settings. If sandbox_mode appears in any loaded configuration file, Codex uses those older settings instead of default_permissions. Consequently, a carefully written profile can exist on disk without governing the observed command.

Before constructing tests, inventory every configuration source relevant to the invocation. Record the user-level configuration, which OpenAI documents at ~/.codex/config.toml; any trusted project-scoped configuration; the selected profile file; environment-dependent launch wrappers; and command-line flags. Project-scoped configuration is loaded only when the project is trusted, so trust state belongs in the evidence record rather than being assumed.

Search the loaded files and invocation for the literal legacy key sandbox_mode. Do not merely inspect the profile you intend to use. A higher-precedence layer, project configuration or wrapper flag can alter the effective mechanism. Also record how the profile is selected and verify that its local name matches the intended test profile. Profile names are user choices; a reassuring name such as least-privilege says nothing about the rules actually loaded.

If a legacy setting is present, halt before interpreting results as profile evidence. Do not remove an organisation-managed setting simply to make a test pass; resolve the controlling mechanism with its owner, then repeat the full configuration-layer review below.

The trade-off is convenience against attribution. Layered configuration is useful for shared defaults and project-specific behaviour, but each layer creates another route by which the effective policy can differ from the reviewed file. For this fixture, favour an invocation whose active layers can be enumerated. If they cannot be enumerated, do not claim that a result validates the intended profile.

Turn the source-edit requirement into observable cases

Do not define “works” as a generally successful Codex session. Translate the requirement into separate observations with one expected operation each. Filesystem and network checks must remain separate because they rely on different controls and can fail independently.

For the allowed filesystem case, define a small, deterministic edit to src/probe.txt. For example, the requested change could replace harbour=west with harbour=east. The evidence should include the exact command or instruction, process exit status, sandbox output and resulting version-control diff. A zero exit alone is insufficient: a command may succeed without changing the intended file, or may change additional files.

For denied filesystem cases, define separate attempts to read .env and nested/.env. Save the exit status and denial text for each path. Do not combine them in one command, because the first denial could prevent the second attempt and obscure which match was exercised. The desired observation is not the dummy value; it is evidence that each access attempt was refused under the active conditions.

Glob behaviour needs operating-system-specific planning. OpenAI documents that on Linux, WSL and native Windows, an unbounded recursive deny-read pattern may require bounded pre-expansion before the sandbox starts. A suitable scan-depth setting or explicit bounded depths may therefore be needed. Deeper scanning adds startup work, and no single result should be presented as universal matching across supported operating systems.

Plan to test both fixture locations on the actual platform. On Linux, WSL or native Windows, record the configured scan depth and ensure it reaches at least the nested fixture depth. On macOS, still test both paths rather than inferring nested behaviour from documentation. If the root file is denied but the nested file is readable, stop: the policy has not met the fixture requirement. If both are denied, describe only that observed depth and platform; do not infer protection for arbitrary directory depth.

OpenAI’s security documentation provides operating-system-specific Codex command-line interface (CLI)A text-based interface for running commands and tools. Open glossary entry sandbox helpers for macOS, Linux and Windows. Later execution should use the helper matching the recorded operating system and explicitly select the intended permission profile. At this planning stage, reserve evidence files for the invocation, standard output, standard error and exit status. Keep those records free of secrets; denial output can itself disclose paths or command arguments.

The filesystem decision rule is conjunctive: trust the candidate policy only if the intended source edit is confined to the expected diff and both dummy environment-file reads are denied. An allowed edit does not compensate for a readable .env, and a denied secret read does not compensate for an inability to perform the required source change. A human must inspect the complete diff and retained command evidence before accepting the result.

Plan network observations without confusing access and enforcement

Network enablement and domain filtering are distinct states. OpenAI documents that setting network.enabled to true permits command network access but does not start the network proxy. Without an active proxy, profile domain rules do not restrict direct network access. Therefore, an unapproved host that remains reachable while the proxy is off is not evidence that a configured allowlist was enforced incorrectly; enforcement was absent.

Record three facts before any network conclusion: whether command networking is enabled, whether the network proxy is active or applicable managed networking is enforcing the rule, and whether the request originated from a local command inside the sandbox. Also record routing or escalation changes that could move traffic outside that path. The later positive observation should target the one named public host; the negative observation should target an unapproved host in a separate command.

Keep network outputs independent from filesystem outputs. A source edit may succeed while the proxy is inactive, and a host request may be denied while filesystem rules are misconfigured. For example, evidence that api.openai.com was reachable would address only the named command destination under the recorded proxy state. Evidence that another host was denied would be meaningful only if active enforcement and sandboxed command origin were established first.

The decision rule is strict: never report an unapproved destination as blocked by the profile unless the command ran inside the sandbox and the proxy or applicable managed networking actively enforced domain rules. Conversely, do not interpret network.enabled as an allowlist.

Create an evidence plan that another reviewer can challenge

Before execution, create a dated test record using the evidence header template under “Prove the allowed edit and every intended refusal”, adding the repository commit identifier, trust state and glob scan depth where relevant. The official-source basis in this tutorial is the OpenAI documentation reviewed as of 6 October 2026, but your local evidence must use the versions and configuration actually present when you run the fixture.

For each case, reserve fields for the requested operation, expected boundary, exact invocation, exit status, denial or error output, resulting diff and reviewer conclusion. Label sample expectations as examples. Do not pre-fill “pass”: doing so encourages confirmation bias and can conceal a command that never exercised the intended path.

Retain only evidence necessary to reproduce the conclusion. Save sanitised command output and configuration excerpts, but exclude credentials, tokens, customer data and private destinations. If output unexpectedly contains sensitive material, stop collection, restrict access according to your organisation’s procedures and rebuild the fixture with dummy values. Never paste such material into a prompt for diagnosis.

Finally, define invalidation conditions before testing. At minimum, invalidate and repeat the evidence after a Codex upgrade, operating-system change, profile edit, configuration-layer change, project-trust change, proxy-state change or routing change. Also repeat after changing fixture depth or the required host. Because profiles are Beta, a previously reviewed result is not a permanent contract.

The acceptance rule is human, not automatic: a reviewer must verify the recorded environment, confirm that no sandbox_mode superseded the profile, inspect the source diff, examine both path denials, and check the provenance and enforcement state of network observations. Until that review is complete, describe the profile as a candidate policy for this disposable fixture—not as trusted protection for real repositories or secrets.

Build a source-editing profile without widening the boundary

This fixture separates two questions. The ordinary source file represents work that the workspace policy may permit, whereas the two .env files test a narrower denial at different directory levels. Do not trust the policy unless a human reviewer can distinguish evidence for an ordinary source edit from evidence for each denied fixture. A successful source operation says nothing by itself about either secret-file path.

Conceptual test bench where a green edit reaches an ordinary source file while red barriers guard a secret envelope and several network gates.
Original conceptual illustration of testing each allowed and denied action as separate evidence.

Start narrow, then state each exception

OpenAI documents a source-editing pattern that extends :workspace, keeps matching .env files denied and names the host required by a command. Adapt that structure instead of creating a broad access model from scratch. The profile name is a local label, not a product entitlement, capability grant or indication that the account has a special service tier.

The following is a reader-adapted template. Replace harbour-edit with a locally meaningful name and replace the example public host only if the test command genuinely needs a different single public host. Do not substitute a global wildcard, a private endpoint or a collection of speculative destinations.

[permissions.harbour-edit]
extends = ":workspace"

[permissions.harbour-edit.filesystem]
"**/*.env" = "deny"

[permissions.harbour-edit.network]
enabled = true
allowed_domains = ["api.openai.com"]

The useful distinction is between an inherited baseline and explicit exceptions. extends = ":workspace" supplies the documented workspace-oriented starting point; the filesystem entry narrows that baseline for matching files; and the network entry expresses command access plus one named destination. Do not interpret the template as proof that the selected profile has loaded, that the glob covers every depth on the current operating system, or that domain filtering is active. Those are separate verification tasks.

Keep the exception list tied to a stated operation. For example, if a local source-checking command must contact api.openai.com, record that specific dependency beside the test plan. If no sandboxed command requires a network request, omit network access rather than enabling it in anticipation of future use. If several hosts appear necessary, investigate redirects, package tooling and subprocesses before adding them. The trade-off is operational convenience versus an expanding destination set whose necessity becomes harder to review.

The profile combines filesystem rules and network destination rules for local commands running in the sandbox, but the two rule types do not prove one another. A denied file read cannot demonstrate domain enforcement, and a destination decision cannot demonstrate filesystem denial. Maintain separate evidence entries for the source file, root fixture, nested fixture, exact host and unapproved host.

Apply deny precedence to the test fixture

Apply the narrower-deny rule explained under “Separate what the profile governs from what it cannot contain” to the dummy paths. The test must record source editing and each .env read separately; reading the intended policy is not a substitute for observing each outcome.

Use that precedence as a review procedure. First, identify the ordinary source path that should be editable. Second, identify every dummy path expected to match the denial. Third, inspect the effective profile rather than merely reading the intended file. Finally, retain distinct evidence for the attempted source change and for attempted access to each fixture. The expected policy interpretation is “source path permitted, matching secret-file paths denied”, but only the later observations can establish what happened under the recorded conditions.

Do not weaken the deny merely because a tool expects environment configuration. Prefer a dummy, non-sensitive test input outside the denied naming pattern when the test’s purpose does not require reading .env. If the actual development workflow cannot function without that file, stop and redesign the workflow or obtain a specific human decision; silently broadening workspace access defeats the profile’s purpose.

The rule also has a strict scope limit. It does not recover a value previously copied into another source file, remove it from Git history, control generated artefacts or govern traffic outside the local command sandbox. For example, denying nested/.env does not make src/copied-config.txt safe if someone has duplicated the dummy value there. Treat path denial as control over matching paths, not as data classification or retrospective secret removal.

Bound the glob test to the operating system

The pattern **/*.env needs an operating-system-qualified test. OpenAI notes that on Linux, WSL and native Windows, an unbounded ** deny-read pattern may require bounded pre-expansion before the sandbox starts. Do not infer universal recursive matching from the pattern’s appearance, and do not claim an arbitrary directory depth works without testing it.

Use both fixtures deliberately. The repository-root .env checks whether the chosen expression covers the root case under the tested implementation. The nested nested/.env checks the depth represented by this repository. If the root does not match the same expression on the tested system, add an explicit root rule rather than pretending the nested pattern covers it. If the nested fixture falls beyond the configured scan, choose and document a suitable glob_scan_max_depth or explicit bounded patterns for the required structure.

Do not copy a depth from an unrelated repository. Count the fixture’s relevant nesting, select a bound sufficient for that layout, and record it with the evidence. Then test the deepest path the policy actually needs to cover. A larger scan depth can add startup work, while a shallow bound can omit intended paths. The decision rule is to choose the smallest documented bound that covers the repository’s required root and nested cases, not the largest available value.

On macOS, still test both fixtures rather than transferring conclusions from another platform. On Linux, WSL and native Windows, explicitly record whether bounded pre-expansion was configured and what depth was chosen. Re-run the fixture if the repository layout moves environment files deeper, because an unchanged pattern with a changed tree may no longer represent the reviewed boundary.

Review every configuration layer before testing

A profile result is interpretable only when the loaded configuration is known. OpenAI’s configuration reference states that user-level configuration lives in ~/.codex/config.toml, while project-scoped configuration loads only for a trusted project. Profile files use the local Codex home and are selected by profile name. These locations serve different purposes: finding a profile file does not prove its selection, and trusting a project can introduce another loaded layer.

  1. Record the operating system and exact Codex version before changing configuration. Also record whether the repository is trusted for project-scoped loading. If trust status is unknown, resolve it before interpreting which project settings participate.

  2. Inspect ~/.codex/config.toml for profile selection, feature settings and legacy entries. Do not place dummy fixture values or any credentials in the review notes.

  3. Inspect every trusted project-level configuration file that Codex will load. Compare its relevant entries with the user-level file instead of assuming the nearer file simply replaces everything.

  4. Confirm that the intended local profile exists under the configured Codex home and that the invocation selects its exact local name. A typographical mismatch makes the planned policy irrelevant even if the file itself is well formed.

  5. Search all loaded layers and invocation flags for sandbox_mode. OpenAI states that permission profiles do not compose with older sandbox settings: when sandbox_mode appears in loaded configuration, Codex uses those older settings instead of default_permissions.

  6. Resolve every legacy occurrence before testing. Remove or isolate it in the disposable test arrangement, then repeat the layer inventory. Do not describe a result as a profile result while a loaded legacy mode controls the sandbox.

  7. Save a redacted configuration inventory showing file locations, selected profile, relevant non-secret settings and invocation conditions. Have another person check the inventory before accepting later observations.

The distinction between “defined” and “effective” is essential. A profile can be syntactically present yet inactive because another profile was selected, a trusted project layer changed the effective configuration, or sandbox_mode displaced default_permissions. The decision rule is that any unresolved layer or legacy mode makes subsequent evidence inconclusive; do not compensate by repeating operations until a preferred result appears.

Make proxy state part of the policy

network.enabled = true permits network access for commands, but OpenAI’s permissions documentation says it does not start the network proxy. Without an active proxy, profile domain rules do not restrict direct network access. Consequently, an allowed-domain list beside enabled = true is not, by itself, destination filtering.

Review proxy state separately from the profile’s destination text. Establish whether features.network_proxy, or applicable managed networking, is actively enforcing command traffic. Record that state at the time of each observation. Avoid non-loopback proxy binding and permissive local-binding or socket workarounds; if the intended proxy cannot operate within the documented arrangement, stop the test rather than broadening exposure to make it pass.

Use these three worked cases to interpret proxy state. In the first case, “network on, proxy off”, a sandboxed command may have direct network access; neither contact with the named host nor contact with another host supports an inference that profile domain rules were enforced. Record only that network access was enabled while filtering was not established.

In the second case, “network on, proxy on, exact host allowed”, a request by a local sandboxed command to the exact listed host tests the allow decision. If retained evidence shows that the applicable proxy handled the traffic under the selected profile, the observation can support the limited inference that this named command destination was permitted under those recorded conditions. It does not establish availability, identity, response correctness or wider egress behaviour.

In the third case, “network on, proxy on, unapproved host”, use a harmless public destination not present in the profile and retain the proxy or sandbox denial evidence. Only this proxy-enabled condition can support an inference that an unapproved domain was filtered by the profile. Even then, reject the inference if traffic escaped the command sandbox, an escalation was approved, routing bypassed the proxy, or the active configuration differed from the reviewed inventory.

The comparison must keep all other relevant conditions fixed: operating system, Codex version, profile selection, configuration layers, proxy state and test surface. Changing the proxy between attempts without labelling the evidence creates a false comparison.

Test exact hosts, not convenient wildcards

Prefer one exact public hostname because it makes the reviewed exception clear. A wildcard subdomain rule is not an apex-domain allowance. OpenAI’s approvals and security documentation distinguishes subdomain patterns from an apex host. In a symbolic, non-routable illustration, *.sample.invalid would match api.sample.invalid, but not sample.invalid; these names are not destinations to test.

Build a negative test around that distinction without adding the wildcard to the production template. In an isolated dummy variation, compare a request addressed to a selected subdomain with one addressed to its apex. Keep the proxy active and applicable, and label the variation as a glob-semantics check rather than evidence for the final exact-host policy. The expected policy distinction is that matching a subdomain does not imply matching the apex; retain the actual observations for human assessment instead of reporting a predetermined outcome.

If the workflow needs only the apex, name the apex exactly. If it needs only one subdomain, name that subdomain exactly. If both are genuinely required, list and justify them separately rather than relying on a wildcard. The trade-off is maintenance effort versus review precision: exact entries may need updates when a dependency changes, but they avoid silently covering sibling subdomains.

Collect bounded evidence without claiming universal egress control

Select the Codex CLI sandbox helper matching macOS, Linux or Windows and apply the intended permissions profile. Running the operating-system helper is a step for you to perform; this article does not report an execution. Preserve the command exit or denial evidence, timestamps, redacted effective settings and repository diff, but never place untrusted command output or secrets into a prompt.

Organise the evidence into separate observations: ordinary source editing; root .env access; nested .env access; exact named-host access with proxy state recorded; and unapproved-host access with proxy state recorded. A reviewer should be able to discard one observation without weakening the provenance of the others. Do not collapse them into a single “least privilege passed” label.

The network proxy filters only traffic from local commands running inside the sandbox. OpenAI expressly distinguishes web search, apps and connectors, MCP servers, Browser and Computer Use, Codex service traffic and other independent surfaces. The local test therefore cannot justify a broad egress guarantee. Git history, copied values and generated artefacts likewise need separate handling.

Before trusting the policy, require human review of the effective configuration, retained denials, proxy evidence and git diff. Remove the fixture afterwards and confirm that no dummy artefacts were unintentionally retained. Re-run the review when any invalidation condition listed earlier changes. Accept the profile only for the exact conditions evidenced; otherwise classify it as unverified and keep consequential decisions with a human reviewer.

Prove the allowed edit and every intended refusal

Return to OpenAI’s permissions documentation and the opening Beta caveat before interpreting results. Record the actual operating system, Codex version, selected profile, configuration layers, legacy-setting status and network-proxy state for this run.

Use a disposable, version-controlled copy of the fictional harbour-map repository. Its fixtures must contain dummy text only: no real secrets, access tokens, customer data, private hostnames or copied production values. The distinction matters because denying a path does not make real sensitive data safe to use in a test, nor does it protect values already copied into history or elsewhere.

Before running a command, create an evidence header such as the following example. Replace the example fields with observed values rather than assumptions:

Repository: disposable harbour-map fixture
Operating system: <name and release>
Codex version: <reported version>
Selected profile: harbour-edit
Configuration layers inspected: <user, trusted project and invocation flags>
sandbox_mode found: <yes/no; location if yes>
Active policy mechanism: <profile or legacy sandbox settings>
Command sandbox helper: <macos, linux or windows>
network.enabled: <observed value>
Network proxy enforcement: <active/inactive/not established>
Allowed test host: api.openai.com
Test date and reviewer: <date and person>

This record tests the effective policy, not merely the file chosen by name. Use the configuration-layer review above to rule out a legacy sandbox_mode override before interpreting the result as evidence for default_permissions.

A translucent sandbox cube and magnifying glass beside one narrow permitted network path and several blocked paths.
Conceptual network-proxy gate with allowed and blocked paths; a local sandbox is only one part of the boundary.

Run a fixture matrix, not a reassuring single check

A successful source edit and a denied environment-file read test different filesystem rules. Neither substitutes for the other. Use the harbour-map fixture defined above for all three filesystem observations.

Commit the synthetic fixture baseline before testing so the reviewer can distinguish the intended source edit from unrelated working-tree changes.

Begin with four independent observations: three filesystem cases (F1 to F3) and one network case (N1). Add a fifth, conditional network case (N2), described below, only after confirming sandboxed command traffic and active proxy enforcement:

Case Requested action Evidence needed Limited conclusion
F1 Write harmless text to src/probe.txt Sandbox command, exit status and reviewed diff The tested source path was writable under the recorded conditions
F2 Read .env Sandbox denial or failed read attributable to policy The root fixture was denied under the recorded conditions
F3 Read nested/.env Separate sandbox denial attributable to policy The tested nested fixture was denied at that depth
N1 Request api.openai.com Sandboxed command evidence plus recorded proxy state The named host was reachable, subject to ordinary network conditions

Do not collapse F2 and F3 into a single glob pass. The platform-qualified bounded expansion and scan-depth rules under “Bound the glob test to the operating system” limit conclusions to the paths and depths actually exercised.

Use the documented operating-system helper

OpenAI’s agent approvals and security documentation directs readers to the matching Codex CLI sandbox helper to observe commands under the sandbox. Choose exactly one helper for the recorded operating system, insert the selected profile, and substitute one narrowly scoped test command for the literal [COMMAND]... token:

# macOS
codex sandbox macos --permissions-profile harbour-edit [COMMAND]...

# Linux or WSL
codex sandbox linux --permissions-profile harbour-edit [COMMAND]...

# Native Windows
codex sandbox windows --permissions-profile harbour-edit [COMMAND]...

Run from the disposable repository root. Retain the complete command line, standard output, standard error, exit status and any denial message. These are collection instructions, not reported results; do not manufacture a successful write or policy-denial message for the evidence file. Keep timestamps and the evidence header alongside each case so a later reviewer can see which profile and proxy state applied.

For F1, ask the helper to run an operating-system-appropriate command that replaces the source fixture with harmless text such as harbour fixture: edited by sandbox test. Do not combine that write with environment-file reads. A zero exit status alone is insufficient: inspect the file and subsequently review git diff. The acceptance rule is that only src/probe.txt changed and its new contents are the exact dummy text requested.

For F2, use a fresh helper invocation to attempt to print .env. For F3, use another invocation to attempt to print nested/.env. Keeping them separate prevents one failed command from masking whether the other path was evaluated. Retain the exit status and denial text, but do not place even dummy file contents into a prompt when a command log is sufficient. Real sensitive values must never be used, requested or pasted into Codex.

A genuine policy denial requires evidence that the command ran through the selected sandbox, the intended profile was active, no legacy override controlled the run, and the failure was attributed to the relevant filesystem rule. A missing file, malformed command, wrong working directory, shell error or unsupported glob expansion is a failed test, not a confirmed denial. Likewise, an approved escalation changes the boundary being tested. Record it as an escalation and rerun without it if the least-privilege policy itself is the subject of the test.

Interpret root and nested denials cautiously

F2 and F3 apply that rule to the root and nested fixture paths. A recorded denial supports only the paths and conditions exercised, not a general claim that all secret-bearing files are protected.

If the root read is denied but the nested read succeeds, first preserve both records. Then check whether the recorded operating system needs bounded glob pre-expansion, whether the scan depth reaches nested/.env, and whether the tested path matches the configured expression. Do not immediately broaden filesystem permissions or add unrelated exceptions. The decision rule is to correct the smallest demonstrable mismatch and repeat both reads, because changing expansion may affect the root and nested cases differently.

If either command prints the fixture, classify the relevant case as a policy failure under the tested conditions. Remove any captured output from prompts and shared logs; even though this fixture is dummy data, the handling procedure should not encourage retention of real secrets. If the helper did not actually use the expected profile, classify the case as missing evidence instead. The trade-off is important: a strict classification produces fewer reassuring “passes”, but makes the retained evidence reproducible and reviewable.

Test network enablement separately from enforcement

A passing filesystem test establishes nothing about destination filtering. Before treating N1 as an allow-rule test, use the independently reviewed proxy state described under “Make proxy state part of the policy”; network access alone does not establish domain filtering.

Before N1, record two facts separately: whether command networking is enabled and whether proxy enforcement is active for the sandboxed command. If enablement is off, an allowed-host request may fail without testing the allow rule. If enablement is on but the proxy is off, a successful request may be direct and therefore cannot validate domain enforcement. The meaningful allowed-host case requires sandboxed command traffic and a known proxy state, while ordinary Domain Name System, Transport Layer Security and remote-service failures remain possible alternative causes.

Request only the single named public host api.openai.com, matching the documented source-editing example. Use a minimal client already present on the test system and avoid credentials, request bodies, private paths and authentication headers. Retain the exact helper invocation, exit status and enough client diagnostics to identify the destination and route without storing sensitive local environment data. Success supports the narrow conclusion that the named host was reachable from that sandboxed command under the recorded conditions; failure does not by itself prove policy denial.

If the request is rerouted through a wrapper, shell alias, local service or process outside the sandbox, mark N1 as rerouted and inconclusive. If a human approves an escalation, mark it as an approved escalation rather than an allow-rule success. If proxy enforcement cannot be demonstrated, label the observation proxy off or proxy state not established. These labels prevent direct access, altered routing and human-authorised exceptions from being mistaken for profile behaviour.

Interpret proxy evidence without overclaiming

Add the fifth case, N2, only after establishing that the request originates from a command inside the Codex sandbox and that proxy enforcement is active. Use one unapproved public host, www.iana.org, and no others. The point is to contrast an exact named destination with one destination absent from the profile, not to scan the internet or test private infrastructure.

Run N2 through the same operating-system helper and network client used for N1, changing only the hostname. Preserve the proxy-state evidence, command, exit status and denial details. A genuine policy denial requires an active enforcing proxy, sandboxed command traffic, no approved escalation and a message or other evidence attributing refusal to the destination policy. A timeout, name-resolution failure, unavailable service, certificate error or missing client is merely a failed network test unless the retained evidence identifies policy enforcement.

If www.iana.org succeeds while proxy enforcement is demonstrably active, record a policy failure or configuration mismatch and investigate the effective destination rules. Do not “fix” it with a global wildcard. As explained under “Test exact hosts, not convenient wildcards”, exact and wildcard hosts are distinct cases. The safer decision rule is to name the genuinely required host exactly, accepting the administrative cost of adding another reviewed destination if the command later needs one.

A denied N2 combined with a successful N1 supports only a local command-proxy conclusion for those two hosts and that recorded run. It is not proof of complete egress control, resistance to hostile code, or protection for traffic that bypasses this boundary. Do not present either result as a guarantee for another operating system, Codex version, profile, configuration stack or proxy state.

Other tool, browser and service surfaces, and values copied into Git history or artefacts, still need the separate reviews identified under “Separate what the profile governs from what it cannot contain”.

Review the only permitted diff and remove the fixtures

After all observations, inspect the repository rather than trusting command status. Start with git status --short, then review git diff -- src/probe.txt. The expected example is one harmless change in that file only. Also inspect git diff -- .env nested/.env; it should show no test-time modifications. Do not stage or commit until a human reviewer confirms that no other tracked path changed and that neither environment fixture appears in the diff.

If the diff includes an unexpected file, stop and preserve the evidence before reverting it. Classify the source-write case as failed even if src/probe.txt contains the requested text, because the allowed operation was intentionally narrow. If the only difference is the exact dummy source line, the reviewer may accept F1 while evaluating F2, F3, N1 and N2 independently. One passing case must not compensate for another case’s missing evidence.

Finally, restore src/probe.txt to its baseline and remove the dummy .env fixtures and any temporary nested directory created solely for testing. Use git status --short again to confirm the disposable repository is clean or contains only deliberately retained, non-sensitive evidence outside tracked source. Have a human review the diff, denial records, proxy-state evidence and classification table before trusting the policy for consequential work.

The final decision should be conditional: the profile is supported by the retained observations only if the source edit was limited as intended, both tested environment paths produced genuine policy denials, the named host behaved as expected, and the negative host was denied under active proxy enforcement. Any unresolved legacy override, rerouting, escalation, proxy-off run or absent log leaves the corresponding boundary unverified.

Maintain the boundary as software, not as a one-off setting

A permission profile should enter the same change-control process as the source tree it is intended to constrain. OpenAI describes permission profiles as Beta and under active development, so, as of 6 October 2026, a successful local test is evidence about one recorded combination of operating system, Codex version, active profile, configuration layers and proxy state. It is not a permanent contract. Preserve the fixture plan, expected observations and retained command evidence so that a later run can be compared with the original conditions.

Keep the policy’s purpose narrower than “secure the repository”. A named profile combines filesystem rules with network-destination rules for sandboxed local commands. That distinction creates two maintenance tracks. The filesystem track asks whether an ordinary source edit remains possible while the root and nested .env fixtures remain unreadable and unwritable. The network track asks first whether command networking is enabled, then whether the relevant traffic actually passes through an active enforcing proxy, and only then whether the required and unapproved destinations behave as intended. A pass in one track cannot repair missing evidence in the other.

Store a review record beside the team’s normal change documentation, without placing secrets or sensitive output in it. A useful record contains the evidence header fields, the commands attempted, exit or denial evidence and the human reviewer’s conclusion. Record the exact public host that the fixture genuinely requires. Do not record tokens, customer information, private endpoints or real environment values; those must never enter either the fixture or a prompt.

For example, a maintenance ticket for the synthetic harbour-map repository could say that the intended capability is to modify an ordinary source fixture, deny access to dummy root and nested environment files, and let one sandboxed command contact one named public host while rejecting an unapproved host when proxy enforcement applies. The ticket should refer to retained evidence rather than saying merely “least privilege works”. The latter hides the profile, platform, route and enforcement assumptions on which the conclusion depends.

Retest after a Codex client upgrade, an operating-system upgrade, a change to the profile, a change to user-level or project-scoped configuration, a different invocation flag, a proxy or routing change, or movement of files deeper in the repository. Also retest when the project’s required host changes. If any condition that determines profile selection, glob expansion, sandbox scope or network routing changes, treat previous results as historical evidence rather than current verification.

Do not make periodic retesting a ritual that automatically preserves every exception. At each review, ask whether the command still needs the named host and whether the source path still needs write access. If a dependency has been removed, remove its exception and rerun the relevant fixture. Keeping unused access because it passed an earlier review reverses least privilege: the policy becomes a catalogue of old requirements rather than a statement of current ones.

The trade-off is between reproducibility and maintenance cost. A deeper filesystem scan may cover more nested fixtures but can add startup work on platforms requiring bounded pre-expansion. A larger network allowlist can reduce interruptions but obscures which destination supports which task. Prefer the smallest depth justified by the repository layout and the smallest exact host set justified by the command. Any proposed expansion should identify the failing fixture or new functional requirement that motivates it, followed by fresh, human-reviewed evidence.

Diagnose misleading passes before widening access

Consider a synthetic incident in harbour-map. The ordinary source edit succeeds, as intended, but a command checking an unapproved domain also succeeds. That pair of observations does not establish that the profile is broken, and it certainly does not justify broader rules. The source result concerns a filesystem permission; the destination result depends on network enablement, active proxy enforcement, command-sandbox scope and routing. Diagnose those conditions separately before inferring anything about the domain rule.

First, preserve the invocation and its output without copying sensitive values. Confirm the tested operating system and Codex version, the profile requested by the invocation, and which configuration layers were loaded. OpenAI’s configuration reference states that user-level configuration resides in ~/.codex/config.toml and that project-scoped configuration is loaded only for a trusted project. Review every applicable layer and invocation flag rather than assuming that the repository’s visible file supplied the effective settings.

Second, check every loaded layer for sandbox_mode as in the configuration-layer review. If it overrides default_permissions, resolve the conflict with its owner and repeat the fixture before interpreting profile evidence.

Third, verify proxy enforcement separately from network enablement, as described under “Make proxy state part of the policy”. With the proxy off, a successful unapproved-host request cannot test the profile allowlist; retain route and enforcement evidence.

Fourth, confirm that the request came from a sandboxed local command and followed the expected route. The earlier boundary review distinguishes excluded tool, browser and service traffic from local proxy tests. Recreate the observation through the documented operating-system helper and retain command evidence.

Fifth, inspect approvals or routing changes that may have altered the test conditions. Do not describe an unapproved destination as blocked unless the command was sandboxed, the correct profile was active, the proxy was enforcing, and the tested route was within its scope. Conversely, do not describe one successful request as proof of unrestricted access everywhere. The meaningful conclusion is conditional: under the recorded route and enforcement state, this command reached this destination.

The decision rule for the incident is to stop and re-establish the test preconditions. Only after profile selection, absence of a legacy override, sandbox scope, proxy status and routing are confirmed should a repeated result be assessed against the domain rule. If the cause remains uncertain, classify it as missing diagnostic evidence and escalate it to a human reviewer. Do not convert uncertainty into a wildcard, a flag that disables the sandbox or an unsupported claim that the Beta mechanism has failed.

Treat ambiguous glob results as test-design defects

A nested .env result needs a separate diagnosis. Apply the operating-system and scan-depth conditions from “Bound the glob test to the operating system” to the actual nested path; never infer all-depth protection from a root-only denial.

Suppose access to the root dummy .env is denied but the nested dummy file produces an unexpected result. Preserve both observations, then document the nested path depth and the effective glob_scan_max_depth, if used. Determine whether the platform required pre-expansion and whether the selected bound reached that fixture before the sandbox started. Repeat the test with a justified bound or explicit depths. This is a test-design correction, not a reason to grant broad filesystem access.

A deeper scan can cover a deeper tree, but OpenAI notes that deeper scans can add startup work. Choose a bound from the repository’s actual protected-file layout rather than setting an arbitrary maximum. If environment-shaped files can appear at depths beyond the chosen scan, either revise the bounded fixture plan and accept the startup trade-off or define explicit protected locations. A human reviewer should approve that choice because it decides which repository structures the evidence covers.

Keep absence of evidence distinct from evidence of absence. If no nested fixture was tested, there is no evidence about nested matching. If the fixture lay beyond the recorded scan depth, its result does not show that a correctly expanded rule would fail. If a properly placed dummy fixture was denied under recorded conditions, that is evidence for that path and test environment, not proof that every secret-like file everywhere is protected.

Even a valid deny observation remains narrow. That path rule does not remove a value already copied into another file, repository history, generated artefacts or command output. It also does not govern excluded non-command surfaces. Review repository history, copied values and other data flows through independent controls rather than treating a local path denial as retroactive containment.

Reject shortcuts during maintenance review

A negative maintenance test asks the reviewer to reject an apparently convenient expansion. For example, imagine a proposed change replacing the one required host with a global wildcard because the unapproved-domain fixture succeeded during an unexplained run. Reject that proposal. It hides whether proxy enforcement was absent, traffic escaped the command sandbox, the wrong profile was selected, or a legacy mode overrode it. Return to the smallest named host, restore the known fixture conditions and repeat the observation.

Apply the same response to a request to bypass the sandbox merely to make a maintenance test pass. Do not adopt unrestricted access, bypass flags, permissive local bindings or blanket socket access as routine remedies. Such a change would alter the boundary rather than diagnose it. Require the proposer to state the concrete operation that cannot proceed, the exact resource it needs and the retained evidence showing that the narrow policy blocks a legitimate requirement.

In maintenance review, apply the exact-host rule under “Test exact hosts, not convenient wildcards”. If a proposed change needs an apex and selected subdomains, list and retest each required destination separately. An unexplained N2 result is not grounds to substitute a global wildcard.

Escalate genuine policy decisions to a human reviewer. A reviewer should decide whether a new destination is operationally required, whether a broader scan depth is justified, whether an excluded surface needs its own control and whether retained evidence is sufficient for release. Codex output may help organise observations, but it must not approve its own permissions. Consequential decisions about access, secrets, releases or incident conclusions require accountable human judgement.

Release checklist for a reviewed local policy

Release means accepting a conditional local policy and its evidence, not declaring a universal security property. Before approval, use the following concise review. Where an item cannot be answered, record a gap and defer the affected conclusion rather than marking it implicitly successful.

  • Fixture: confirm that the repository is disposable and version controlled, with ordinary source content plus root and nested .env fixtures containing dummy values only. No real secret, token, customer data or private endpoint may appear in the repository, prompts or retained output.
  • Environment: record the operating system, Codex version, repository revision and test date. Treat every result as conditional on those values and schedule retesting after relevant Codex, operating-system, fixture or configuration changes.
  • Selection: record the active profile and inspect applicable user, project and invocation layers. If any loaded layer contains sandbox_mode, stop: the legacy setting overrides default_permissions, so profile evidence is not interpretable until the conflict is resolved.
  • Filesystem: retain separate evidence for the permitted source edit, root environment-file refusal and nested environment-file refusal. Document the platform-specific glob expansion and bounded scan depth. Do not infer nested coverage from a root-only test.
  • Network: record network enablement separately from active proxy enforcement. Retain observations for the exact required public host and an unapproved host only when the request came from a sandboxed local command on the intended route.
  • Scope: state that local command restrictions do not govern web search, apps, connectors, MCP servers, Browser and Computer Use, Codex service traffic, repository history or copied values. Identify independent controls and owners for relevant surfaces.
  • Diff: have a human inspect the repository diff and confirm that only the intended dummy source edit occurred. Remove fixtures when their retention is unnecessary and verify that no generated artefact or copied dummy value was accidentally committed.
  • Exceptions: justify every writable path, scan depth and named host against a current requirement. Reject global wildcards and sandbox-bypass flags offered as diagnostic shortcuts. Remove access that the command no longer needs.
  • Evidence: retain commands, exits or denial output, effective conditions and reviewer conclusions without retaining secrets. Label unrun, escaped or ambiguous cases as missing evidence rather than evidence that access is absent.
  • Approval: require a human reviewer to accept residual uncertainty, approve genuine boundary changes and set the next retest trigger. Do not trust the Beta profile for consequential use solely because one fixture run appeared reassuring.

The final decision rule is conservative: release the local policy only when its intended source operation has positive evidence, each required refusal has applicable evidence, and the effective configuration and routing are understood. Otherwise, preserve the narrow policy, investigate the gap and withhold the unsupported conclusion. That approach keeps change control aligned with what was actually tested rather than with what the profile’s text appears to promise.

Access 40,000+ AI Prompts for ChatGPT, Claude & Codex — Free!

Subscribe to get instant access to our complete Notion Prompt Library — the largest curated collection of prompts for ChatGPT, Claude, OpenAI Codex, and other leading AI models. Optimized for real-world workflows across coding, research, content creation, and business.

Access Free Prompt Library

Useful Links

Get Free Access to 40,000+ AI Prompts for ChatGPT, Claude & Codex

Subscribe for instant access to the largest curated Notion Prompt Library for AI workflows.

More on this