anomalyco / anomalyco/opencode
OAuth callback servers bind 0.0.0.0 instead of loopback (codex, digitalocean)
@kitlangton is already working on this.
Since Aug 8, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Summary
Two OAuth callback servers call listen(port, cb) without a hostname. Node defaults to 0.0.0.0, so the callback port is bound on every interface (LAN/VPN/public) for the duration of the login flow, instead of loopback only.
The same code base already binds loopback correctly in two other places, so this looks like an oversight rather than a deliberate choice.
Affected
| File | Line | Current |
|---|---|---|
packages/opencode/src/plugin/openai/codex.ts |
220 | oauthServer!.listen(OAUTH_PORT, () => { |
packages/opencode/src/plugin/digitalocean.ts |
129 | oauthServer!.listen(OAUTH_PORT, () => { |
Already correct elsewhere (precedent for the fix)
| File | Line | Current |
|---|---|---|
packages/opencode/src/mcp/oauth-callback.ts |
126 | server!.listen(currentPort, OAUTH_CALLBACK_HOST, () => { (const OAUTH_CALLBACK_HOST = "127.0.0.1") |
packages/opencode/src/plugin/snowflake-cortex.ts |
216 | oauthServer!.listen(0, OAUTH_CALLBACK_HOST, () => { |
Impact
The callback server only ever needs to accept a redirect from the local browser, so there is no reason for it to be reachable off-host. While the login window is open:
- anyone on the same network segment can reach
http://<host-ip>:1455(codex) /:1456(digitalocean) and submit a craftedcode/stateto the callback endpoint; - on Windows it also triggers a "allow this app on public networks" firewall prompt, which is a confusing prompt to show during a login flow.
Reproduction
Verified on a downstream fork carrying these two call sites (behaviour is identical since the lines are unchanged):
- Start the OAuth flow so the callback server is listening.
- From the same machine, connect to the host's non-loopback IPv4 address on that port — the connection is accepted.
- After adding the loopback hostname, the same connection is refused, while
127.0.0.1keeps working and the browser redirect still completes.
Suggested fix
- oauthServer!.listen(OAUTH_PORT, () => {
+ oauthServer!.listen(OAUTH_PORT, "127.0.0.1", () => {
…or introduce a shared OAUTH_CALLBACK_HOST constant to match mcp/oauth-callback.ts.
One caveat for whoever fixes it
Do not change the redirect_uri literals in these two plugins. They are http://localhost:<port>/auth/callback and must stay byte-identical to what is registered on the provider side, otherwise the flow fails with redirect_uri mismatch.
Binding 127.0.0.1 while the redirect URI still says localhost is fine in practice: localhost may resolve to ::1 first, but browsers (and fetch) fall back to 127.0.0.1 via Happy Eyeballs. Verified locally — fetch("http://localhost:<port>") returns 200 with the server bound to 127.0.0.1 only.
I'm happy to open a PR with the two-line change if that's welcome.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.