WebDriverAgent 16.15.3 closes a LAN-open video port that USE_IP never covered; phoneshell's own check has never tested it either
WebDriverAgent's MJPEG broadcaster bound to every network interface regardless of USE_IP until pull request #1299 landed on 16 October 2026. phoneshell sets USE_IP and forwards both WDA ports, but its lan_exposure_check has only ever opened a socket to port 8100, the one that was never the problem.
WebDriverAgent 16.15.3, merged 16 October 2026 as pull request #1299, fixes an MJPEG broadcaster that ignored the USE_IP setting entirely: the HTTP server on port 8100 bound to the configured address, but the live video stream on port 9100 listened on every network interface regardless. phoneshell sets USE_IP=127.0.0.1 on every launch and forwards both ports as a pair, and its own LAN-exposure check has never once opened a socket to port 9100 to find out whether that promise held.
Two sockets, one setting, one of them listening anyway
WebDriverAgent exposes two TCP services on a physical iPhone: an HTTP server (default port 8100, carrying /wda/activeAppInfo, /source, /wda/apps/launch, every command a driver sends) and an MJPEG broadcaster (default port 9100) that streams the screen as a sequence of JPEG frames with no authentication of any kind. Both are meant to be gated by one setting, USE_IP, exposed internally as FBConfiguration.bindingIPAddress, which restricts which address the server listens on. Set it to 127.0.0.1 and the HTTP server stops answering on the phone's wifi network.
The MJPEG broadcaster did not stop. Pull request #1299's own description states the HTTP server is restricted "to one address through setInterface:," while "the MJPEG screenshots broadcaster never sets FBTCPSocket.interface." With no interface set, the broadcaster's listener is created with nw_listener_create_with_port and binds to every interface, wifi included. The author's reproduction, on an iPhone 16 Pro running iOS 26.6.2 with USE_IP=127.0.0.1: port 8100 refused a connection from the device's wifi address, as expected, while port 9100 accepted it and streamed the screen. The fix applies the same binding address to the broadcaster; when USE_IP is unset, behaviour is unchanged.
| version | date | change | PR |
|---|---|---|---|
| 16.15.0 | 2026-10-12 | adds asAppUnderTest flag to /wda/apps/launch | #1289 |
| 16.15.1 | 2026-10-16 | cleans up an AUT launch abandoned by a deleted session | #1297 |
| 16.15.2 | 2026-10-16 | makes /health a standalone route | #1300 |
| 16.15.3 | 2026-10-16 | binds the MJPEG broadcaster to USE_IP, closing the LAN leak on port 9100 | #1299 |
What phoneshell's own check actually probes
phoneshell has carried a lan-exposure check since before this fix existed, and its docstring states the stakes plainly: "WDA authenticates nothing, so this matters." The function, lan_exposure_check in phoneshell/device.py, reads the phone's wifi address with phone_lan_ip, then opens exactly one socket:
with socket.create_connection((ip, cfg.wda.port), timeout=1.5):
return Check("lan-exposure", False,
f"SECURITY: WebDriverAgent answers on {ip}:{cfg.wda.port} to ANYONE on this wifi...")
cfg.wda.port is 8100. There is no second call anywhere in the function, or anywhere else in the codebase, against cfg.wda.mjpeg_port (9100). phoneshell's own PortForward class explicitly treats the two as a pair it tunnels together, with pairs: tuple[tuple[int, int], ...] = ((8100, 8100), (9100, 9100)) as its default, and launch_wda sets both USE_PORT and MJPEG_SERVER_PORT in the same environment dictionary before passing USE_IP alongside them. The code clearly treats both ports as the same trust boundary. The audit does not.
What it means for someone running a bridge over wifi
phoneshell's default transport binds WDA to loopback and reaches it over a cable-only usbmux tunnel, which this bug never touched regardless of what the broadcaster bound to underneath. The exposure is real for the configurations the LAN check exists to catch: a runner launched with USE_IP set but reached over wifi (phoneshell up --wifi), or any deployment that trusted the "good" verdict to cover the bridge's full surface rather than one port of it. For those, every WebDriverAgent build before 16.15.3 streamed the phone's screen, unauthenticated, to anyone on the same wifi network, while phoneshell's own doctor command reported nothing wrong.
Our judgement, not WebDriverAgent's: a check named lan-exposure that audits one of two ports a tool explicitly pairs and forwards together is not auditing the exposure, it is auditing a sample of it. The upstream fix closes this specific bug. It does not make phoneshell's check correct in general, because the check still has no logic that would catch the next service WDA adds on a new port, or any other port phoneshell itself forwards.
Check it yourself
git clone https://github.com/Blomega/phoneshell && cd phoneshell
# the check probes exactly one port
grep -n "cfg.wda.port\|cfg.wda.mjpeg_port\|create_connection" phoneshell/device.py
# the two ports phoneshell forwards together for the same tunnel
grep -n "pairs: tuple" phoneshell/device.py
# from a second machine on the same wifi as the phone, with a pre-16.15.3
# runner and USE_IP set, test both ports yourself:
nc -zv <phone-wifi-ip> 8100 # refused, if USE_IP is honoured
nc -zv <phone-wifi-ip> 9100 # answered, on any WebDriverAgent build before 16.15.3
What would prove this wrong
The claim is narrow and checkable: as of 20 October 2026, lan_exposure_check contains no socket probe against cfg.wda.mjpeg_port, anywhere in phoneshell/device.py or the rest of the repository. If a maintainer adds one, or a reader finds a probe we missed, this finding is wrong and we will say so in public. We will recheck the repository on 3 November 2026 and publish whichever is true.
Sources
- appium/WebDriverAgent, pull request #1299, "bind the MJPEG server to the USE_IP address", merged 16 October 2026. github.com/appium/WebDriverAgent/pull/1299
- appium/WebDriverAgent, CHANGELOG.md, versions 16.15.0 through 16.15.3. github.com/appium/WebDriverAgent/blob/master/CHANGELOG.md
- registry.npmjs.org, appium-webdriveragent publish timestamps, accessed 20 October 2026. registry.npmjs.org/appium-webdriveragent
- Blomega/phoneshell,
phoneshell/device.py:lan_exposure_check,launch_wda,PortForward. github.com/Blomega/phoneshell