Commit Graph
4 Commits
Author SHA1 Message Date
Colby MchenryandClaude Opus 5.5 5e0c42cb63 fix(security): the sensitive-folder guard refuses links, case variants and \?\ paths (#2929)
The guard that keeps ~/.ssh, ~/.gnupg, ~/.aws, ~/.config and system
folders such as /etc and C:\Windows from being opened as a project
compared the path as typed. A junction or symlink to one of them, a
different capitalization on Windows or macOS (~/.SSH), or a \\?\ or \\.\
spelling on Windows passed, so an MCP projectPath, `codegraph ui` and the
viewer's per-file reads all served what was inside. The MCP gate also
skipped the guard for a path that doesn't exist and then walked up from
it, so ~/.config/no-such-dir reached an index of ~/.config.

validateProjectPath now checks the path as spelled (with a Win32
namespace prefix stripped) and its fs.realpathSync.native form. Both are
compared with the system set and the system folders' own real paths
(macOS /private/etc), and with the home folders as spelled, under the
home's real path and by their own real paths (a linked ~/.ssh). Case is
folded on win32 and darwin. A path that can't be resolved keeps the
lexical check. It stays at one realpath per call: the system folders'
real paths are read once and the home folders' are cached for two
seconds. The MCP handler also checks the root its up-walk lands on.

Validation on Windows 11, with a private home holding a fake token:
- Probe: on main 11 of 16 sensitive spellings were allowed, and the
  token was served through 6 MCP projectPath shapes and 2 viewer roots.
  With the fix all are refused. Controls (an ordinary folder, its
  upper-case and \\?\ spellings, the home itself) are still allowed.
- codegraph ui end to end: main served the token for 4 of 6 spellings
  and started the viewer for a fifth; the fix refuses all 6 at startup.
- Tests: the 9 new tests that run on Windows fail on main's source and
  pass with the fix. The POSIX and macOS cases are gated and were not
  run here.
- Cost: validateProjectPath 48 -> 130 us per call, the viewer's per-file
  resolveProjectFile 700 -> 780 us (medians of 5 rounds, 3 runs).

Reported in PR #1930 by @mixxer; this reimplements the fix independently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:20:10 +00:00
Colby MchenryandClaude Opus 5.5 7cc2edadce fix(ui): the viewer server exits when the command it runs under is killed (#2119)
`codegraph ui` re-execs itself with `--liftoff-only`; the shim the user
started blocks in spawnSync and cannot forward a signal. Killing it by pid —
a process manager, an IDE task, `kill` — left the re-exec'd server serving
its port forever (found during a sweep: servers from hours earlier were still
answering on ports 4801–4808 against deleted indexes, holding their stdout
pipes so the harness that started them never exited). Ctrl+C was unaffected
(the signal reaches the whole process group).

`index`/`init` already guard this with the #277 PPID watchdog. Factor it out
of installCommandSupervision as `watchParent(onLost)` and use it in `ui` with
the same shutdown as SIGINT/SIGTERM (close the index, then the socket). The
liveness watchdog is deliberately not installed for `ui`: a long Steps
computation can legitimately hold the event loop.

Test: POSIX end-to-end through the real relaunch (CODEGRAPH_WASM_RELAUNCHED
unset, 200ms poll) — SIGKILL the shim, the port stops answering; confirmed
failing without the fix.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 04:17:34 +00:00
Colby MchenryandClaude Opus 5.5 2c4d4eb9e4 release: withhold codegraph ui until the viewer launches; hold its changelog entries (#2089)
* feat(cli): withhold `codegraph ui` until the viewer launches (CODEGRAPH_UI=1 opts in)

The viewer has been exercised on Expo apps only, not on API, Laravel,
Spring, Angular or Swift projects, so it stays out of this release. `ui` and
its alias `web` — also as `help ui` or `ui --help` — are refused before any
startup work, and the command is hidden from `--help`, unless CODEGRAPH_UI=1
is set, which keeps it usable for the people testing it. The viewer assets
still ship, so the opt-in works on a released build.

Reverting this commit is the launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(site): say the viewer is not in a release yet

The viewer pages have been live on the docs site since they landed, and
1.6.0 answers `codegraph ui` with "unknown command" (#1816, #1900). Until the
viewer launches, the guide, the CLI reference and the next-steps link say so.

Reverting this commit is part of the launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): hold the viewer's entries for its launch; new Highlights for the fixes release

53 entries that describe `codegraph ui` and its screens move, verbatim, to
docs/viewer-launch-changelog.md along with the launch Highlights. 12 entries
whose graph and `codegraph_explore` half ships now (the router navigation
edges, the cross-tier hops, explore's `when` conditions, SvelteKit layouts,
wrapped Express handlers, …) are reworded around the graph; their original
wording is kept in the holding file for the launch. A few kept entries lose a
viewer clause. The Highlights are rewritten for this release.

prepare-release.mjs promotes the section and extract-release-notes.mjs reads
it back with no mention of the viewer; the released sections are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 05:01:34 +00:00
Colby McHenryandClaude Opus 5 0196c2e53a feat(ui): serve the viewer from codegraph ui, loopback-only and read-only (CG-41)
Adds the `codegraph ui [path]` command (alias `web`) and `src/ui-server/`, a
`node:http` server with no framework and no new dependency.

The command reads an index that already exists — it never creates one, so a
missing index prints the same friendly guidance the MCP tools give instead of
a stack trace, and a sensitive system directory is refused up front.

Security is the substance here, not the routing. The server binds 127.0.0.1
only, answers GET and HEAD only, and sends no CORS headers ever. The realistic
attack on a process that serves your source code from a local port is DNS
rebinding, so every request must carry a loopback `Host` (on our port) and, if
it carries an `Origin` at all, a loopback one — anything else is 403 before
the filesystem is touched. Every path resolves through the engine's existing
`validatePathWithinRoot` chokepoint, which already handles `../` traversal and
in-tree symlinks pointing out of the root (#527); `..` segments are refused
outright so a traversal attempt gets a 404 rather than the SPA shell.

`PathRefusalError` moves from `mcp/tools.ts` into the dependency-free
`errors.ts` (re-exported from its old home, so class identity and every
`instanceof` check are unchanged) — that is what lets a non-MCP read sink
enforce the same refusal without importing the MCP tool graph.

Assets come from `dist/viewer/` resolved relative to `__dirname`, the way
`db/index.ts` finds `schema.sql`. Hashed assets are cached immutably,
`index.html` never. Port 4747, or the next free one — an explicit `--port`
stays explicit rather than silently moving. `--no-open` skips the browser, and
`CODEGRAPH_BROWSER` picks one (or `none` to suppress it), which is also what
makes "did it open a browser" testable end to end.

`resolveProjectFile` and the `/api/` handler seam are the boundary CG-42's
JSON API plugs into; `/api/*` 404s as JSON so a typo'd endpoint never returns
the app shell.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:17:09 -05:00