MenuBarNetToggle is a small native macOS menu-bar utility that repeatedly keeps every network interface down except the interfaces you explicitly allow.
The application appears in the menu bar as NetCtl. It provides controls for enabling or disabling enforcement, editing the interface allowlist and enforcement interval, and starting the menu-bar application automatically when you sign in.
Caution
Enabling enforcement can immediately disconnect Wi-Fi, Ethernet, VPNs, virtual machines, AirDrop, device sharing, and other services if their interfaces are not in the allowlist. Identify the interfaces your Mac needs before turning enforcement on. Keep lo0 allowed so local loopback networking continues to work.
When enforcement is enabled, MenuBarNetToggle installs and starts a root-owned launch daemon named com.netctl.netenforce. The daemon enumerates the Mac's network interfaces with ifconfig -l. On each pass, it runs ifconfig <interface> down for every interface that is absent from the configured allowlist.
The default source configuration is:
ALLOWLIST="en0 utun4 lo0"
INTERVAL=15
Do not assume these defaults match another Mac. In particular, utun numbers can change as VPNs and system networking services start and stop.
MenuBarNetToggle does not:
- inspect or capture packets;
- send interface information to a remote service;
- configure the macOS application firewall or Packet Filter (
pf); - automatically restore interfaces that it previously brought down;
- stop the root helper merely because the menu-bar application quits.
The menu-bar title reflects the helper state:
| Indicator | Meaning |
|---|---|
NetCtl ○ |
The enforcement daemon is not running. |
NetCtl ● |
The enforcement daemon has a live process and enforcement is active. |
Open the menu to access:
- Enforcement: On/Off — installs, starts, or stops the privileged helper. macOS requests administrator authorization.
- Launch at Login — creates or removes a per-user LaunchAgent for the menu-bar application.
- Settings… — edits the allowlist and interval. Saving requires administrator authorization because the configuration is root-owned.
- Quit — exits only the menu-bar application. It does not stop active enforcement.
MenuBarNetToggle consists of two independently managed processes:
User session
MenuBarNetToggle.app
Optional LaunchAgent: ~/Library/LaunchAgents/com.netctl.MenuBarNetToggle.plist
System session
LaunchDaemon: /Library/LaunchDaemons/com.netctl.netenforce.plist
/bin/sh /Library/Application Support/MenuBarNetToggle/enforce.sh
Configuration: /Library/Application Support/MenuBarNetToggle/config.conf
PID record: /var/run/com.netctl.netenforce.pid
The menu-bar application runs as the signed-in user. It asks macOS for administrator authorization when it needs to create, update, start, or stop the system helper. The helper and its configuration are then owned by root:wheel.
The launch-at-login setting and enforcement setting are intentionally separate:
- Launch at Login can be on while enforcement is off.
- Enforcement can remain on after the menu-bar application quits.
- Logging out stops the menu-bar application, but the system LaunchDaemon can remain active.
- macOS 13 or later
- Xcode or Xcode Command Line Tools with the Swift compiler
- An administrator account to enable enforcement or change its privileged configuration
- A native build performed on the target Mac
Check the developer tools before building:
xcode-select -p
xcrun --find swiftc
swiftc --versionIf the tools are missing, install Apple's Command Line Tools:
xcode-select --installgit clone https://github.com/hideouts-io/MenuBarNetToggle.git
cd MenuBarNetToggleThis application intentionally performs privileged network-interface changes. Review at least these files before running it:
less main.swift
less build.sh
plutil -p Info.plistThe helper script and LaunchDaemon are generated by main.swift only after you enable enforcement.
./build.shThe build script:
- Compiles
main.swiftwith Apple's Swift compiler and Cocoa framework. - Creates
build/MenuBarNetToggle.app. - Applies a local ad-hoc code signature.
- Performs strict code-signature verification.
The resulting application targets macOS 13 or later and is built for the current Mac's architecture (arm64 or x86_64). It is not a universal binary, Developer ID signed, or notarized for third-party distribution.
You can repeat the verification manually:
codesign --verify --deep --strict --verbose=2 build/MenuBarNetToggle.app
codesign -d --verbose=4 build/MenuBarNetToggle.app
file build/MenuBarNetToggle.app/Contents/MacOS/MenuBarNetToggleQuit any older copy of MenuBarNetToggle first. Then copy the new bundle:
ditto build/MenuBarNetToggle.app /Applications/MenuBarNetToggle.appIf /Applications is not writable by your account, run only the copy command with administrator privileges:
sudo ditto build/MenuBarNetToggle.app /Applications/MenuBarNetToggle.appVerify the installed copy:
codesign --verify --deep --strict --verbose=2 /Applications/MenuBarNetToggle.appopen /Applications/MenuBarNetToggle.appLook for NetCtl ○ in the menu bar. The application is an agent-style app (LSUIElement) and therefore does not normally appear in the Dock.
Open NetCtl → Settings… and enter the interfaces that must stay usable. Separate interface names with spaces or commas. The interval must be between 1 and 3600 seconds.
Use these read-only commands to inspect the current Mac:
ifconfig -l
networksetup -listallhardwareports
route -n get default
scutil --nwiTypical interface names include:
lo0— local loopback; normally keep this allowed.en0,en1, and similar — Wi-Fi, Ethernet, or other hardware ports. Confirm them withnetworksetup; do not rely only on the name.utun0,utun1, and similar — tunnel interfaces used by VPNs and some Apple networking features. Their numbering is not stable.bridge0— a software bridge used by features such as Internet Sharing or virtualization.awdl0andllw0— Apple peer-to-peer networking used by features such as AirDrop and Continuity.
Select NetCtl → Enforcement: Off. Approve the administrator prompt. The label changes to Enforcement: On, and the menu-bar indicator becomes NetCtl ● when the helper is running.
Immediately confirm that required connectivity still works. If it does not, turn enforcement off and restore the affected interface before adjusting the allowlist.
Select NetCtl → Launch at Login. This creates:
~/Library/LaunchAgents/com.netctl.MenuBarNetToggle.plist
The LaunchAgent points to the exact location of the running application. Move the app to /Applications before enabling this setting so the saved path remains valid.
- Turn Enforcement off if you want to avoid interface changes during the update.
- Turn Launch at Login off if the existing LaunchAgent points to a different app location.
- Quit MenuBarNetToggle.
- Pull and rebuild the source.
- Replace the installed application.
- Start the new application and re-enable the desired settings.
git pull --ff-only
./build.sh
sudo ditto build/MenuBarNetToggle.app /Applications/MenuBarNetToggle.app
open /Applications/MenuBarNetToggle.appReplacing the app does not automatically remove the existing root helper or configuration. Enabling enforcement from the rebuilt app rewrites those helper files using the current configuration.
pgrep -alf MenuBarNetToggle
launchctl print "gui/$(id -u)/com.netctl.MenuBarNetToggle"The launchctl print command reports an error when Launch at Login is disabled or the LaunchAgent is not loaded.
launchctl print system/com.netctl.netenforce
cat "/Library/Application Support/MenuBarNetToggle/config.conf"
cat /var/run/com.netctl.netenforce.pidCheck ownership and permissions:
ls -l "/Library/Application Support/MenuBarNetToggle"
ls -l /Library/LaunchDaemons/com.netctl.netenforce.plistExpected privileged files are owned by root:wheel. The helper script is executable, while the configuration and daemon property list are not.
for interface_name in $(ifconfig -l); do
ifconfig "$interface_name" | sed -n '1p;/status:/p'
doneAn interface without the UP flag has been administratively brought down or is otherwise inactive. Interface status alone does not prove MenuBarNetToggle caused the state; correlate it with the daemon state and configuration.
The LaunchDaemon writes standard output and standard error to:
/var/log/com.netctl.netenforce.out
/var/log/com.netctl.netenforce.err
Inspect them without modifying them:
sudo tail -n 100 /var/log/com.netctl.netenforce.out
sudo tail -n 100 /var/log/com.netctl.netenforce.errEmpty logs are normal when all helper commands complete successfully.
Select NetCtl → Enforcement: On and approve the administrator prompt. Confirm that the helper is no longer loaded:
launchctl print system/com.netctl.netenforceThe expected result is an error stating that the service could not be found.
Stopping enforcement does not automatically bring previously disabled interfaces back up. The safest general recovery is to use macOS System Settings → Network to reconnect the required service. If you have positively identified the affected interface, you can bring that exact interface up manually:
sudo ifconfig en0 upReplace en0 only with an interface name you verified on the current Mac.
The Quit command and deleting the .app are not sufficient when enforcement or Launch at Login has been enabled. Use the following sequence to remove the user LaunchAgent, root LaunchDaemon, helper, configuration, PID record, logs, and application.
Use the menu to turn Enforcement off, turn Launch at Login off, and then choose Quit.
if launchctl print "gui/$(id -u)/com.netctl.MenuBarNetToggle" >/dev/null 2>&1; then
launchctl bootout "gui/$(id -u)/com.netctl.MenuBarNetToggle"
fi
rm -f "$HOME/Library/LaunchAgents/com.netctl.MenuBarNetToggle.plist"if launchctl print system/com.netctl.netenforce >/dev/null 2>&1; then
sudo launchctl bootout system/com.netctl.netenforce
fi
sudo rm -f /Library/LaunchDaemons/com.netctl.netenforce.plist
sudo rm -f /var/run/com.netctl.netenforce.pid
sudo rm -f /var/log/com.netctl.netenforce.out
sudo rm -f /var/log/com.netctl.netenforce.err
sudo rm -rf "/Library/Application Support/MenuBarNetToggle"These commands target only files created by MenuBarNetToggle. The configuration is deleted and cannot be recovered unless it was backed up separately.
sudo rm -rf /Applications/MenuBarNetToggle.appIf you ran the application directly from the source tree, also remove its generated build directory if desired:
rm -rf buildpgrep -alf MenuBarNetToggle
launchctl print system/com.netctl.netenforce
test ! -e "$HOME/Library/LaunchAgents/com.netctl.MenuBarNetToggle.plist"
test ! -e /Library/LaunchDaemons/com.netctl.netenforce.plist
test ! -e "/Library/Application Support/MenuBarNetToggle"The process search should return no MenuBarNetToggle process, both launchctl print checks should report that the services are absent, and the test commands should produce no output with a successful status.
Confirm that the process started:
pgrep -alf MenuBarNetToggle
open /Applications/MenuBarNetToggle.appIf the process exits, launch the executable from Terminal to see immediate errors:
/Applications/MenuBarNetToggle.app/Contents/MacOS/MenuBarNetToggleNo privileged change is completed when macOS reports error -128 for cancelled authorization. Reopen the menu and repeat the operation when ready.
That is expected. The system LaunchDaemon is independent of the menu-bar UI. Reopen the app and turn enforcement off, or stop the exact service from Terminal:
sudo launchctl bootout system/com.netctl.netenforceTurn enforcement off first. Determine the VPN's current tunnel interface with scutil --nwi and the VPN application's diagnostics. Add the confirmed interface to the allowlist before re-enabling enforcement. Avoid assuming that a previously observed utun number remains correct.
Stopping the helper prevents future enforcement passes but does not reverse prior ifconfig ... down commands. Reconnect the service in System Settings or bring the verified interface up manually. If macOS service state remains inconsistent, toggle the relevant network service off and on or restart the Mac after confirming the helper is unloaded.
Confirm that the active developer directory and SDK are valid:
xcode-select -p
xcrun --sdk macosx --show-sdk-path
xcrun --find swiftcIf Xcode was moved or replaced, select the intended installation explicitly:
sudo xcode-select --switch /Applications/Xcode.app/Contents/DeveloperUse the path of the Xcode installation actually present on the Mac.
- The GUI is locally ad-hoc signed by the build script. This provides bundle integrity checking but not publisher identity or Apple notarization.
- Enabling or changing enforcement requires an administrator authorization prompt.
- The helper, configuration, and LaunchDaemon are written as root-owned files.
- The helper executes as root because changing arbitrary interface state requires elevated privileges.
- The configuration is sourced by a root shell. Do not change its ownership or grant untrusted users write access.
- Interface names are validated by the GUI, but the design remains intentionally small and is not a replacement for a hardened privileged-helper architecture using
SMAppServiceor XPC. - The PID file is a lightweight status mechanism. It is not a cryptographic proof of process identity.
- Allowlisting an interface permits that interface to remain up; it does not restrict destinations, applications, protocols, or traffic content.
- The project currently has no automatic update mechanism.
Review the code and test on a non-critical Mac before relying on it for operational controls.
The project deliberately avoids third-party dependencies and project generators. The complete application is built from:
MenuBarNetToggle/
├── Info.plist
├── README.md
├── build.sh
└── main.swift
Build artifacts are written beneath build/ and excluded from Git.
Before proposing a change:
./build.sh
plutil -lint Info.plist
codesign --verify --deep --strict --verbose=2 build/MenuBarNetToggle.appChanges affecting allowlist validation, launchd paths, privilege boundaries, or uninstall behavior should be tested against real macOS launchd behavior. Do not test enforcement remotely unless you have a separate recovery path; an incorrect allowlist can sever the connection used to administer the Mac.
No open-source license has been selected yet. Copyright law therefore reserves the code by default. Add an explicit license before inviting third-party reuse or contributions.