abhigyanpatwari/GitNexus · critical · Error
${PUBLIC_ORIGIN_ENV} is set (${raw}), but 'gitnexus serve' h
Error message
${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. What it means
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.
Source
Thrown at gitnexus/src/server/middleware.ts:249
/**
* Refuse to start when {@link PUBLIC_ORIGIN_ENV} is set and no `serve`
* authentication is configured.
*
* {@link PUBLIC_ORIGIN_ENV} is the setting that makes a public bind usable — it
* is what admits a non-loopback browser origin to the write routes. Until
* {@link isServeAuthConfigured} can return `true`, setting it opens the door
* with nothing behind it, so the door does not open at all. There is
* deliberately no override flag: an escape hatch is the thing an operator sets
* once and forgets, which is exactly the state this guards against.
*
* @throws when {@link PUBLIC_ORIGIN_ENV} is set without authentication. `serve`
* surfaces it as `serve.startFailed` and exits non-zero.
*/
export function assertServeAuthForPublicOrigin(): void {
const raw = process.env[PUBLIC_ORIGIN_ENV]?.trim();
if (!raw || isServeAuthConfigured()) return;
throw new Error(
`${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.`,
);
}
/**
* Report at startup what {@link createWriteOriginGuard} will admit, so an
* operator can see it without reproducing a 403. A wildcard bind always warns —
* gating that on {@link PUBLIC_ORIGIN_ENV} being constructible would diagnose a
* misconfigured value worse than an absent one.
*/
export function logOriginPolicy(boundHost?: string): void {
const raw = process.env[PUBLIC_ORIGIN_ENV]?.trim();
const publicOrigin = createPublicOriginMatcher(raw);View on GitHub (pinned to aac7515d2a)
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
Example fix
# before (docker-compose.yml) environment: - GITNEXUS_PUBLIC_ORIGIN=https://gitnexus.example.com # serve exits: serve.startFailed # after — loopback + tunnel, no public origin environment: - GITNEXUS_HOME=/data/gitnexus # access remotely via: ssh -L 4747:localhost:4747 host
Defensive patterns
Strategy: validation
Validate before calling
// Before spawning `gitnexus serve`, check the guard's precondition yourself:
const PUBLIC_ORIGIN_ENV = 'GITNEXUS_PUBLIC_ORIGIN';
if (process.env[PUBLIC_ORIGIN_ENV]?.trim()) {
throw new Error(
`${PUBLIC_ORIGIN_ENV} must not be set: serve has no authentication; ` +
`bind loopback or front it with an authenticating proxy`,
);
} Try / catch
// If you programmatically start serve (e.g. in tests/containers):
try {
await startServe();
} catch (err) {
if (err instanceof Error && err.message.includes('has no authentication yet')) {
delete process.env.GITNEXUS_PUBLIC_ORIGIN; // fall back to safe loopback mode
await startServe();
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Clone target must be a subdirectory of ${CLONE_ROOT}
- ${name} must be a positive integer, got "${raw}"
- ${name} must be a positive integer <= ${max}, got "${raw}"
- ${name} must be a non-negative integer, got "${raw}"
- GITNEXUS_EMBEDDING_DIMS must be a positive integer, got "${r
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/3d584e37bdd126bf.
Report an issue: GitHub.