#419·portless

Bug: syncHostsFile erases all non-portless /etc/hosts entries when readHostsFile fails silently

Author: xdewxCreated Sep 11, 2026Updated Sep 12, 2026

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-allrouters

After (broken):

# portless-start
127.0.0.1 itemhub.localhost
# portless-end

Root Cause

In packages/portless/src/hosts.ts, readHostsFile() silently swallows read errors and returns "":

typescript
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:

typescript
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

  1. Throw instead of returning "" on read failure — a failed read should not be treated as "empty file"
  2. Validate content before writing — abort if the read content looks suspicious (e.g. no localhost entry, empty after removing portless block)
  3. Back up before overwriting — write old content to /etc/hosts.bak before 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.