GitNexus/gitnexus-web
Copilot 60752de3e9
fix(ip): Scope write-route origin guard to server's own bound host (#2172)
* Initial plan

* Allow RFC1918 LAN origins in requireLocalhostOrigin

* Harden LAN origin parsing in middleware tests

* Refactor private IPv4 checks into shared server helper

* fix: scope origin guard to server's bound host, fix [::1], guard all write routes

- P1: Replace blanket RFC1918 trust with same-host check — only the server's
  own bound host is allowed (via `createLocalhostOriginGuard(host)`), not
  every device on the LAN.
- P2: Fix dead `::1` branch — compare against `'[::1]'` (with brackets) as
  returned by WHATWG URL parser.
- P3: Update 403 message to "same-host origins" and doc comments.
- Out-of-scope: Add `requireLocalhostOrigin` to `DELETE /api/repo`,
  `POST /api/embed`, `DELETE /api/embed/:jobId`, `DELETE /api/analyze/:jobId`.
- Tests: Add [::1] regression, ftp://, null origin, direct private-ip.ts
  unit tests, and createLocalhostOriginGuard bound-host tests.

* fix: cast route params to string when middleware breaks type inference

* chore(autofix): apply prettier + eslint fixes via /autofix command

* fix(test): update rate-limit test regex to match multi-line embed route registration

* fix(ip): normalize boundHost and keep wildcard binds loopback-only

The same-host write guard compared the raw `--host` string to the WHATWG
`URL.hostname` of the Origin, so it silently 403'd legitimate same-host
browser writes for several bind forms:
  - mixed-case hostnames (`MyHost.local` vs lowercased `myhost.local`)
  - non-loopback IPv6 (`fe80::1` vs bracketed `[fe80::1]`, and non-canonical
    forms like `fe80:0:0:0:0:0:0:1` / `::ffff:127.0.0.1`)
  - wildcard binds (`0.0.0.0` / `::`), the CLI-advertised remote-access config

Canonicalize boundHost once at guard construction through `new URL().hostname`
(provably the same form the Origin is parsed into), and treat wildcard binds as
having no single host identity → writes stay loopback-only. We deliberately do
NOT fall through to RFC1918 for wildcards (that would re-open whole-LAN reach).
`createServer` now warns when bound to a wildcard so a remote-access deployment
is not silently write-blocked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ip): tag origin-block 403 with a machine-readable code and surface it in the web client

The write-route Origin guard returned a 403 with only a human-readable
`error` string, so clients could not distinguish an origin block from any
other 403. The hosted web client (gitnexus.vercel.app driving a local
backend) swallowed the resulting failure: the repo delete button caught the
error and only `console.error`'d it, so it silently no-op'd.

- Server: add a stable `code: 'origin_not_allowed'` discriminator to the 403 body.
- Web client: `assertOk` reads `body.code` and maps `origin_not_allowed` to a new
  `BackendError` code `origin_blocked`; `formatBackendError` renders an actionable
  i18n message (en + zh-CN) instead of the generic client message.
- Header: surface the delete failure inline instead of swallowing it to console.

Scope note: the embedding-status badge (EmbeddingStatus.tsx) hides in backend
mode (its `serverBaseUrl` guard), so it is not the surface where an origin-block
embed error appears; a dedicated backend-mode embedding-error surface is deferred
with the broader hosted-UI mode-awareness follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ip): remove unused isValidIpv4Address export

`isValidIpv4Address` had no `src/` consumer — only its own test imported it.
It was a leftover from the reverted RFC1918-middleware approach (the same-host
guard now compares against a canonicalized bound host, not an IPv4 validity
check). Remove the export and its orphaned test block. `parseIpv4Octets` stays
(it feeds `isRfc1918PrivateIpv4`, which CORS `isAllowedOrigin` still uses).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 09:24:03 +01:00
..
e2e fix(web): replace broken Browse-for-folder with upload directory picker (#1850) 2026-06-10 20:50:59 +01:00
src fix(ip): Scope write-route origin guard to server's own bound host (#2172) 2026-06-13 09:24:03 +01:00
test fix(ip): Scope write-route origin guard to server's own bound host (#2172) 2026-06-13 09:24:03 +01:00
.gitignore gitnexus wal cleanup preventing reindexing issue fixed 2026-02-06 06:35:15 +05:30
index.html feat: configure prettier with pre-commit hook (#563) 2026-03-28 14:58:04 +00:00
package-lock.json chore(deps)(deps-dev): bump @vercel/node in /gitnexus-web (#2156) 2026-06-10 21:52:41 +01:00
package.json chore(deps)(deps-dev): bump @vercel/node in /gitnexus-web (#2156) 2026-06-10 21:52:41 +01:00
playwright.config.ts feat: configure prettier with pre-commit hook (#563) 2026-03-28 14:58:04 +00:00
tsconfig.app.json feat: unify web and cli ingestion pipeline (#536) 2026-03-28 14:07:11 +00:00
tsconfig.json feat: configure prettier with pre-commit hook (#563) 2026-03-28 14:58:04 +00:00
tsconfig.node.json feat: merge gitnexus-mcp into gitnexus package - unified CLI+MCP 2026-02-04 01:12:41 +05:30
vercel.json ci(web): use npm ci for deterministic Vercel installs (#1764) 2026-05-22 05:08:42 +01:00
vite.config.ts feat: add docker support (#848) 2026-04-18 08:39:18 +01:00
vitest.config.ts feat(web): Introduce Tree View and Circles View in Web Viewer (#1799) 2026-05-26 18:05:50 +01:00