Skip to content

fix: bind the HTTP server to the loopback - #41

Merged
antoinevalentinHA merged 1 commit into
masterfrom
fix/bind-http-server-loopback
Sep 5, 2026
Merged

fix: bind the HTTP server to the loopback#41
antoinevalentinHA merged 1 commit into
masterfrom
fix/bind-http-server-loopback

Conversation

@antoinevalentinHA

Copy link
Copy Markdown
Owner

The server was started with listen(int), whose default host is 0.0.0.0. An API with no authentication was therefore reachable from every machine on the same network segment, which could read, write and delete in the user's library while STUdio ran. The launcher opens a browser on localhost, so nothing told the user anything else was listening.

CORS does not cover this. It is enforced by browsers, on requests issued by a page, and says nothing to curl or a script on another machine.

Nothing is lost

Remote use was never possible. The web UI is served by this same server and addresses it as http://localhost:8080, hardcoded throughout the frontend — API calls, event bus, even the translation files. A browser on another machine would receive the page and then send every request to its own loopback. The wider binding exposed the API without ever making the application usable from elsewhere.

The address is fixed, not configurable. An override would keep the exposure reachable to buy back a capability that does not work; the day remote access is genuinely wanted it will mean changing the frontend's addresses too, and that is when the question should be reopened.

The test asserts the consequence, not the value

HttpServerBindingTest starts a real server on the address production uses, then opens real sockets.

Case What it requires
loopback reachable a complete request answered 200 — an open port proves only that a socket was accepted; this proves the application behind it still serves
non-loopback refused an IOException when aiming at a routable IPv4 address of this same machine

The refusal case skips where the machine has no such address: one that cannot set up the situation has established nothing about it. The nominal case runs everywhere and carries equal weight — a server bound to nothing at all would pass every refusal.

Verified red twice, the second time against the final test: the connection really was established from this machine's LAN address, 192.168.1.94. Not an inference.

A trap that the red proof caught

The address was first written as a static final String. The re-proof failed to reproduce — the test stayed green while production had been set back to 0.0.0.0.

A compile-time constant is inlined into whatever reads it, so the test held a frozen copy and never read production at all. The first red proof had passed only because the test happened to have been compiled against the old value. Anyone reopening the binding later would have seen the test stay green.

It is now a method, which the compiler cannot inline, and the reason is written in the javadoc so it is not converted back.

Two side effects of the new test, handled here

  • The request-and-answer exchange lets the server finish with the connection before teardown closes Vert.x, which removes a logged Netty stack trace. Zero warnings and zero exceptions in the log afterwards.
  • *.log is now ignored. No test initialised log4j2 before, so a run left studio-latest.log and a dated roll-over untracked in the tree.

Counts and scope

web-ui 81 → 83, Java standard 282 → 284, JavaScript 57 unchanged and not re-run — no JS file was touched. TESTING.md and both READMEs follow.

No markdown in the repository claimed the server listened on all interfaces, so no documented statement becomes false. The published release notes do say it, and are left alone: they describe the 0.4.3-fork.1 artifact, for which they remain accurate.

Untouched: the hardcoded port 8080, the frontend's addresses, CORS, authentication, evergreen.

What this does not do

It narrows the exposure of CVE-2023-24815 in vertx-web 3.9 (StaticHandler path traversal on Windows) to the local machine. It does not fix it, and that check is still outstanding.

🤖 Generated with Claude Code

The server was started with listen(int), whose default host is 0.0.0.0.
An API with no authentication was therefore reachable from every machine
on the same network segment, which could read, write and delete in the
user's library while STUdio ran. The CORS filter does not cover that:
CORS is enforced by browsers on requests issued by a page, and says
nothing to curl or a script on another machine.

Nothing is lost, because remote use was never possible. The web UI is
served by this same server and addresses it as http://localhost:8080,
hardcoded throughout the frontend, so a browser on another machine would
receive the page and then send every request to its own loopback. The
wider binding exposed the API without ever making the application usable
from elsewhere.

The address is fixed rather than configurable. An override would keep the
exposure reachable to buy back a capability that does not work, and the
day remote access is genuinely wanted it will mean changing the
frontend's addresses too.

HttpServerBindingTest asserts the consequence rather than the value: it
starts a real server on the address production uses, then opens real
sockets. The loopback must complete a request and answer 200 — an open
port proves only that a socket was accepted — and a routable address of
this same machine must be refused. That case skips where the machine has
no such address, since a machine that cannot set up the situation
establishes nothing about it. Verified red twice, the second time against
the final test: the connection really was established from this machine's
LAN address.

The address is a method, not a static final String. A compile-time
constant is inlined into whatever reads it, so a test holding an inlined
copy would keep binding to the address it was compiled against and stay
green while production had been reopened. The first red proof passed only
because the test happened to have been compiled against the old value.

Two side effects of the new test are handled here rather than left
behind: the request-and-answer exchange lets the server finish with the
connection before teardown closes Vert.x, which removes a logged Netty
stack trace; and *.log is ignored, since no test initialised log4j2
before and a run now left untracked files in the tree.

Test counts move to 284 Java, 57 JavaScript unchanged. This narrows the
exposure of CVE-2023-24815 in vertx-web 3.9 to the local machine; it does
not fix it, and that check is still outstanding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@antoinevalentinHA
antoinevalentinHA merged commit 59c336c into master Sep 5, 2026
3 checks passed
@antoinevalentinHA
antoinevalentinHA deleted the fix/bind-http-server-loopback branch September 5, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant