{"record":{"id":"a384375b5493f45a","repo":"abhigyanpatwari/GitNexus","slug":"origin-not-allowed","errorCode":"origin_not_allowed","errorMessage":"This endpoint is restricted to trusted browser origins","messagePattern":"This endpoint is restricted to trusted browser origins","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"gitnexus/src/server/middleware.ts","lineNumber":209,"sourceCode":"      return;\n    }\n    try {\n      const parsed = new URL(origin);\n      const { hostname, protocol } = parsed;\n      if (protocol !== 'http:' && protocol !== 'https:') {\n        throw new Error('Unsupported origin protocol');\n      }\n      const matchesBoundHost =\n        hostname === normalizedBoundHost &&\n        (normalizedBoundPort === undefined || effectivePort(parsed) === normalizedBoundPort);\n      if (isLoopbackHostname(hostname) || matchesBoundHost || publicOrigin?.matches(parsed)) {\n        next();\n        return;\n      }\n    } catch {\n      /* malformed origin → reject */\n    }\n    res.status(403).json({\n      error: 'This endpoint is restricted to trusted browser origins',\n      code: 'origin_not_allowed',\n    });\n  };\n}\n\n/**\n * Whether `serve` has any request authentication configured.\n *\n * Nothing can configure it yet: `serve` has no authentication of any kind, and\n * {@link createWriteOriginGuard} passes every request that carries no `Origin`\n * header, so `curl` reaches `POST /api/analyze` and `DELETE /api/repo`\n * unauthenticated. That has been safe only because `serve` bound loopback.\n *\n * So this returns `false` unconditionally, and it is a placeholder on purpose:\n * the `serve` auth change replaces this body, and {@link assertServeAuthForPublicOrigin}\n * and its tests then hold without being rewritten.\n */","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/middleware.ts#L191-L227","documentation":"HTTP 403 (body code 'origin_not_allowed') returned by the requireTrustedOrigin middleware guarding mutating routes (POST /api/analyze and /api/analyze/upload, POST /api/embed, the DELETE endpoints, ...): the request carried an Origin header that is neither a loopback origin (localhost/127.x.x.x/::1), nor the server's bound host with matching port, nor the operator-configured GITNEXUS_PUBLIC_ORIGIN. The guard blocks cross-origin browser writes (CSRF/DNS-rebinding shaped requests); requests without an Origin header — curl, CLI, server-side code — always pass.","triggerScenarios":"A page served from another host/port calling the API (dev server on :3000 against serve bound elsewhere); browsing via a LAN IP or hostname (http://myhost.local:4747) without GITNEXUS_PUBLIC_ORIGIN set; an Origin whose port differs from the bound port; a malformed Origin header (parse failure rejects).","commonSituations":"Exposing serve on 0.0.0.0/LAN and accessing it by IP or mDNS name; reverse proxies rewriting Host/Origin; testing from a phone on the same network; embedded webviews with unexpected origins.","solutions":["Access the UI via a loopback origin (http://localhost:<port>) — always trusted","Set GITNEXUS_PUBLIC_ORIGIN to the exact public origin (scheme+host+port) the UI is served from, then restart serve","Make ports match: via the bound-host path, the Origin port must equal the server's bound port","For non-browser automation, send no Origin header at all (curl and Node fetch don't add one)"],"exampleFix":"# before — LAN access blocked\nGITNEXUS_BIND=0.0.0.0 npx gitnexus serve\n# browsing http://myhost.local:4747 → writes fail 403 origin_not_allowed\n\n# after\nGITNEXUS_BIND=0.0.0.0 GITNEXUS_PUBLIC_ORIGIN=http://myhost.local:4747 npx gitnexus serve","handlingStrategy":"validation","validationCode":"// Browser-side pre-flight: is this origin trusted by the server?\nconst TRUSTED_HOSTS = ['localhost', '127.0.0.1', '::1'];\nfunction originIsTrusted(origin: string, publicOrigin?: string): boolean {\n  try {\n    const u = new URL(origin);\n    return TRUSTED_HOSTS.includes(u.hostname) || u.origin === publicOrigin;\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"On 403 with body.code === 'origin_not_allowed', stop retrying (it is deterministic per origin) and surface a configuration hint: use a loopback origin or set GITNEXUS_PUBLIC_ORIGIN server-side.","preventionTips":["Serve UI and API from one origin when possible","Configure GITNEXUS_PUBLIC_ORIGIN before exposing serve beyond loopback","Match scheme+host+port exactly — the check is strict","Remember CLI/curl calls (no Origin header) are never blocked"],"tags":["http-403","security","csrf","origin","cors","browser","lan"],"backgroundTag":"cross-origin-request-blocked","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}