From 06955f6210513d4c62ccc1bcc4fc23acbc4f99a9 Mon Sep 17 00:00:00 2001 From: nistee <52573120+niStee@users.noreply.github.com> Date: Sun, 17 May 2026 00:01:28 +0200 Subject: [PATCH 1/2] fix(security): bind server to localhost (127.0.0.1) --- config/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.yaml b/config/default.yaml index 35fefcb0..abe6b643 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -27,7 +27,7 @@ auth: oauth_auth_endpoint: https://auth.openai.com/oauth/authorize oauth_token_endpoint: https://auth.openai.com/oauth/token server: - host: "::" + host: "127.0.0.1" port: 8080 proxy_api_key: null trust_proxy: false From db5f8d6175d5a90b5cb2f8b2b873a1b140597669 Mon Sep 17 00:00:00 2001 From: icebear0828 Date: Wed, 20 May 2026 20:06:51 -0700 Subject: [PATCH 2/2] fix: keep host binding safe across local and docker --- Dockerfile | 5 +++++ README.md | 7 +++++-- docker-compose.yml | 5 ++++- src/config-loader.ts | 7 +++++++ src/config-schema.ts | 2 +- tests/unit/config-loader.test.ts | 16 ++++++++++++++++ tests/unit/config-schema.test.ts | 2 +- 7 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 78e7c50e..62ea9f4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,11 @@ RUN npm ci && npm run build # ── Stage 2: Application ──────────────────────────────────────────── FROM node:20-slim +# The checked-in default is loopback-only for local source installs. Containers +# need to listen on all interfaces inside the network namespace so published +# ports and Docker health checks can reach the service. +ENV CODEX_PROXY_HOST=0.0.0.0 + # curl: needed by full-update.ts # unzip: needed by full-update.ts to extract Codex.app # gosu: needed by entrypoint to drop from root to node user diff --git a/README.md b/README.md index f68ee038..e5df7a43 100644 --- a/README.md +++ b/README.md @@ -572,14 +572,16 @@ model: ### 局域网访问 -源码/容器默认配置监听 `::`(IPv6 unspecified,通常也覆盖本机访问);Electron 启动时会传入 `127.0.0.1`,除非 `data/local.yaml` 显式覆盖。建议需要仅本机访问时写入: +源码默认配置仅监听 `127.0.0.1`;Electron 也会传入 `127.0.0.1`,除非 `data/local.yaml` 显式覆盖。Docker 镜像会通过 `CODEX_PROXY_HOST=0.0.0.0` 在容器内监听所有接口,`docker-compose.yml` 默认仍只把宿主机端口绑定到 `127.0.0.1`。 + +需要仅本机访问时写入: ```yaml server: host: "127.0.0.1" ``` -如需局域网内其他设备访问,在 `data/local.yaml` 中添加: +如需局域网内其他设备访问,在 `data/local.yaml` 中添加,并把 `docker-compose.yml` 的端口映射从 `127.0.0.1:${PORT:-8080}:8080` 改成 `${PORT:-8080}:8080`: ```yaml server: @@ -711,6 +713,7 @@ curl -N http://localhost:8080/official-agent/threads/{threadId}/turns \ | 环境变量 | 覆盖配置 | |---------|---------| | `PORT` | `server.port` | +| `CODEX_PROXY_HOST` | `server.host`(仅当 `data/local.yaml` 未显式设置 `server.host` 时生效) | | `CODEX_PLATFORM` | `client.platform` | | `CODEX_ARCH` | `client.arch` | | `HTTPS_PROXY` | `tls.proxy_url` | diff --git a/docker-compose.yml b/docker-compose.yml index ae5440f5..9a2eb8bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,9 @@ services: extra_hosts: - "host.docker.internal:host-gateway" ports: - - "${PORT:-8080}:8080" + # Loopback-only on the host by default; change to "${PORT:-8080}:8080" + # if you intentionally want LAN access. + - "127.0.0.1:${PORT:-8080}:8080" - "1455:1455" # Optional Ollama-compatible bridge. Enable ollama.enabled and use # ollama.host=0.0.0.0 inside the container before uncommenting. @@ -22,6 +24,7 @@ services: environment: - NODE_ENV=production - PORT=8080 + - CODEX_PROXY_HOST=0.0.0.0 # -- Automatic updates (uncomment to enable) -- # watchtower: diff --git a/src/config-loader.ts b/src/config-loader.ts index 9b76cf6c..cefa13c7 100644 --- a/src/config-loader.ts +++ b/src/config-loader.ts @@ -193,6 +193,13 @@ export function applyEnvOverrides( (raw.server as Record).port = parsed; } } + const serverHostEnv = process.env.CODEX_PROXY_HOST?.trim(); + const localServer = localOverrides?.server as Record | undefined; + const localHasServerHost = localServer !== undefined && "host" in localServer; + if (serverHostEnv && !localHasServerHost) { + if (!raw.server) raw.server = {}; + (raw.server as Record).host = serverHostEnv; + } const ollamaEnabledEnv = process.env.OLLAMA_BRIDGE_ENABLED?.trim().toLowerCase(); const ollamaHostEnv = process.env.OLLAMA_BRIDGE_HOST?.trim(); const ollamaPortEnv = process.env.OLLAMA_BRIDGE_PORT?.trim(); diff --git a/src/config-schema.ts b/src/config-schema.ts index 213db50f..7436dd3e 100644 --- a/src/config-schema.ts +++ b/src/config-schema.ts @@ -104,7 +104,7 @@ export const ConfigSchema = z.object({ oauth_token_endpoint: z.string().default("https://auth.openai.com/oauth/token"), }), server: z.object({ - host: z.string().default("0.0.0.0"), + host: z.string().default("127.0.0.1"), port: z.number().min(1).max(65535).default(8080), proxy_api_key: z.string().nullable().default(null), trust_proxy: z.boolean().default(false), diff --git a/tests/unit/config-loader.test.ts b/tests/unit/config-loader.test.ts index a97cc70b..9bbffe2f 100644 --- a/tests/unit/config-loader.test.ts +++ b/tests/unit/config-loader.test.ts @@ -320,6 +320,7 @@ describe("applyEnvOverrides", () => { savedEnv.CODEX_JWT_TOKEN = process.env.CODEX_JWT_TOKEN; savedEnv.CODEX_PLATFORM = process.env.CODEX_PLATFORM; savedEnv.CODEX_ARCH = process.env.CODEX_ARCH; + savedEnv.CODEX_PROXY_HOST = process.env.CODEX_PROXY_HOST; savedEnv.PORT = process.env.PORT; savedEnv.HTTPS_PROXY = process.env.HTTPS_PROXY; savedEnv.https_proxy = process.env.https_proxy; @@ -332,6 +333,7 @@ describe("applyEnvOverrides", () => { delete process.env.CODEX_JWT_TOKEN; delete process.env.CODEX_PLATFORM; delete process.env.CODEX_ARCH; + delete process.env.CODEX_PROXY_HOST; delete process.env.PORT; delete process.env.HTTPS_PROXY; delete process.env.https_proxy; @@ -371,6 +373,20 @@ describe("applyEnvOverrides", () => { expect((raw.server as Record).port).toBe(3000); }); + it("applies CODEX_PROXY_HOST when local.yaml has no server.host", () => { + process.env.CODEX_PROXY_HOST = "0.0.0.0"; + const raw = { server: { host: "127.0.0.1" }, auth: {} } as Record; + applyEnvOverrides(raw, null); + expect((raw.server as Record).host).toBe("0.0.0.0"); + }); + + it("does not override explicit local.yaml server.host with CODEX_PROXY_HOST", () => { + process.env.CODEX_PROXY_HOST = "0.0.0.0"; + const raw = { server: { host: "127.0.0.1" }, auth: {} } as Record; + applyEnvOverrides(raw, { server: { host: "127.0.0.1" } }); + expect((raw.server as Record).host).toBe("127.0.0.1"); + }); + it("ignores non-numeric PORT", () => { process.env.PORT = "abc"; const raw = { server: { port: 8080 }, auth: {} } as Record; diff --git a/tests/unit/config-schema.test.ts b/tests/unit/config-schema.test.ts index 7e80dae1..f105a64c 100644 --- a/tests/unit/config-schema.test.ts +++ b/tests/unit/config-schema.test.ts @@ -30,7 +30,7 @@ describe("ConfigSchema", () => { expect(result.api.base_url).toBe("https://chatgpt.com/backend-api"); expect(result.api.timeout_seconds).toBe(60); expect(result.server.port).toBe(8080); - expect(result.server.host).toBe("0.0.0.0"); + expect(result.server.host).toBe("127.0.0.1"); expect(result.server.proxy_api_key).toBeNull(); expect(result.auth.rotation_strategy).toBe("least_used"); expect(result.auth.refresh_concurrency).toBe(2);