Security
What Vhostly can do on your Mac
Last updated 3 November 2026
Vhostly changes system configuration, so you should be able to check exactly how much access it has. The short answer: one small helper runs as root, it can only add local names to /etc/hosts, and everything else runs as you.
What runs as root
Only the hosts helper, /Library/PrivilegedHelperTools/app.vhostly.hosts. It is installed once, when you enter your password at the "Vhostly: one-time setup permission" prompt. Its whole job is to rewrite the block of /etc/hosts between Vhostly's markers.
- It reads hostnames on standard input and takes no arguments.
- It accepts only names ending in
.testor.localhost. Anything else, including a real domain, an IP address or a stray character, makes it stop without writing. - It writes only
127.0.0.1and::1, so it can only ever point a name at this Mac. - Lines outside Vhostly's block are left exactly as they are. The new file replaces the old one in one step.
- It is owned by root in a folder only root can write to, so nothing running as you can change what it does.
The sudoers rule
One file, /etc/sudoers.d/vhostly, with one line. It lets your account run that helper as root without a password, with no arguments, and nothing else. It is checked with visudo before it is put in place.
<you> ALL=(root) NOPASSWD: /Library/PrivilegedHelperTools/app.vhostly.hosts ""
The helper, in full
This is the exact script the app installs. It is short on purpose, so it can be read in a minute.
#!/bin/zsh -f
# Vhostly hosts helper v1. Installed by Vhostly, owned by root.
# Rewrites only the Vhostly block of /etc/hosts. See https://vhostly.app/security/
emulate -L zsh
setopt err_exit no_unset pipe_fail
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
umask 022
HOSTS=/etc/hosts
BEGIN='# >>> vhostly: managed by Vhostly, do not edit >>>'
END='# <<< vhostly <<<'
NAME_RE='^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*[.](test|localhost)$'
typeset -a keep
typeset -A ours
count=0
while IFS= read -r line || [[ -n $line ]]; do
[[ -z $line ]] && continue
(( ++count > 5000 )) && { print -u2 "vhostly: too many names"; exit 2 }
forget=0
if [[ $line == -* ]]; then forget=1; line=${line#-}; fi
if (( ${#line} > 253 )) || [[ ! $line =~ $NAME_RE ]]; then
print -u2 "vhostly: refused $line (only names under .test or .localhost)"; exit 2
fi
[[ -n ${ours[$line]-} ]] && continue
ours[$line]=1
(( forget )) || keep+=$line
done
tmp=$(mktemp /private/etc/.hosts.vhostly.XXXXXX)
trap 'rm -f $tmp' EXIT
inblock=0
while IFS= read -r l || [[ -n $l ]]; do
if [[ $l == "$BEGIN" ]]; then inblock=1; continue; fi
if [[ $l == "$END" ]]; then inblock=0; continue; fi
(( inblock )) && continue
if [[ $l == 127.0.0.1[[:space:]]* || $l == ::1[[:space:]]* ]]; then
typeset -a toks rest
toks=(${=l}); rest=()
for t in $toks; do [[ -n ${ours[$t]-} ]] || rest+=$t; done
if (( ${#rest} != ${#toks} )); then
(( ${#rest} > 1 )) || continue
l=${(j: :)rest}
fi
fi
print -r -- $l >> $tmp
done < $HOSTS
if (( ${#keep} )); then
{ print -r -- $BEGIN
for n in $keep; do print -r -- "127.0.0.1 $n"; print -r -- "::1 $n"; done
print -r -- $END; } >> $tmp
fi
chown root:wheel $tmp
chmod 644 $tmp
mv -f $tmp $HOSTS
trap - EXIT
dscacheutil -flushcache 2>/dev/null || true
killall -HUP mDNSResponder 2>/dev/null || true
What runs as you
- Apache. Homebrew's httpd runs from your own LaunchAgent, and macOS lets any process listen on ports 80 and 443, so it needs no root. Vhostly restarts it with
apachectl -k restart, without sudo. - Apache's config.
httpd.confand the vhost files live in Homebrew's folder, which already belongs to you. Vhostly backs each one up before it changes it, and checks the result withapachectl -tbefore restarting. See what Vhostly writes. - Start commands. A proxy site can run a command when it is switched on, such as
npm run dev. It is the command you typed, run as you, in the folder you chose; Vhostly never runs a command it was not given. Commands are stored in~/Library/Application Support/Vhostly/runners.json.
The app's local API
The window talks to Vhostly's engine over HTTP on 127.0.0.1, on a random port. It is not reachable from other devices. It refuses requests that come from a web page, by their Origin header, and requests addressed to any host other than 127.0.0.1 or localhost, which stops DNS-rebinding tricks. Its CORS header names only the app's own window, so a web page cannot read its answers.
Network mode and public links
- Network mode serves a site you choose on a separate port so your phone on the same Wi-Fi can open it. Only sites you share are reachable, and switching a site off or quitting network mode closes them.
- Public links use Cloudflare quick tunnels (
cloudflared, trycloudflare.com). Anyone with the link can open that one site while the tunnel runs. Vhostly shows a banner for as long as any link is open, and stops the tunnels when you switch the site off or quit the app.
Certificates and keys
- Local HTTPS uses mkcert. Its certificate authority's private key is in
~/Library/Application Support/mkcert, readable only by your account. - Each site's key is in
/opt/homebrew/etc/httpd/certs, also readable only by your account and Apache, which runs as you. - Trusting that authority is mkcert's trade-off, not Vhostly's: a program running as you could read its key and make certificates your browsers accept. If you stop using local HTTPS, run
mkcert -uninstallto remove the trust.
Downloads and updates
Builds are published on GitHub Releases and downloaded over HTTPS. The app is ad-hoc signed and not yet notarised by Apple, so macOS asks you to confirm the first launch; once a notarised build ships, macOS will verify it automatically. Vhostly has no automatic updater: nothing is installed without you downloading it.
Undoing everything
Every change is listed, with how to reverse it by hand, in what Vhostly writes. Removing the helper and its rule is one command:
sudo rm /etc/sudoers.d/vhostly /Library/PrivilegedHelperTools/app.vhostly.hosts
Versions before 1.3
Vhostly 1.2 and earlier gave your account ownership of /etc/hosts and allowed sudo apachectl without a password. Because Homebrew's apachectl lives in a folder your account owns, that rule could be used by any program running as you to gain root. Vhostly 1.3 removes both the first time it opens: it deletes the old rule, gives /etc/hosts back to root and installs the helper above. Settings → Health shows a warning if anything from the old setup is still in place. If you have stopped using an older version, remove its rule with sudo rm /etc/sudoers.d/vhostly.
Reporting a problem
If you find a security issue, please send us a private message rather than opening a public issue. We will reply, fix it, and credit you if you would like.
Questions about this page? Send us a message. See also the Terms, Privacy Policy and Refund Policy.