fix: bind the HTTP server to the loopback - #41
Merged
Conversation
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>
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The server was started with
listen(int), whose default host is0.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 onlocalhost, 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
curlor 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
HttpServerBindingTeststarts a real server on the address production uses, then opens real sockets.IOExceptionwhen aiming at a routable IPv4 address of this same machineThe 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 to0.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
*.logis now ignored. No test initialised log4j2 before, so a run leftstudio-latest.logand a dated roll-over untracked in the tree.Counts and scope
web-ui81 → 83, Java standard 282 → 284, JavaScript 57 unchanged and not re-run — no JS file was touched.TESTING.mdand 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.1artifact, 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-web3.9 (StaticHandlerpath traversal on Windows) to the local machine. It does not fix it, and that check is still outstanding.🤖 Generated with Claude Code