Part 7: The Safety Net

Hooks, Policies, and the Approval Chain

Welcome to Part 7 of our journey into Codex's architecture. We've explored the brain, the nervous system, the vaults, the frontends, and the plugin ecosystem. Now we arrive at something equally fundamental, yet often invisible: the safety layer.

Here's a hard truth: AI agents can do remarkable things. But an agent without guardrails is a loaded gun in a china shop. The difference between a tool that's useful and one that's dangerous often comes down to a single question: Does a human remain in control?

This is Part 7. This is where Codex earns your trust.


The Philosophy: AI Assists, Humans Decide

Before we talk about code, let's talk about principles.

When you use Codex, you're not handing off control. You're delegating with oversight. The agent can suggest, plan, execute—but at every critical juncture, there's a human with their hand on the brake.

This is the human-in-the-loop philosophy.

Consider this scenario:

  • Codex suggests running rm -rf /home/important_data/
  • Without a safety layer, it runs immediately. Your data is gone. You fire everyone involved.
  • With Codex's safety net, one of three things happens: The policy says "allow"—and it runs because you've pre-approved it. The policy says "prompt"—and Codex pauses, shows you what's about to run, and waits for your explicit yes. The policy says "forbidden"—and it blocks the command, no questions asked.

The key insight: you decide the rules upfront, not in the panic of the moment.

This is why Codex has a layered defense model. It's not one safety mechanism—it's five, working in concert:

  1. Policy — declarative rules you set
  2. Approval — interactive prompts for edge cases
  3. Sandbox — OS-level isolation
  4. Hooks — event listeners for audit and monitoring
  5. Secrets — automatic redaction of sensitive data

Let's dive into each layer.


Layer 1: The ExecPolicy DSL — Policy as Code

At the foundation of Codex's safety sits execpolicy, a domain-specific language for declaring what code can and cannot do.

The .execpolicy File

When you start a Codex session, it reads three policy files in order:

  1. System defaults (/etc/codex/system.execpolicy)
  2. User policy (~/.codex/execpolicy)
  3. Project policy (.codex/execpolicy in your repo root)

Each level overrides the previous. This is least privilege by default, escalating upward only when needed.

A policy file looks like this:

# Allow safe read commands
allow prefix cargo build

# Prompt before deleting anything
prompt prefix rm
prompt prefix rm -rf

# Block network access to sensitive domains
deny network github.enterprise.com:443

# Allow public registries
allow network registry.npmjs.org:443
allow network crates.io:443

# Network policy: allow only HTTPS to known hosts
deny network * : * except https to safe-registries

# Shell escalation: prompt before any sudo
prompt prefix sudo

Pattern Matching: Flexible and Precise

Commands are matched using prefix rules. When you write:

allow prefix cargo build

Codex allows any command starting with cargo build. This means:

  • cargo build
  • cargo build --release
  • cargo build --features "feature1,feature2"

All pass. The rule is greedy but safe—it assumes the user knows what they're doing.

But if you want to be more precise:

prompt prefix cargo build --release
allow prefix cargo build

Now cargo build --release gets a prompt, but cargo build runs silently. Rules are evaluated in order, first match wins.

Network Policies: Host + Port + Protocol

Network rules are equally expressive:

deny network *:*  # Block everything by default

allow network github.com:443      # Allow HTTPS to GitHub
allow network api.example.com:443 # Allow HTTPS to your API
allow network localhost:3000      # Allow local dev server

deny network 192.168.1.1:22       # Block SSH to internal network

The grammar: [allow|prompt|deny] network [host]:[port] [protocol]

The Three-Tier Decision Flow

Every rule returns one of three decisions:

Allow — Execute immediately, no questions asked.

  • Used for safe commands you trust: cargo build, npm install, git commit
  • Pre-approval saves time

Prompt — Pause and ask the user for explicit approval.

  • Used for riskier operations: cargo publish, git push, rm -rf
  • This is your safety valve

Forbidden — Block immediately, no override possible.

  • Used for truly dangerous commands: sudo, curl https://malware.com | sh
  • No negotiation

The Amendment API: Dynamic Policy Updates

Here's the clever part: Codex policies can change during a session.

When Codex encounters a command that matches prompt, and you say "yes, do it," Codex can automatically update your policy:

pub async fn blocking_append_allow_prefix_rule(
    policy_path: &Path,
    prefix: &[String],
) -> Result<()> {
    // Append to ~/.codex/execpolicy
    let mut file = OpenOptions::new()
        .append(true)
        .open(policy_path)?;

    writeln!(file, "allow prefix {}", prefix.join(" "))?;

    Ok(())
}

