Dynoxide 0.11.3: closing a Windows port hijack
Dynoxide 0.11.3 is a small one, and for once there isn't a conformance fix in it. It's about who is allowed to bind the ports.
An issue had been sitting on the tracker for a while: investigate SO_EXCLUSIVEADDRUSE on Windows, for parity with the TIME_WAIT bypass dynoxide already had on Unix. I finally picked it up this week, and the investigation came back inside out. The parity already existed. The actual gap was something else.
The wrong assumption
On Unix, dynoxide sets SO_REUSEADDR on its listener so a restart isn't blocked by the previous run's TIME_WAIT sockets. On Windows it set nothing, because SO_REUSEADDR means something nastier there - it lets a second socket bind straight over a live one. Setting nothing felt like the safe default.
It isn't. Microsoft's bind tables show that a Windows socket bound with no options can be taken over by another process running as the same user, if that process asks for SO_REUSEADDR. Meanwhile the TIME_WAIT rebind I assumed Windows needed an option for works out of the box - a plain bind sails past sockets that would block a Unix bind entirely.
So the option the issue pointed at was the right one, for the opposite reason. SO_EXCLUSIVEADDRUSE makes a takeover bind fail instead of succeed, and Microsoft's guidance is blunt about it: every server should set it. 0.11.3 sets it on both listeners - the DynamoDB HTTP port and the MCP transport.
On severity: this needs an attacker already running as your user, and a process running as you can read the MCP token file off disk anyway. It's hardening for a local dev tool, not a drop-everything advisory. But the MCP port carries bearer tokens, so it shouldn't be the easy target either.
The tests had never run on Windows
There was a catch to check before shipping. MSDN warns that an exclusively bound port can refuse to rebind while connections from the previous instance are still settling. For a dev server that gets restarted constantly, trading a hijack hole for broken restarts would be a bad deal.
I couldn't answer that from a Mac, and it turned out CI couldn't either. cargo test had only ever run on Ubuntu; the Windows job built binaries and never executed a single test. So this release also adds a Windows test job, plus a regression test that walks a real connection into TIME_WAIT and immediately rebinds the port. It passes on Windows - restarts survive the new option.
The first ever Windows test run also failed two existing tests, both quiet Unix assumptions: one used a directory as a stand-in for an unreadable file, the other asserted a forward-slash path substring. Both now test the same intent portably.
0.11.3 is out everywhere dynoxide ships:
- npm:
npm install --save-dev dynoxide, thennpx dynoxide - Homebrew:
brew install nubo-db/tap/dynoxide - Cargo:
cargo install dynoxide-rs(crates.io, docs.rs) - Docker:
ghcr.io/nubo-db/dynoxide, mirrored best-effort to Docker Hub (nubodb/dynoxide) and ECR Public - GitHub: source and pre-built binaries, plus the
nubo-db/dynoxide/actionGitHub Action for CI
If you're on Windows and a restart ever refuses the port, that's a bug worth filing - there's now a test claiming it can't happen, and I'd like to keep that test honest.