Compare commits

...
Author SHA1 Message Date
abhigyanpatwariandClaude Sonnet 4.6 a0ff60250d fix(serve): use localhost as default host instead of ::
Per reviewer feedback, bind to 'localhost' and let the OS decide
IPv4 vs IPv6 resolution, rather than hardcoding '::' (dual-stack).

Also updates the stale 127.0.0.1 comment in api.ts and removes a
redundant ternary in the CORS origin callback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:11:06 +05:30
Abhigyan PatwariandClaude Opus 4.6 6b86b10a97 fix(server): bind to dual-stack '::' so localhost works on IPv6-first systems
The serve command defaulted to 127.0.0.1 (IPv4 only). On systems where
'localhost' resolves to ::1 (IPv6 loopback), the server was unreachable
via localhost — browsers showed CORS errors because the request never
reached the server (no CORS headers returned = browser blocks it).

Change default host to '::' (dual-stack), which accepts connections on
both 127.0.0.1 and ::1. The console now shows 'http://localhost:PORT'
instead of 'http://:::PORT'. Users can still restrict with --host.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:19:53 +05:30
Abhigyan PatwariandClaude Opus 4.6 cd62bc64a0 fix(server): return clean CORS rejection instead of 500 error
When an unknown origin made a request, the CORS middleware called
callback(new Error('Not allowed by CORS')) which bubbled into Express's
default error handler, returning a 500 Internal Server Error with a stack
trace. The browser showed a confusing CORS + 500 error instead of a clean
CORS block.

Fix: pass `false` to the cors callback for rejected origins. This omits
the Access-Control-Allow-Origin header (so the browser blocks the request)
without triggering a server error.

Closes #640

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 07:22:48 +05:30
2 changed files with 11 additions and 8 deletions
+4 -1
View File
@@ -14,7 +14,10 @@ process.on('unhandledRejection', (reason: any) => {
export const serveCommand = async (options?: { port?: string; host?: string }) => {
const port = Number(options?.port ?? 4747);
const host = options?.host ?? '127.0.0.1';
// Default to 'localhost' so the OS decides whether to bind to 127.0.0.1 or
// ::1 based on system configuration, avoiding spurious CORS errors when the
// hosted frontend at gitnexus.vercel.app connects to localhost.
const host = options?.host ?? 'localhost';
try {
await createServer(port, host);
+7 -7
View File
@@ -4,7 +4,7 @@
* REST API for browser-based clients to query the local .gitnexus/ index.
* Also hosts the MCP server over StreamableHTTP for remote AI tool access.
*
* Security: binds to 127.0.0.1 by default (use --host to override).
* Security: binds to localhost by default (use --host to override).
* CORS is restricted to localhost, private/LAN networks, and the deployed site.
*/
@@ -276,14 +276,13 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
// CORS: allow localhost, private/LAN networks, and the deployed site.
// Non-browser requests (curl, server-to-server) have no origin and are allowed.
// Disallowed origins get the response without Access-Control-Allow-Origin,
// so the browser blocks it. We pass `false` instead of throwing an Error to
// avoid crashing into Express's default error handler (which returned 500).
app.use(
cors({
origin: (origin, callback) => {
if (isAllowedOrigin(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
callback(null, isAllowedOrigin(origin));
},
}),
);
@@ -1248,7 +1247,8 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
// to the caller instead of crashing with an unhandled 'error' event.
await new Promise<void>((resolve, reject) => {
const server = app.listen(port, host, () => {
console.log(`GitNexus server running on http://${host}:${port}`);
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
console.log(`GitNexus server running on http://${displayHost}:${port}`);
resolve();
});
server.on('error', (err) => reject(err));