This is optional but powerful. You can say:

  • "Yes, do it this once" — immediate execution, no policy change
  • "Yes, do it and remember for next time" — immediate execution, plus rule added

The policy file is human-readable and version-controlled. You can review your own decisions later, or audit what rules your team has accumulated.


Layer 2: The Approval Chain — Interactive Prompts in Action

When a command hits prompt, Codex doesn't just silently block. It asks you.

But asking is an art. Here's how Codex does it:

The Three-Question Model

When a shell command needs approval, Codex shows:

  1. What — The exact command being proposed
  2. Why — Codex's reasoning for running it
  3. Implications — What Codex detected as risky

Then it waits. You have three options:

[1] Yes, run it (this time)
[2] Yes, run it (and update my policy to allow this)
[3] No, block it

If you choose option 2, Codex appends to your .codex/execpolicy:

allow prefix rm -rf /home/user/old_project

Next time Codex tries this, it's pre-approved.

Approval Context Matters

Codex doesn't show approvals in isolation. It shows you the full turn:


This context helps you make informed decisions. You see the bigger picture, not just the dangerous command.


Layer 3: Lifecycle Hooks — Event-Driven Monitoring

While policies enforce what can happen, hooks let you observe what does happen.

Hooks are event listeners that fire at specific moments in a Codex session. They're fully pluggable—you can write your own.

The Hook Event Types

Codex fires hooks at four critical moments:

SessionStart

{
  "event_type": "session_start",
  "session_id": "sess-abc123",
  "cwd": "/home/user/project",
  "timestamp": "2025-03-18T10:30:00Z"
}

Fired when a user opens Codex. Use this to log who's starting sessions, or post to Slack.

AfterToolUse

{
  "event_type": "after_tool_use",
  "turn_id": "turn-5",
  "call_id": "call-42",
  "tool_name": "local_shell",
  "tool_kind": "local_shell",
  "tool_input": {
    "input_type": "local_shell",
    "params": {
      "command": ["cargo", "test"],
      "workdir": "codex-rs",
      "timeout_ms": 60000
    }
  },
  "executed": true,
  "success": true,
  "duration_ms": 8234,
  "mutating": false,
  "sandbox_policy": "strict",
  "output_preview": "test result: ok"
}

Fired after any tool use (shell, MCP, function calls). Perfect for logging and audit trails.

AfterAgent

{
  "event_type": "after_agent",
  "thread_id": "thread-xyz789",
  "turn_id": "turn-5",
  "input_messages": ["What tests are failing?"],
  "last_assistant_message": "I found 3 failing tests in util.rs..."
}

Fired after Codex responds. Use this to analyze Codex's reasoning or log the conversation.

Hook Payloads: Rich, Structured Data

Every hook payload includes:

  • session_id — Which Codex session this is
  • triggered_at — When the event fired (ISO 8601)
  • cwd — The working directory context
  • hook_event — The event details (tool, command, output, etc.)

This is structured logging. No parsing needed. Each hook can be consumed by monitoring tools, log aggregators, or custom scripts.

Writing Custom Hooks

You configure hooks in ~/.codex/config.toml:

[[hooks]]
name = "log-to-slack"
event = "after_tool_use"
command = "python3"
args = ["/home/user/codex-hooks/slack-notifier.py"]
only_on = "mutating"  # Fire only for commands that change state

[[hooks]]
name = "log-to-datadog"
event = "after_tool_use"
command = "/usr/local/bin/datadog-agent"
args = ["log", "--json"]

When the hook fires, Codex writes the HookPayload to the script's stdin as JSON. Your script processes it and can:

  • Post to Slack
  • Write to Datadog
  • Store in a local SQLite database
  • Trigger alerts
  • Anything you can script

Here's a minimal Python hook that logs commands to a file:

#!/usr/bin/env python3
import json
import sys
from datetime import datetime

payload = json.load(sys.stdin)

if payload['hook_event']['event_type'] == 'after_tool_use':
    event = payload['hook_event']
    if event.get('executed'):
        log_line = f"{payload['triggered_at']} | {event['tool_name']} | {event['success']}\n"
        with open('/home/user/.codex/command.log', 'a') as f:
            f.write(log_line)

Hooks fail gracefully. If a hook crashes, Codex logs the error but continues—it doesn't let monitoring break your workflow.


Layer 4: Secret Detection — Preventing Data Leaks

One of the deadliest mistakes an AI agent can make is exposing a secret: an API key, database password, or auth token.

