Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Limit Network Access for Agents on a Hosted Mac

An agent that can call any host on the internet is an unaudited data pipe. One hallucinated URL, one compromised dependency, or one runaway retry loop is enough to leak data. Pinning the agent to its approved endpoints is a one-time setup that removes that surface.

Choose a Figment Mac Plan for Network-Restricted Agent Work

Start on the Figment virtual Intel macOS environment at $29/month (2 vCPU, 8 GiB RAM, 80 GiB disk). It runs macOS on Intel via Linux/KVM, with no GPU or audio, and is accessed through the browser console. For an agent that calls a small, stable set of HTTPS APIs, that is enough: pf is part of the macOS base system and enforces IP/port egress on this environment.

Move up to an M-series Mac from $95/month when the agent needs Apple silicon features — ARM execution, Core ML, or Vision framework work — on top of the network restrictions. The exact chip, RAM and disk at $95 are confirmed per quote, so request a configuration before you commit. Physical i3 Macs at $49 and M-series units collect requests until supply is confirmed.

If you are cross-shopping, check the vendor pricing pages directly: CloudMac publishes $99.99/month and a $999/year prepay on cloudmac.org (effective $83.25/month, below 12×$95 at Figment) for an M4/16GB/256GB; MacStadium lists M2.S at $109 and M4.S at $149 with support and a published 99.99% uptime target. Figment's difference on managed plans is the specific CI setup, backups and monitoring work, not a hardware spec match.

Use Two Layers: pf for Network, LuLu for Process

macOS ships with two independent network controls, and neither does everything on its own.

LayerWhat it filtersPer-processHostnamesBest for
pf (packet filter)IP addresses, ports, protocolsNoNo (IPs only)Broad egress allow-lists
Application FirewallSigned binaries, inbound onlyNoNoNot useful for agent egress
LuLu (Objective-See)Outbound per processYesResolved at prompt timePer-agent rules

Use pf as the baseline deny-by-default on the virtual Intel macOS. Add LuLu (version 4.5.7, requires macOS 10.15+) on an M-series host where you can approve its system extension in System Settings. In the $29 virtual environment, system-extension approval is not guaranteed; confirm in the configuration quote if you need per-process rules there, and otherwise rely on pf plus application-level allow-lists.

Build the Destination Policy First

Document every destination the agent legitimately needs before you write a rule. This table is the artifact you verify against.

ServiceHostnamePortProtocolEnforcement
OpenAI APIapi.openai.com443TCP/HTTPSpf CIDR + LuLu rule
Anthropic APIapi.anthropic.com443TCP/HTTPSpf CIDR + LuLu rule
GitHub (self-hosted runner)endpoints from GitHub meta API443TCP/HTTPSpf CIDR
Your artifact storeyour bucket host443TCP/HTTPSpf CIDR
Local loopback127.0.0.1anyanypf pass
Everything else*anyanypf default deny

pf matches on IPs, not hostnames. Resolve each host with dig +short at deploy time and schedule a weekly re-resolution. Pull provider ranges from the vendor's own published source (for example GitHub's /meta API or an AWS IP-range feed) rather than inventing CIDRs. Do not add App Store Connect or TestFlight "webhook" ranges — Apple does not publish such an allow-list; update only the firewalls and CI services you actually control.

Configure pf Egress Rules

The macOS pf configuration lives in /etc/pf.conf and loads anchor files from /etc/pf.anchors/. Keep agent rules in their own anchor. See man pfctl and man pf.conf on the host for the exact syntax of your macOS version.

Create /etc/pf.anchors/agent-egress:

# /etc/pf.anchors/agent-egress
# pf uses last-match semantics unless 'quick' is set. Put the broad
# default-deny first and let specific allow rules override it below.
# Replace the IPs with values re-resolved from your destination policy.

block out all

# Loopback
pass out on lo0 all

# DNS so the agent can resolve its own destinations
pass out proto udp to any port 53

# HTTPS to approved service IPs (re-resolve before each deploy)
pass out proto tcp to <openai_ips>     port 443
pass out proto tcp to <anthropic_ips>  port 443
pass out proto tcp to <github_ips>     port 443

Reference the anchor from /etc/pf.conf:

anchor "agent-egress"
load anchor "agent-egress" from "/etc/pf.anchors/agent-egress"

Check syntax, then load:

sudo pfctl -n -f /etc/pf.conf
sudo pfctl -ef /etc/pf.conf

Persist across reboots with a LaunchDaemon plist in /Library/LaunchDaemons/ — the location Apple documents for system daemons in the launchd developer archive. Set the plist to invoke pfctl -ef /etc/pf.conf at load; keep the plist owned by root with mode 644.

Verify Every Allowed and Blocked Path

Run this after every rule change. It scores reachability against the policy and exits non-zero on any mismatch.

#!/bin/bash
# verify_network_policy.sh
set -uo pipefail
PASS=0; FAIL=0

check() {
  local label="$1" host="$2" port="$3" expect="$4"
  if nc -z -G 3 -w 3 "$host" "$port" 2>/dev/null; then
    reached=yes
  else
    reached=no
  fi
  if [ "$reached" = "$expect" ]; then
    echo "PASS  $label (expected=$expect)"
    PASS=$((PASS+1))
  else
    echo "FAIL  $label (expected=$expect got=$reached)"
    FAIL=$((FAIL+1))
  fi
}

# Allowed
check "OpenAI"      api.openai.com                 443 yes
check "Anthropic"   api.anthropic.com              443 yes
check "GitHub API"  api.github.com                 443 yes

# Must be blocked
check "SMTP egress" smtp.gmail.com                 587 no
check "Random web"  example.com                    80  no
check "Metadata"    checkip.amazonaws.com          80  no

echo "Results: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]

A FAIL on any blocked line means a rule gap — fix it before the agent handles sensitive data. pf drops silently by default, so an unexpected timeout in the agent's own logs is the usual signal of a new destination you have not approved.

Handle Drift and Secrets

Cloud providers rotate IPs. Subscribe to the provider's own range feed, re-resolve the hostnames in your destination policy weekly, and diff against your anchor. When the agent needs a new endpoint, add it to the policy table first, update pf, then re-run the verification script.

Keep API keys in the host's Keychain or your CI secret manager, not in a plaintext env file. If you move secrets between hosts, transfer the names and restore values from the secret manager on the new host — never dump the whole environment to disk.

Start

Write the destination policy table first; it takes thirty minutes and eliminates most future debugging. Then browse Mac hosting plans and request your configuration to confirm chip, RAM, disk and whether per-process controls like LuLu are supported on the plan you pick.

Related reading


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms