Anka wordmark above a coding agent sending a commit to a CI agent in a fresh Anka VM inside a network boundary, with the Git mirror allowed and the host Mac and neighbor VM blocked

Why CI Agents for macOS and iOS Need Network-Secure Disposable Environments

When a coding agent writes the commit and a second agent validates it, the second agent should start on a clean Mac and reach only what the build needs. For iOS and macOS, that means a fresh macOS VM for every commit, with a network boundary drawn before the CI agent starts.

The job to be done

AI coding agents now make code changes, run unit tests, and commit. A CI agent then picks up that commit. The flow most teams are building looks like this:

  1. A coding agent changes the source tree and runs unit tests.
  2. The coding agent commits the change.
  3. A CI agent starts in a disposable environment.
  4. The CI agent builds and validates that exact commit.
  5. Results go back to the pull request or pipeline.
  6. The environment is destroyed.

The split between steps 2 and 3 matters. The coding agent works iteratively and collects state as it goes: installed packages, caches, credentials, half-finished experiments. The CI agent should validate the commit from a clean state and inherit none of that.

On Linux, a container covers the clean state. iOS and macOS builds need a real macOS environment on Apple hardware, with the macOS and Xcode versions the project needs available on demand.

The problem with macOS CI agents on open networks

A traditional CI job runs a fixed sequence of scripts. A CI agent driven by an LLM makes decisions while the job runs. It can invoke tools, run shell commands, read files, install software, retry failed steps, and call external services.

A disposable VM stops one job’s filesystem from leaking into the next job. It does not decide what the agent can reach while the VM is up. With default networking, a CI agent in a macOS VM can often reach:

  • the Mac host it runs on
  • other CI VMs on the same host, including jobs for other repos
  • internal engineering services such as artifact stores, dashboards, and admin APIs
  • package repositories and arbitrary Internet endpoints
  • build infrastructure that has nothing to do with this job

Disposable does not mean secure. Each CI agent should get the network access its build needs and nothing more.

Why this is harder on macOS

Mac hardware is expensive, so teams run more than one VM per host. That puts CI agents for different jobs side by side on the same machine at the same time. Isolation has to hold between agents running now, as well as between jobs over time.

Default virtual networking is built for convenience. Guests share a private subnet, reach the host gateway, and see their neighbors. That helps a person who wants to SSH in and install a package. It is the wrong default for an agent that runs commands it chose itself.

The environment is also recreated for every commit. A host firewall rule that someone set by hand on one node does not follow the VM to the next node, and it drifts. Any network control for CI agents has to ship inside the template that every job clones.

How Anka handles it: the VM is the agent’s boundary

Treat each disposable macOS VM as a capability boundary around one CI agent run. Before the agent starts, the orchestration layer picks the template, the network rules, and the short-lived credentials for this job. The agent then works inside that VM. The rules decide whether it can reach the host, a neighbor VM, or a service outside the job. When the job ends, artifacts and results leave and the VM is deleted.

Anka Build gives you both halves of that model.

Disposable macOS VMs across a Mac cluster

You keep VM templates in the Anka Registry, one tag per macOS and Xcode combination. The Anka Build Cloud Controller starts an instance from a tag on any node with free capacity, and deletes it when the job is done. Each job starts from the tagged template, so nothing from the previous job is present. See running several macOS and Xcode versions on one Mac for how teams lay out those tags.

CI systems request these VMs through Anka plugins and integrations for Jenkins, GitLab, Buildkite, and GitHub Actions. If your repos live on Cursor Origin, the Origin, Buildkite, and Anka walkthrough shows the path from pull request to VM. For GitHub Actions at scale, see enterprise macOS GitHub Actions runners with Anka.

A network boundary that ships with the template

Anka’s network controls live in the VM configuration, so they travel with the tag to every node that pulls it. IP filtering and the advanced security CLI features require an Enterprise or Enterprise Plus license. The rules work on shared networking. anka modify refuses the change while the VM is running (is running). Stop the VM, apply the rules, then start it.

These results are from Anka Build Enterprise Plus 3.10.0. Two shared-mode VMs used 192.168.64.2 and 192.168.64.3. The host gateway was 192.168.64.1.

block local stops the host and the peer VM. It leaves the public internet open. From the guest on .2, before any filter:

$ ping -c 2 192.168.64.1
64 bytes from 192.168.64.1: icmp_seq=0 ttl=64 time=0.307 ms
64 bytes from 192.168.64.1: icmp_seq=1 ttl=64 time=2.874 ms

