anomalyco / anomalyco/opencode

OAuth callback servers bind 0.0.0.0 instead of loopback (codex, digitalocean)

Open
#41,255 0 comments 0 reactions 1 assignee View on GitHub

@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 crafted code/state to 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):

  1. Start the OAuth flow so the callback server is listening.
  2. From the same machine, connect to the host's non-loopback IPv4 address on that port — the connection is accepted.
  3. After adding the loopback hostname, the same connection is refused, while 127.0.0.1 keeps 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.