Codex's secrets module watches for this. It:

  1. Detects patterns that look like secrets (GitHub tokens, AWS keys, database URLs with passwords)
  2. Redacts them from logs and hook outputs
  3. Alerts you if a secret appears in the output

Recommended by LinkedIn

Secret Detection in Action

Say Codex runs:

$ curl -H "Authorization: Bearer sk-abcd1234efgh5678ijkl9012" \
  https://api.example.com/deploy

The secret detector scans the output for patterns:

  • sk_* (OpenAI keys)
  • ghp_* (GitHub personal access tokens)
  • mongodb+srv://*:*@* (MongoDB connection strings with passwords)
  • AWS* environment variables

If a secret is detected, Codex:

  1. Logs a warning
  2. Redacts it in the hook
  3. Never stores the raw value — Only the fact that a secret was present

This is passive detection. Codex doesn't stop the command—secrets are useful sometimes. But it ensures they don't leak into logs, Slack messages, or audit trails.


Layer 5: Process Hardening — OS-Level Security

Beyond policies and hooks, Codex hardens the process itself at the OS level.

What Process Hardening Does

When Codex starts, before it runs any user code, it:

  1. Disables core dumps
  2. Disables ptrace attach (Linux/macOS)
  3. Removes dangerous environment variables
  4. Sets restrictive file permissions

All of this happens before main() using the ctor crate, ensuring it's the very first thing Codex does.


Layer 6: Shell Escalation Detection

One of the trickiest attacks: getting Codex to run sudo (privilege escalation).

If an attacker can trick Codex into running sudo, suddenly your entire system is at risk. The shell-escalation crate detects and manages this.

How It Works

When Codex is about to execute a command with sudo, the escalation module:

  1. Detects that the command starts with sudo
  2. Checks the escalation policy — is this allowed?
  3. Prompts the user — "Codex wants root. Yes or no?"
  4. Validates the command — does it match pre-approved patterns?

An escalation policy might look like:

allow_escalation_prefix: ["sudo apt-get install"]
allow_escalation_prefix: ["sudo systemctl restart"]
deny_escalation: true  # Default: block all escalation

If Codex tries sudo rm -rf /, and there's no explicit allow-rule, the command is blocked immediately—before it even asks for a password.

Timeout Protection

Even if you approve escalation, there's a safeguard: timeout after 5 seconds of inactivity.

pub struct Stopwatch {
    idle_threshold_ms: u64,
}

impl Stopwatch {
    pub fn check_idle(&self) -> bool {
        // If no keyboard input for 5 seconds, cancel the escalation
        self.elapsed_ms > self.idle_threshold_ms
    }
}

This prevents a situation where you approve something, walk away, and return to find the system has been escalated without your continued consent.


The Layered Defense in Practice: A Real Scenario

Let's walk through a real execution to see all layers working together:

The Scenario

You ask Codex: "Deploy the app to production."

Codex plan:

  1. Build the app (cargo build --release)
  2. Push the image (docker push myregistry/app:latest)
  3. Restart the service (sudo systemctl restart myapp)

Layer 1: Policy Check

Codex evaluates each command:

cargo build --release
  → Check policy: "allow prefix cargo build" ✓ ALLOW

docker push myregistry/app:latest
  → Check policy: "prompt prefix docker push" ✓ PROMPT

sudo systemctl restart myapp
  → Check policy: "deny prefix sudo" ✓ DENIED

Step 1: First Command (Auto-Approved)

Policy: Allow
├─ Command executes silently
└─ Hook fires: after_tool_use
   └─ Logs to ~/.codex/command.log

Step 2: Second Command (Requires Approval)

Policy: Prompt
├─ Approval chain triggered
│  ├─ What: docker push myregistry/app:latest
│  ├─ Why: Deploying the app image to the registry
│  └─ Implications: ⚠ Network access to external registry
├─ User sees full turn context
├─ User clicks "Yes, run it"
└─ Execution + Hook fire

Step 3: Third Command (Blocked)

Policy: Forbidden
├─ Command blocked immediately
├─ User is notified: "This command is forbidden by policy"
├─ No escalation request sent to kernel
└─ Codex told: "I cannot run this; it violates security policy"

Codex adapts. Maybe it suggests:

I can't use sudo, but I can ask you to manually run:
  sudo systemctl restart myapp

Or I can deploy using Docker Compose:
  docker-compose -f prod.yml restart

The Amendment API: Learning from Experience

Here's what makes this different from typical security models: policies evolve with use.

