> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Restrict Codex Desktop to LLM and MCP

> Intercept Codex Desktop and child-process traffic, then allow only LLM and approved MCP egress

export const G = ({term, anchor, children}) => {
  const href = anchor ? `/docs/glossary/index#${anchor}` : `/docs/glossary/index`;
  return <a href={href} className="glossary-link" style={{
    textDecoration: "underline",
    textDecorationLine: "underline",
    textDecorationColor: "#6b7280",
    textDecorationThickness: "1px",
    textUnderlineOffset: "2px",
    color: "inherit",
    transition: "text-decoration-color 0.2s ease",
    borderBottom: "none"
  }} onMouseEnter={e => e.target.style.textDecorationColor = "#fff"} onMouseLeave={e => e.target.style.textDecorationColor = "#6b7280"}>
  {children || term}
</a>;
};

## Overview

<Tip>
  The [Restrict Codex Desktop egress](https://app.formal.ai/recipes/restrict-codex-desktop-egress)
  recipe creates this network rule and policy for you. See
  [Recipes](/docs/guides/getting-started/recipes).
</Tip>

Codex Desktop can open a built-in or external browser for web search. Teams often want coding agents to use an approved MCP server instead.

Formal can enforce that on macOS with Endpoint [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode):

1. A [network rule](/docs/guides/network-rules) intercepts Codex Desktop and the processes it launches.
2. The Endpoint classifies each flow as LLM, MCP, or HTTP.
3. A request-stage <G anchor="policy">policy</G> allows LLM traffic and named MCP resources.
4. Direct web search, spawned browsers, and other HTTP are blocked.

Developer browsers that Codex did not launch stay off this path. They do not have Codex in the process tree.

```mermaid theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sequenceDiagram
  participant Codex as Codex Desktop
  participant Child as Child process or browser
  participant Endpoint as Formal Endpoint
  participant MCP as Approved MCP
  participant Web as Direct web search

  Codex->>Endpoint: LLM request
  Endpoint-->>Codex: Allow classified LLM traffic
  Codex->>Endpoint: Approved MCP tool call
  Endpoint->>MCP: Forward named MCP resource
  Codex->>Child: Spawn browser or search client
  Child->>Endpoint: HTTP to an unapproved host
  Endpoint-->>Child: Block by policy
```

## Prerequisites

* Formal Endpoint on macOS with [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode) enabled
* Permission to create Network Rules, <G anchor="resource">Resources</G>, and policies
* An approved MCP server created as a Formal MCP resource

## Create approved MCP resources

Named MCP resources are the allowlist. Unnamed MCP traffic is blocked.

Create one Formal MCP resource per approved server. Use the MCP hostname and port.

<Tabs>
  <Tab title="Control Plane">
    1. Navigate to [Resources](https://app.formal.ai/resources)
    2. Click **Create Resource**
    3. Set **Technology** to **MCP**
    4. Enter the MCP hostname and port `443`
    5. Save the resource
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    resource "formal_resource" "web_search_mcp" {
      name       = "web-search-mcp"
      technology = "mcp"
      hostname   = "mcp.example.com"
      port       = 443
    }
    ```
  </Tab>
</Tabs>

Replace `mcp.example.com` with your approved MCP hostname. Repeat for each allowed MCP server.

LLM calls to ChatGPT are classified from the wire protocol. You do not need a ChatGPT <G anchor="resource">resource</G> for this policy.

## Create the network rule

Intercept Codex Desktop and any child it spawns. Leave **Forward to Connector** unset so the Endpoint evaluates policy locally.

The Codex Desktop app is signed with bundle ID `com.openai.codex`. Matching that ID on the connecting process or an ancestor covers spawned browsers and helper binaries.

<Tabs>
  <Tab title="Control Plane">
    1. Navigate to [Network Rules](https://app.formal.ai/network-rules)
    2. Create a rule named `codex-desktop-egress`
    3. Paste the CEL below
    4. Leave **Forward to Connector** unset
    5. Save the rule and set it to **Active**
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    resource "formal_network_rule" "codex_desktop_egress" {
      name        = "codex-desktop-egress"
      description = "Intercept Codex Desktop and child-process traffic"
      status      = "active"

      cel_expression = <<-EOT
        {
          "condition": {
            "pre_tls": process.signing.bundle_identifier == "com.openai.codex" ||
              process.ancestors.exists(p, p.signing.bundle_identifier == "com.openai.codex"),
            "post_tls": true
          }
        }
      EOT
    }
    ```
  </Tab>
</Tabs>

```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
  "condition": {
    "pre_tls": process.signing.bundle_identifier == "com.openai.codex" ||
      process.ancestors.exists(p, p.signing.bundle_identifier == "com.openai.codex"),
    "post_tls": true
  }
}
```

`process.has_agent_ancestor` is true for Codex Desktop itself and for children of any known agent. Use it when you want this intercept for every coding agent, not only Codex Desktop.

<Note>
  Do not add `forward_to_connector` on this rule. LLM traffic is evaluated on
  the Endpoint. User browsers without Codex in the ancestor list skip the rule.
</Note>

## Create the policy

The policy blocks Codex Desktop requests that are not allowlisted. Allow LLM traffic, named MCP resources, and ChatGPT HTTP except built-in search.

Codex Desktop still needs ChatGPT HTTP for auth and session setup. Those calls are not classified as LLM. The built-in search path stays blocked so Codex must use your MCP.

<Tabs>
  <Tab title="Control Plane">
    1. Navigate to [Policies](https://app.formal.ai/policies)
    2. Click **Create Policy**
    3. In **Choose a Template**, select **Allow Codex Desktop LLM And MCP Only**
    4. Click **Create Policy** to save

    You can also paste this Rego if you skip the template:

    ```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    package formal.v2

    import future.keywords.if
    import future.keywords.in

    llm_traffic := true if {
      input.resource.technology == "llm"
    }

    allowlisted_mcp := true if {
      input.resource.technology == "mcp"
      input.resource.name != ""
    }

    chatgpt_hostname := true if {
      input.http.hostname == "chatgpt.com"
    }

    chatgpt_hostname := true if {
      endswith(input.http.hostname, ".chatgpt.com")
    }

    chatgpt_http_not_search := true if {
      input.resource.technology == "http"
      chatgpt_hostname
      input.http.path != "/backend-api/codex/alpha/search"
    }

    codex_agent := true if {
      lower(input.agent.type) in {
        "codex desktop",
        "codex-desktop",
        "codex_cli_rs",
      }
    }

    request := {
      "action": "block",
      "type": "block_with_formal_message",
      "reason": "Direct web access blocked: use an approved MCP server",
    } if {
      codex_agent
      not llm_traffic
      not allowlisted_mcp
      not chatgpt_http_not_search
    }
    ```
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    resource "formal_policy" "codex_desktop_llm_mcp_only" {
      name        = "codex-desktop-llm-mcp-only"
      description = "Allow Codex Desktop egress only through LLM and named MCP resources"
      status      = "active"

      module = <<-EOT
        package formal.v2

        import future.keywords.if
        import future.keywords.in

        llm_traffic := true if {
          input.resource.technology == "llm"
        }

        allowlisted_mcp := true if {
          input.resource.technology == "mcp"
          input.resource.name != ""
        }

        chatgpt_hostname := true if {
          input.http.hostname == "chatgpt.com"
        }

        chatgpt_hostname := true if {
          endswith(input.http.hostname, ".chatgpt.com")
        }

        chatgpt_http_not_search := true if {
          input.resource.technology == "http"
          chatgpt_hostname
          input.http.path != "/backend-api/codex/alpha/search"
        }

        codex_agent := true if {
          lower(input.agent.type) in {
            "codex desktop",
            "codex-desktop",
            "codex_cli_rs",
          }
        }

        request := {
          "action": "block",
          "type": "block_with_formal_message",
          "reason": "Direct web access blocked: use an approved MCP server",
        } if {
          codex_agent
          not llm_traffic
          not allowlisted_mcp
          not chatgpt_http_not_search
        }
      EOT
    }
    ```
  </Tab>
</Tabs>

Create the policy in **Draft** or **Dry-run** first. Set it to **Active** after you review matching logs.

Desktop LLM sessions set `input.agent.type` from the User-Agent. The value is `Codex Desktop`. Spawned browsers inherit that session through the process tree.

To allow another HTTP service, add a helper like `chatgpt_http_not_search`. Include that helper in the `not` conditions.

## Verify

Confirm four outcomes after you activate the rule and policy.

**Allowed LLM traffic**

1. Send a normal Codex Desktop prompt that does not search the web.
2. Open [Logs](https://app.formal.ai/logs).
3. Confirm `resource.technology:llm` and Agent **Codex Desktop**.
4. Confirm the request is allowed.

**Allowed MCP traffic**

1. Ask Codex to search or fetch using your connected MCP server.
2. Confirm `resource.technology:mcp` and a non-empty `resource.name`.
3. Confirm the request is allowed.

**Blocked built-in search**

1. Ask Codex to search the web without using your MCP server.
2. Codex should receive a Formal block.
3. Confirm a blocked log for `/backend-api/codex/alpha/search`.

**Blocked child-process browsing**

1. Ask Codex to open an external or headless browser to an unapproved site.
2. The request should fail with a Formal block.
3. Confirm the HTTP log is attributed to Codex Desktop.

**Unchanged developer browsing**

Open your own browser to the same site. That flow should not match the Codex rule. It should not show Agent as Codex Desktop.

## Next Steps

<CardGroup cols={2}>
  <Card title="Transparent Mode" icon="desktop" href="/docs/guides/client-apps/desktop-app#transparent-mode">
    Install and enable Formal Endpoint Transparent Mode on macOS
  </Card>

  <Card title="Network Rules" icon="filter" href="/docs/guides/network-rules">
    Select which Endpoint traffic Formal intercepts
  </Card>

  <Card title="MCP Resources" icon="robot" href="/docs/guides/core-concepts/resources/mcp">
    Proxy and govern approved MCP servers
  </Card>

  <Card title="LLM Resources" icon="brain-circuit" href="/docs/guides/core-concepts/resources/llm">
    Inspect classified LLM traffic on the Endpoint
  </Card>

  <Card title="Agent Policy Inputs" icon="magnifying-glass" href="/docs/guides/policies/evaluation#ai-agents">
    Use `input.agent` in request-stage policies
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.