Bind the loopback by default, make host and port configurable - #6
Conversation
f47b592 to
9b073a8
Compare
The web server called `listen(8080)`, whose Vert.x overload uses the default host `0.0.0.0`. Every interface accepted connections to an API that has no authentication and that lists, downloads, uploads, converts and deletes in the library, and drives a connected device. Observed on a running instance: `ss` reported `*:8080`, and `GET /api/library/infos` answered 200 from the machine's LAN address, disclosing the library path. It now listens on 127.0.0.1 unless `-Dstudio.host` says otherwise, and the port comes from `-Dstudio.port`, following the convention `studio.open` already set. `listen()` was also called with no handler, so a failed bind was discarded and an occupied port left a running process that served nothing and logged nothing. The bind result is now handled, and the browser opens only once the socket is actually bound, at the address it was bound to. The frontend named `localhost:8080` in 20 places, which would have made the port configurable in name only. Those are now relative, so the UI follows whatever origin served it. The event bus is the exception: sockjs-client rejects a URL with no host and no protocol, so it derives an absolute URL from `window.location.origin` instead. Under `yarn start` the CRA dev server cannot proxy that connection, so `.env.development` points it at the Java backend and `proxy` in package.json forwards the rest. Not verified by running the suites: neither Maven nor Yarn is available in the environment this was written in, and no JDK compiler either. Reviewed by reading. The listen behaviour has no test yet; that belongs with the test asked for in the issue this closes. Co-authored-by: kairoh <3878594+kairoh@users.noreply.github.com>
Five cases on the real MainVerticle, deployed with `env=dev` so the mock story teller is used and libusb is never touched, and with every path -- library, temporary directory, both metadata databases -- pointed at a temporary folder. The official database matters in particular: when its file is missing the service falls back to fetching it over the network, and a test that quietly downloads a database is a test that fails on a train. What they pin: the loopback is accepted, every other interface is refused, `studio.port` is honoured, `studio.host` can still widen the binding, and a port already in use leaves its occupant alone without taking the process down. Checked against the previous behaviour rather than assumed: restoring the `0.0.0.0` default makes `refusesNonLoopback` fail with "the server should not be reachable on <lan address>: expected <false> but was <true>". The other four pass either way, which is what they are for -- they hold the surrounding behaviour still while the default changes. Not asserted, and said so in the class javadoc: that a failed bind is logged (the message is not a contract), and that the browser is not opened (it goes through java.awt.Desktop, which has no seam here). Full suite: 287 tests, 0 failures, 39 skipped on Linux -- 282 before these five. Run on Temurin 11, the version CI uses. Windows is not covered locally; CI is what will report it.
9b073a8 to
76b3c1e
Compare
|
I merged antoinevalentinHA#41 fifteen minutes after you opened this, and saw it afterwards. Timing, not disregard — this is the better piece of work on several counts. Three things you found that mine did not, all verified on my master before writing this:
Your Your frontend fix invalidates the argument I published on #1. I wrote there that a setting would "buy back a capability that does not work", because the frontend hardcoded The decision still stands, but it now rests on a different and narrower ground: STUdio is a local desktop tool with no authentication at all, so I do not want it to offer a supported way to expose that API on a network. Loopback-only, no override. What would change my mind is authentication, not configurability — if the API is ever safe to expose, That is a judgement about network architecture, and I would rather say plainly that it is one, not dress it up as the only defensible reading of your PR. On #3, and this is the part that matters most. Your issue is what made me go and check, so credit where it is due — but the result is not what either of us expected. The strict attribution is wrong: GHSA-53jx-vvf9-4x38 declares It is not. So: your citation is inexact, your instinct was right, and the behaviour is confirmed by reproduction rather than by reading. I would have concluded "out of range, probably fine" from the source. The test is what settled it. Two consequences I want to state rather than leave implied:
This deserves its own correction, independent of the binding. My inclination is something small and targeted — refusing paths carrying a backslash or its encodings before they reach If you want this integrated: rebase on |
|
Thanks — and the correction on the CVE is taken. I cited an advisory without checking whether its mechanism existed in 3.9.0, and it does not. "Out of range, probably fine" was the reading the source supported; you got the real answer by building a probe. I will cite ranges more carefully. Opinion only on this one. I am not asking to write the fix — you have the reproduction and the Windows machine, and splitting that across two people would be worse. What follows is review, and one place where I think your plan needs a small change. I reproduced the mechanism independently, and it is slightly worse than "backslashes survive"Calling So the normaliser is asymmetric about percent-encoding: it decodes Where I would differ from your planYou proposed refusing paths carrying a backslash or its encodings before What I would assert instead, on the normalised path, before it reaches
Anything else is refused. That rejects One case worth adding to your Windows probe either way: On the two consequences you statedBoth correct, and I want to underline the second. The loopback bind is not a fix, agreed — it narrows who can reach the defect, not the defect. The published That is a judgement call about disclosure that is yours, not mine, so I will only say what I would weigh: it is a pre-release, the README says plainly that it is work in progress, and the reach you measured is application resources and Not a fix, but worth knowing
Happy to review whatever you write, and to test it on Linux — where I can at least confirm the allowlist does not break any real asset path in the bundle. The Windows half only you can do. |
Closes #1.
What this changes
MainVerticlecalledlisten(8080), whose Vert.x overload uses the default host0.0.0.0. Every interface accepted connections to an API that has no authentication and that lists, downloads, uploads, converts and deletes in the library, and drives a connected device.Observed on a running instance before the change, not inferred from the source:
It now listens on
127.0.0.1unless-Dstudio.hostsays otherwise, and the port comes from-Dstudio.port, following the conventionstudio.openalready set.listen()was also called with no handler, so a failed bind was discarded and an occupied port left a running process that served nothing and logged nothing. The bind result is now handled, and the browser opens only once the socket is bound, at the address it was bound to.The frontend named
localhost:8080in 20 places, which would have made the port configurable in name only. Those are now relative. The event bus is the exception: sockjs-client rejects a URL with no host and no protocol (sockjs.js, "The URL '...' is invalid"), so it derives an absolute URL fromwindow.location.origin. Underyarn startthe CRA dev server cannot proxy that connection, so.env.developmentpoints it at the Java backend andproxyinpackage.jsonforwards the rest.Attribution
Making the host and port configurable, and deriving the CORS pattern and the browser URL from them, comes from @kairoh's fork — commit
74f53cc, "Configurable listen host and port" (2 April 2022), which predates that fork's move to Quarkus and so applied to the same Vert.x code this fork still runs. Both projects are MPL-2.0.Credited in three places: a
Co-authored-bytrailer on the commit, a "Code from other forks" section in the README, and a comment above the code itself.That commit is credited for what it did and not for more: it keeps
listen(port), which still accepts connections on every interface. Binding to the loopback and reporting a failed bind are not from it.Tests
ServerBindingTest, five cases on the realMainVerticle, deployed withenv=devso the mock story teller is used and libusb is never touched, and with every path pointed at a temporary folder — including both metadata databases, since a missing official database makes the service fetch it over the network.They pin: the loopback is accepted, every other interface is refused,
studio.portis honoured,studio.hostcan still widen the binding, and a port already in use leaves its occupant alone.Checked against the previous behaviour rather than assumed — restoring the
0.0.0.0default makes one fail:Full suite locally on Temurin 11: 287 tests, 0 failures, 39 skipped — 282 before these five.
git diff --exit-codeclean.Not verified locally
react-scripts3.0.1; this was written on Node 24.App.js,i18n.jsand the threeservices/files are touched, sopacks.test.js,eventBusChannel.test.jsandaddFromLibrary.test.jswant a run.mvn packagehas not been exercised with the relative URLs.Unrelated finding
The project does not declare
project.build.sourceEncoding, so compilation uses the platform encoding while non-ASCII already appears in main sources. It works on today's runners by luck of their defaults. Belongs with #5; I kept an ASCII hyphen in the new log message rather than add to the bet.