Skip to content

8. Security hardening

Network
Firewall on, nothing exposed
Access
SSH keys, Tailscale only
Secrets
Keychain, least privilege

An always-on machine that holds API keys and source code — and runs agents that can execute commands — deserves a little more care than a laptop. None of this is exotic; it’s mostly switches.

  • The internet finding an exposed service. → Expose nothing; use Tailscale.
  • Theft of the machine. → FileVault, and nothing sensitive in plain text.
  • An agent doing something you didn’t intend, through a bug, a bad instruction, or a prompt injected via a web page, issue or document it read. → Limit what the agent’s account and tokens can reach.
  • You, later, unable to remember what’s installed. → Keep notes, a Brewfile and backups.
  • Use an administrator account only for installing software and sudo.
  • For the strongest separation, run your agents in a standard account (System Settings → Users & Groups → Add User). A standard account can’t change system settings or install system-wide software, which limits the damage a misbehaving agent can do. Homebrew installs still need the admin account.
  • Never give an agent your admin password or passwordless sudo.

System Settings → Network → Firewall → On. Under Options, you can also turn on stealth mode so the Mac doesn’t answer pings or probes. In Terminal:

Terminal window
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
  • Nothing forwarded on your router. Reach the Mac through Tailscale.
  • SSH with keys only — turn off password logins.
  • Only your account allowed for Remote Login and Screen Sharing.
  • In Tailscale, use access controls if other people share your tailnet, and review the device list now and then.
  • Turn off sharing services you don’t use: System Settings → General → Sharing (File Sharing, Media Sharing, Remote Management, Remote Apple Events, Internet Sharing) and AirPlay Receiver.

API keys in .env files, shell profiles and plists are plain text. The login Keychain encrypts them and can be read from scripts:

Terminal window
# Store (you'll be prompted for the value, so it doesn't land in shell history)
security add-generic-password -a "$USER" -s example-api-key -w
# Read it in a script
export EXAMPLE_API_KEY="$(security find-generic-password -a "$USER" -s example-api-key -w)"

A LaunchAgent can call a small wrapper script that reads the key this way and then execs the real command, so the key never sits in the plist.

Also:

  • Give each tool its own key with the smallest scope — for example a GitHub fine-grained token limited to the repositories an agent works on, with an expiry date.
  • Rotate keys you’ve pasted anywhere you shouldn’t have.
  • Keep .env files out of git (.gitignore) and out of synced folders.

Coding agents can run shell commands. Their approval settings are your main safety control:

  • Keep command approval on for anything that can delete, deploy or spend money, and use allowlists for routine commands rather than turning approvals off wholesale.
  • Only run “skip all permission prompts” modes in a disposable environment — a separate standard account, a container or a VM — never in your main account with access to your keys and documents.
  • Point agents at specific project folders, not your whole home directory.
  • Treat content an agent reads from the web, issues or documents as untrusted input.
  • macOS: Security Responses and system files install automatically; install macOS updates on your own schedule (see macOS setup).
  • Tools: brew update && brew upgrade weekly; agent update for the Cursor CLI; Claude Code updates itself.
  • Keep FileVault on unless you’ve deliberately chosen Option B.
  • Keep Require password after screen saver begins or display is turned off on.
  • Turn on Find My Mac (System Settings → [your name] → iCloud → Find My Mac).
  • Leave System Integrity Protection and Gatekeeper on. Check with csrutil status and spctl --status.
Terminal window
fdesetup status
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
csrutil status
spctl --status
sudo systemsetup -getremotelogin
ls /etc/ssh/sshd_config.d/