$ ping -c 2 192.168.64.3
64 bytes from 192.168.64.3: icmp_seq=0 ttl=64 time=1.494 ms
64 bytes from 192.168.64.3: icmp_seq=1 ttl=64 time=0.513 ms

$ ping -c 2 1.1.1.1
64 bytes from 1.1.1.1: icmp_seq=0 ttl=53 time=74.853 ms
64 bytes from 1.1.1.1: icmp_seq=1 ttl=53 time=73.482 ms

After echo "block local" | anka modify ci-agent-net-a network -f- and a start, anka show ci-agent-net-a network -f printed block local. The same pings:

$ ping -c 2 192.168.64.1
Request timeout for icmp_seq 0
2 packets transmitted, 0 packets received, 100.0% packet loss

$ ping -c 2 192.168.64.3
Request timeout for icmp_seq 0
2 packets transmitted, 0 packets received, 100.0% packet loss

$ ping -c 2 1.1.1.1
64 bytes from 1.1.1.1: icmp_seq=0 ttl=53 time=13.323 ms
64 bytes from 1.1.1.1: icmp_seq=1 ttl=52 time=13.249 ms

--no-local is the network-card flag, separate from the filter file. With the filter off, anka show reports no_local | 1. Pings to .1 and .3 timed out. The ping to 1.1.1.1 still got a reply. arp -a inside that VM showed the peer as (incomplete), so it did not learn the peer MAC. The peer VM, still on the default --local, did learn this VM’s MAC. The gateway MAC was still listed inside the --no-local VM.

Network Isolation for macOS VMs covers ARP spoofing prevention and the rest of the control list. ARP spoofing prevention stays on when you keep experimental nat off the template.

Per-job settings from the Controller

The template sets the default boundary. The Controller’s start instance API lets the orchestration layer adjust it per job:

  • tag picks the macOS and Xcode image for this commit.
  • network_local with forceOff turns off VM-to-host networking for this instance before it starts.
  • startup_script runs inside the VM after the network is up. You can pass environment variables in it, such as the commit SHA and a short-lived token scoped to this job.
  • external_id stores an arbitrary string with the instance, so you can tie each VM to the commit it validated.
  • group sets the Permission Group for the instance when Resource Permissions for Permission Groups is on.

A DELETE on the same endpoint terminates the instance when the CI agent reports back.

An allowlist for one CI agent VM

block any after a single pass out is not enough. That file left the guest with no default route (route: writing to routing socket: not in table), and every ping failed with sendto: No route to host. DHCP uses ports 67 and 68. Those packets have to match a pass rule before block any. Return traffic needs its own pass in. The first matching rule wins, so the pass lines go above block any.

Stop the VM, then embed the file. 1.1.1.1 stands in for the one host this job may use. Swap it for your Git remote or package mirror.

anka stop ci-agent-net-a

cat <<'EOF' | anka modify ci-agent-net-a network -f-
pass out from port 68 to port 67
pass in from any port 67 to any port 68
pass in from 1.1.1.1
pass out to 1.1.1.1
block local
block any
EOF

anka show ci-agent-net-a network -f
anka start ci-agent-net-a

anka show printed those six lines back. From inside the guest:

  • ping 1.1.1.1 got replies (51 ms and 126 ms).
  • ping 8.8.8.8, ping 192.168.64.1, and ping 192.168.64.3 each ended with 100.0% packet loss.

A port rule is narrower than a host rule. This file allows DHCP, plus port 443 to 1.1.1.1 in both directions:

pass out from port 68 to port 67
pass in from any port 67 to any port 68
pass out to 1.1.1.1 port 443
pass in from 1.1.1.1 port 443
block local
block any

curl -m 8 -o /dev/null -w "%{http_code}" https://1.1.1.1 printed 301. ping 1.1.1.1 timed out.

Put block any first and the later pass lines never run. The same 443 rules under block any made that curl fail in 13 ms: Couldn't connect to server.

A second anka modify … network -f- replaces the whole set. After echo "block any" | anka modify ci-agent-net-a network -f-, anka show … network -f printed only block any.

anka modify … network --filter off clears the file. Add --no-local in the same stopped window if you also want the card-level cut. Do not stack --no-local on the allowlist unless you have checked that your mirror is still reachable. --no-local with no filter still reached 1.1.1.1 and still blocked .1 and .3.

Which control covers which risk

RiskControl
CI agent pings the Mac host (192.168.64.1)ipf: block local. Or --no-local
CI agent pings the VM next to itipf: block local. Or --no-local
CI agent reaches the rest of the internetipf: DHCP pass rules, then pass for each approved host, then block any
CI agent uses only HTTPS to one hostipf: pass … port 443 in both directions
A later pass never matchesipf: keep block any last. First match wins
Peer learns this VM’s MAC--no-local: set it on every VM that must stay hidden

