Bug: syncHostsFile erases all non-portless /etc/hosts entries when readHostsFile fails silently
Bug Description
/etc/hosts was completely overwritten to contain only the portless-managed block. All original system entries (127.0.0.1 localhost, ::1, ip6-localhost, etc.) were lost.
Before (normal):
127.0.0.1 localhost
127.0.0.1 myhost
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allroutersAfter (broken):
# portless-start
127.0.0.1 itemhub.localhost
# portless-endRoot Cause
In packages/portless/src/hosts.ts, readHostsFile() silently swallows read errors and returns "":
function readHostsFile(): string {
try {
return fs.readFileSync(HOSTS_PATH, "utf-8");
} catch {
return ""; // silent failure returns empty string
}
}Then syncHostsFile() treats that empty string as valid content:
export function syncHostsFile(hostnames: string[]): boolean {
const content = readHostsFile(); // "" on error
if (blockMatchesHostnames(content, hostnames)) return true;
try {
const cleaned = removeBlock(content); // "" — nothing to remove
fs.writeFileSync(HOSTS_PATH, cleaned.trimEnd() + "\n\n" + block + "\n");
// Writes ONLY the portless block — original entries are gone
}
// ...
}If readHostsFile fails even once (file lock, temporary permission issue, race condition), the entire hosts file is replaced with just the portless block. Since writeFileSync succeeds, portless considers the operation successful and the loss is invisible until DNS breaks.
Suggested Fix
- Throw instead of returning
""on read failure — a failed read should not be treated as "empty file" - Validate content before writing — abort if the read content looks suspicious (e.g. no
localhostentry, empty after removing portless block) - Back up before overwriting — write old content to
/etc/hosts.bakbefore modifying
Impact
Lost 127.0.0.1 localhost causes breakage across the system (many tools rely on localhost resolving). IPv6 loopback entries are also lost. Recovery requires manual editing with sudo.
Source: vercel-labs/portless