Disclosure: I'm the developer of the app described here, and this post was drafted and published by an AI agent from the project's source code.
Bolo turns an Android phone into a microphone for a Windows PC: you speak on the phone and the text appears at the PC cursor over local Wi-Fi. The PC side is a small Node.js helper that listens for the phone on the LAN: a TCP port for the connection, a UDP port for a discovery probe, and mDNS.
For a while, a version of the helper did not add any firewall rules on purpose. The reasoning was reasonable: the first time an exe binds a listening socket, Windows shows its "Windows Security Alert: allow access?" popup, the user clicks Allow, done, and no UAC prompt from us. Then one PC stopped seeing the phone entirely, and this is what the cause turned out to be.
The symptom: loopback answers, the LAN doesn't
The discovery probe sent from the same PC got a reply. The same probe from the phone got nothing. No error on either side, just silence. Windows Firewall drops inbound packets without telling the sender, so "the firewall ate it" looks exactly like "nobody is listening", unless you test from loopback, which the firewall does not filter.
That is the first lesson: when a LAN service is silent, send the same request to 127.0.0.1 on the same machine. If loopback works and the LAN doesn't, stop debugging your code.
The cause: the popup you are relying on can be switched off
On that machine (Windows Server 2022, domain-joined), every firewall profile had NotifyOnListen set to False and DefaultInboundAction set to Block. With notifications off, the "allow access?" popup never appears. Windows blocks the listener and asks nobody. There was no rule for the helper, so its traffic was dropped.
You can check a machine yourself in PowerShell:
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, NotifyOnListen
Group policy on managed and server machines commonly turns those notifications off. So any LAN app that depends on the popup works on a home laptop and fails silently in an office.
The fix: check for the rules on every start, and add them once, deliberately
The helper now treats "are my inbound rules present?" as a startup check, not a one-time setup step:
function missingRules() {
if (!isWindows()) return [];
return [RULE_NAME_TCP, RULE_NAME_UDP_DISC, RULE_NAME_MDNS].filter(name => !hasRule(name));
}
If any are missing, it logs a loud warning saying the phone will not see this PC, and then adds all of them with one elevated batch, rather than one UAC prompt per rule:
`Start-Process -FilePath cmd.exe -ArgumentList '/c ${batch}' -Verb RunAs -Wait -WindowStyle Hidden`
-Wait matters: after the prompt closes, the helper checks the rules again and only then records firewallConfigured = true. "We asked" is not the same as "the rules exist"; if the user declined, the flag stays false.
Some rules about when to raise that prompt:
-
Not under
node. Ifprocess.execPathisnode.exe, this is a dev or CI run. Elevating there would pop a prompt on a developer's machine. Only the packaged exe asks. -
Not for a standard user. On a shared PC, a non-admin user cannot approve UAC; asking them only shows an admin password box. The helper checks
whoami /groupsfor the Administrators SID (S-1-5-32-544) and leaves the job to an admin session otherwise. - Once per version, not every start. The version it last asked on is saved. After that, a tray item ("Allow through Windows Firewall") retries on demand instead of nagging at every boot.
The second bug: rules scoped to one user's exe
The first version of the fix scoped the rules to the program path, which sounds like least privilege. But on a PC where several Windows users each run their own copy from their own %LOCALAPPDATA%, the rule named only the first user's exe. Worse, the check matched rules by name, so every other user's helper saw the rule, believed it was allowed, and was still dropped.
The rules are now scoped by port and by network instead of by path:
const lan = 'remoteip=localsubnet profile=any enable=yes';
`netsh advfirewall firewall add rule name="${RULE_NAME_TCP}" dir=in action=allow protocol=TCP localport=${TCP_PORT_RANGE} ${lan}`
That is the app's own port range, the discovery port and mDNS, and only from the local subnet. One admin approval covers every user of the PC, and nothing from outside the local network is let in. Because the rule names changed, PCs that only had the old per-exe rules see them as missing and ask once more.
What carries over
- Silent on the LAN but fine on loopback: suspect the firewall before your code.
- Don't depend on Windows' "allow access?" popup.
NotifyOnListencan be off, and then nothing asks. - Verify the rules on every start, add them in one elevated batch, and set your "configured" flag only after re-checking.
- Don't elevate from dev runs or for users who can't approve.
- If a check matches rules by name, make sure the name means the same thing for every user. Port plus
localsubnetis often a better scope than an exe path.
Bolo is on Google Play (search "Bolo AI"), and the Windows companion is on GitHub releases. The phone and PC talk over your local Wi-Fi.
Top comments (0)