Each time you say "Yes, and remember this for next time," Codex appends to your policy file:

--- ~/.codex/execpolicy (before)
allow prefix cargo build

+++ ~/.codex/execpolicy (after)
allow prefix cargo build
+allow prefix docker push myregistry/app:latest

You can review this file anytime:

$ cat ~/.codex/execpolicy | grep allow
allow prefix cargo build
allow prefix docker push myregistry/app:latest
allow prefix npm run deploy

And you can version-control it:

$ git diff ~/.codex/execpolicy
+allow prefix docker push myregistry/app:latest

Over time, your policy becomes a record of your trust decisions. New team members can see what's been approved. Auditors can review your decisions.


Composing Policies: System, User, Project

Policies compose in layers:

  1. System policy (strict defaults)
  2. User policy (your personal preferences)
  3. Project policy (.codex/execpolicy in your repo)

They merge, with later layers overriding earlier:

System:  deny network *:*
Project: allow network registry.npmjs.org:443
Result:  npm install works, everything else is blocked

This is principle of least privilege in practice: default deny, then selectively allow based on context.


Hooks in the Wild: Real Monitoring Scenarios

Here are common hooks teams use:

Slack Notifications for Risky Commands

[[hooks]]
name = "slack-alert"
event = "after_tool_use"
command = "python3"
args = ["./hooks/slack-notifier.py"]
only_on = "mutating"  # Only notify for writes

Every time a mutating command succeeds, your #codex-audit channel gets a notification:

🤖 [Codex] Command executed
User: alice@example.com
Command: cargo publish v1.2.3
Duration: 12.4s
Result: ✓ Success

DataDog Metrics

[[hooks]]
name = "datadog"
event = "after_tool_use"
command = "/usr/local/bin/datadog-agent"
args = ["log"]

Every tool use becomes a DataDog event, searchable and graphable:

datadog:~$ logs "hook_event.event_type:after_tool_use AND hook_event.success:false"
14 results in the past hour

Local Audit Log

[[hooks]]
name = "local-audit"
event = "after_tool_use"
command = "python3"
args = ["./hooks/audit-logger.py"]

Hooks write to a SQLite database for offline analysis:

$ sqlite3 ~/.codex/audit.db "SELECT COUNT(*) FROM commands WHERE success = 0"
3 failed commands today

The Missing Layer: What Codex Cannot Protect Against

Before we finish, let's be honest about what safety layers can't do:

  1. Fundamental trust: If you approve a malicious command, it runs. Trust is the base layer.
  2. Supply chain attacks: If a dependencies contains malware, Codex can't detect it.
  3. Man-in-the-middle: If your network is compromised, secrets can leak.
  4. Physical security: If someone has physical access to your machine, all bets are off.

Codex's safety layers assume you're operating in a reasonably secure environment. They're a multiplier, not a replacement, for good security practices.


A Cliffhanger: Building on Top of Codex

You now understand how Codex works end-to-end:

  • Part 1-2: The architecture and protocol
  • Part 3: How Codex reasons
  • Part 4-5: How data is stored and accessed
  • Part 6: How plugins extend it
  • Part 7: How it stays safe

But here's the real question: What if you want to build on top of Codex?

What if you want to:

  • Embed Codex in your own product?
  • Create custom tool servers?
  • Build a dashboard for monitoring?
  • Integrate Codex with your company's infrastructure?

That's where the SDKs come in. Codex exposes everything as libraries—the agent engine, the protocol, the policy engine, even the hooks system.

You don't have to fork. You don't have to reinvent. You can compose.

In Part 8—the final part—we'll explore the SDKs. We'll build a custom MCP server from scratch, integrate Codex into a CI/CD pipeline, and create a monitoring dashboard.

The power to build is yours. The safety is built in. The future is open.

See you in Part 8.


Key Takeaways

  1. Human-in-the-loop is not just philosophy—it's architecture. Every risky command can be caught and approved.
  2. Policies are code. You write them once, version-control them, and they evolve with your needs.
  3. Five layers of defense: Policy → Approval → Sandbox → Hooks → Secrets. No single point of failure.
  4. Hooks are your telescope into Codex. Monitor, audit, and understand what's happening in real-time.
  5. Escalation is trackable and blockable. Privilege escalation requires both a policy and active user approval.
  6. Policies compose. System defaults + user preferences + project rules = context-aware security.
  7. Transparency wins. Everything is logged, auditable, and reversible. Trust but verify.

Codex isn't a tool you blindly trust. It's a system you understand, configure, and control.

That's the point.

Originally published on LinkedIn.