A strict allowlist has a cost. If a build fetches dependencies from public registries at run time, block any will break it. Point the build at a mirror you pass, or install the dependencies into the template. Keep a separate tag for people who debug builds by hand.

The Controller start-instance API can set network_local to forceOff for one job. That is the per-job form of --no-local.

Ephemerality and isolation solve different problems

Ephemerality gives every CI agent a clean starting point and removes its state when the job ends. Network isolation controls what that agent can reach while it is alive. A fresh VM with an open network still lets an agent reach your host and its neighbors, and a locked network on a long-lived VM still carries state from one commit to the next.

CI systems driven by coding and validation agents need both on macOS.

Thanks for reading. If you have any questions please reach out to the team at support@veertu.com or jump right in by requesting a trial license: https://veertu.com/getting-started-trials/

Share this post

Anka wordmark above two macOS VMs and the host Mac, with VM-to-host and VM-to-VM links blocked by block local and --no-local
Network Isolation for macOS VMs: VM-to-VM, VM-to-Host, and ARP Spoofing Prevention
Meet macOS VM compliance requirements with Anka: block local for VM-to-VM and VM-to-host isolation, --no-local for a hard cut from the host, ARP spoofing prevention, and ipf-style IP filters on shared networking.
Read More
Diagram of Anka Build Cloud monitoring: anka_agent and Controller feed the Anka Prometheus Exporter, Prometheus scrapes it, promtail pushes Node and container logs to Loki, and Grafana reads both for dashboards and alerts
Monitor Anka Build Cloud Disk Space with Prometheus, Grafana, and Loki
Anka Build Cloud exposes node disk, capacity, and instance metrics through the Anka Prometheus Exporter, and its agent and container logs reach Loki through promtail. Here is how to scrape the metrics into Prometheus, graph free disk in Grafana, and alert...
Read More
Cursor Origin logo above an Origin PR to Buildkite to Anka VM flow for macOS CI on hardware you control.
Cursor Origin, Buildkite, and Anka: macOS CI on Agent-Hosted Repos
Cursor Origin now connects Buildkite for CI on Origin-hosted repos. Pair that with Anka's Buildkite plugin so each job runs in a disposable macOS VM on hardware you control.
Read More
Anka wordmark above two isolated VM windows: macOS 14 with Xcode 15, and macOS 15 with Xcode 16, on one Mac.
Running Several macOS and Xcode Versions Side by Side on One Mac
Run concurrent Anka macOS VMs with different OS and Xcode stacks on one host: density math for vCPU and RAM, and why per-project templates beat a shared mutable machine.
Read More
anka-and-kubernetes
On-Demand macOS VMs in Azure DevOps Pipelines with Anka
Run macOS VMs in Azure DevOps Pipelines with Anka. Anklet-style on-demand agents are blocked by Microsoft self-hosted pools today; use a registered agent plus per-job Anka VMs.
Read More
AWS + Anka Build Cost Diagramv3
Ephemeral macOS VMs on AWS EC2 Mac with Anka
Run ephemeral macOS VMs on AWS EC2 Mac with Anka and Anklet. Pack more iOS CI capacity per instance, start jobs in seconds, and cut cost.
Read More
Screenshot 2025-01-08 at 2.16
Enterprise macOS GitHub Actions Runners with Anka
Run self-hosted macOS GitHub Actions at enterprise scale with Anka and Anklet: ephemeral Apple Silicon VMs, more control than hosted runners.
Read More
The Anka product ladder: Develop, Flow, Build, and EC2 Mac as four ascending steps, with Crypt, MCP, Anka Scan, and AMI Scan named below
Which Anka Product Do You Actually Need? A Walkthrough of the Whole Lineup
A situation-first guide to every Veertu product: Anka Develop, Anka Flow, Anka Build, AWS EC2 Mac, Anka Crypt, Anka MCP, Anka Scan, and EC2 Mac AMI Scan, including the moment you move from one to the next.
Read More
anka2024v1-1536x768
A Year of Anka: Highlights from 2024
We’re starting a new annual tradition here at Veertu with our A Year of Anka blog posts. We want our customers to know how the product has grown over the past year and think this is a great avenue to do so. Please enjoy and happy holidays from all...
Read More
anka-or-1
Anka vs Orka in 2024
It has been several years since we made our first side by side comparison between Anka and Orka. A lot has changed, and we believe it’s important to make sure the information out there is accurate. We’ll be specifically addressing a newer...
Read More