{"record":{"id":"3d584e37bdd126bf","repo":"abhigyanpatwari/GitNexus","slug":"public-origin-env-is-set-raw-but-gitnexu","errorCode":null,"errorMessage":"${PUBLIC_ORIGIN_ENV} is set (${raw}), but 'gitnexus serve' has no authentication yet. It would admit browser writes from that origin, and requests without an Origin header (curl, any script) already reach POST /api/analyze and DELETE /api/repo unauthenticated — so a reachable deployment would let anyone index and delete repositories. Unset ${PUBLIC_ORIGIN_ENV} and bind loopback (the default), or reach the server through a proxy that authenticates for it.","messagePattern":"(.+?) is set \\((.+?)\\), but 'gitnexus serve' has no authentication yet\\. It would admit browser writes from that origin, and requests without an Origin header \\(curl, any script\\) already reach POST /api/analyze and DELETE /api/repo unauthenticated — so a reachable deployment would let anyone index and delete repositories\\. Unset (.+?) and bind loopback \\(the default\\), or reach the server through a proxy that authenticates for it\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"gitnexus/src/server/middleware.ts","lineNumber":249,"sourceCode":"\n/**\n * Refuse to start when {@link PUBLIC_ORIGIN_ENV} is set and no `serve`\n * authentication is configured.\n *\n * {@link PUBLIC_ORIGIN_ENV} is the setting that makes a public bind usable — it\n * is what admits a non-loopback browser origin to the write routes. Until\n * {@link isServeAuthConfigured} can return `true`, setting it opens the door\n * with nothing behind it, so the door does not open at all. There is\n * deliberately no override flag: an escape hatch is the thing an operator sets\n * once and forgets, which is exactly the state this guards against.\n *\n * @throws when {@link PUBLIC_ORIGIN_ENV} is set without authentication. `serve`\n *   surfaces it as `serve.startFailed` and exits non-zero.\n */\nexport function assertServeAuthForPublicOrigin(): void {\n  const raw = process.env[PUBLIC_ORIGIN_ENV]?.trim();\n  if (!raw || isServeAuthConfigured()) return;\n  throw new Error(\n    `${PUBLIC_ORIGIN_ENV} is set (${raw}), but 'gitnexus serve' has no authentication yet. ` +\n      `It would admit browser writes from that origin, and requests without an Origin header ` +\n      `(curl, any script) already reach POST /api/analyze and DELETE /api/repo unauthenticated — ` +\n      `so a reachable deployment would let anyone index and delete repositories. Unset ` +\n      `${PUBLIC_ORIGIN_ENV} and bind loopback (the default), or reach the server through a proxy ` +\n      `that authenticates for it.`,\n  );\n}\n\n/**\n * Report at startup what {@link createWriteOriginGuard} will admit, so an\n * operator can see it without reproducing a 403. A wildcard bind always warns —\n * gating that on {@link PUBLIC_ORIGIN_ENV} being constructible would diagnose a\n * misconfigured value worse than an absent one.\n */\nexport function logOriginPolicy(boundHost?: string): void {\n  const raw = process.env[PUBLIC_ORIGIN_ENV]?.trim();\n  const publicOrigin = createPublicOriginMatcher(raw);","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/middleware.ts#L231-L267","documentation":"At startup, `gitnexus serve` runs assertServeAuthForPublicOrigin and refuses to start (surfaces as serve.startFailed, exits non-zero) when GITNEXUS_PUBLIC_ORIGIN is set but serve has no authentication — in this version isServeAuthConfigured() returns false unconditionally, so any set value fails. GITNEXUS_PUBLIC_ORIGIN is precisely the setting that admits a non-loopback browser origin to the write routes, and requests without an Origin header (curl, scripts) already reach POST /api/analyze and DELETE /api/repo unauthenticated, so a reachable deployment would let anyone index and delete repositories. There is deliberately no override flag.","triggerScenarios":"Starting `gitnexus serve` (directly, via npm script, or in Docker) with GITNEXUS_PUBLIC_ORIGIN=https://gitnexus.example.com in the environment while no serve auth exists. The throw happens during startup, before the listener opens — e.g. docker-compose with `-e GITNEXUS_PUBLIC_ORIGIN=...`, a systemd unit, or a .env exported into the shell.","commonSituations":"Trying to use the web UI from another machine by setting the public-origin env; copy-pasting a cloud deployment guide written for a future version that has serve auth; CI smoke tests that set every documented env var; leftover env from an experiment. The variable is also ignored-with-warning when malformed, but set-and-valid is the case that hard-fails.","solutions":["Unset GITNEXUS_PUBLIC_ORIGIN and keep the default loopback bind; reach the server from the same machine or via SSH tunnel (ssh -L 4747:localhost:4747)","If you must expose it publicly, put an authenticating reverse proxy (oauth2-proxy, mTLS gateway, etc.) in front and access it through the proxy — do not set the env var on the bare server","Check for stray definitions: `env | grep GITNEXUS_PUBLIC_ORIGIN`, docker-compose.yml environment blocks, systemd Environment= lines, .env files loaded by wrappers","If you are building/authoring a fork with serve auth implemented, make isServeAuthConfigured() return true for your configured auth — the guard then passes by design"],"exampleFix":"# before (docker-compose.yml)\nenvironment:\n  - GITNEXUS_PUBLIC_ORIGIN=https://gitnexus.example.com   # serve exits: serve.startFailed\n# after — loopback + tunnel, no public origin\nenvironment:\n  - GITNEXUS_HOME=/data/gitnexus\n# access remotely via: ssh -L 4747:localhost:4747 host","handlingStrategy":"validation","validationCode":"// Before spawning `gitnexus serve`, check the guard's precondition yourself:\nconst PUBLIC_ORIGIN_ENV = 'GITNEXUS_PUBLIC_ORIGIN';\nif (process.env[PUBLIC_ORIGIN_ENV]?.trim()) {\n  throw new Error(\n    `${PUBLIC_ORIGIN_ENV} must not be set: serve has no authentication; ` +\n    `bind loopback or front it with an authenticating proxy`,\n  );\n}","typeGuard":null,"tryCatchPattern":"// If you programmatically start serve (e.g. in tests/containers):\ntry {\n  await startServe();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('has no authentication yet')) {\n    delete process.env.GITNEXUS_PUBLIC_ORIGIN; // fall back to safe loopback mode\n    await startServe();\n  } else throw err;\n}","preventionTips":["Audit deployment environments (docker-compose, systemd, .env, CI matrices) for stray GITNEXUS_PUBLIC_ORIGIN definitions","Access remote serve instances through SSH tunnels or an authenticating reverse proxy instead of public binds","Treat a serve.startFailed exit as a hard stop — there is intentionally no override flag, so fix the environment rather than suppressing the error"],"tags":["serve","startup-failure","authentication","public-origin","configuration","security-guard"],"backgroundTag":"server-refuses-start-without-auth","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}