Skip to content

Security hardening offline UI#577

Open
lahirunirmalx wants to merge 5 commits into
DeusData:mainfrom
lahirunirmalx:security-hardening-offline-ui
Open

Security hardening offline UI#577
lahirunirmalx wants to merge 5 commits into
DeusData:mainfrom
lahirunirmalx:security-hardening-offline-ui

Conversation

@lahirunirmalx

@lahirunirmalx lahirunirmalx commented Jun 23, 2026

Copy link
Copy Markdown

What does this PR do?

Replaces the graph UI's whole-graph layout — which OOM-killed the server trying to lay out 299k nodes / 1.07M edges, and froze the browser — with a hierarchical drill-down explorer that represents a codebase of any size while only ever materializing one level at a time.

Backend: new cbm_store_expand_tree + GET /api/graph?project=&parent= — groups nodes one level below a qualified-name prefix and aggregates cross-group relationship edges into weighted super-edges. Parameterized SQL with a range scan on the (project, qualified_name) index, results memoized (index is static per run). Memory-safe at any size (root view ~7 KB, RSS ~13 MB vs the old ~14 GB).
Frontend: overview → click-to-drill → breadcrumbs, plus an aim-free LevelList and larger 3D hit targets (the bloom glow was bigger than the clickable sphere, so halo clicks missed).
Adds an include_labels whitelist (label IN (...)) to search params; cbm_layout_compute gains a label arg with per-label balancing.

Checklist

  • Every commit is signed off (git commit -s) — required, CI rejects
    unsigned commits (DCO, see CONTRIBUTING.md)
  • Tests pass locally (make -f Makefile.cbm test)
  • Lint passes (make -f Makefile.cbm lint-ci)
  • New behavior is covered by a test (reproduce-first for bug fixes)

Remove the runtime dependency on third-party font CDNs when viewing a
codebase in the graph UI:

- 2D UI (Inter + JetBrains Mono): drop the Google Fonts <link> in
  index.html and self-host via @fontsource packages imported in main.tsx;
  Vite bundles the woff/woff2 locally.
- 3D node labels (troika/drei <Text>): troika only calls its
  unicode-font-resolver (a cdn.jsdelivr.net fetch) for characters not
  covered by an explicit font. Vendor DejaVu Sans (Latin/Greek/Cyrillic)
  under public/fonts and pass it via the font prop, so code identifiers
  render with zero external requests. DejaVu license included.

Signed-off-by: lahiru <lahirunirmalx@gmail.com>
Security and build-portability fixes:

- self-update (cli.c): fail closed. download_verify_install now aborts
  and deletes the archive on any non-zero checksum result, not only an
  explicit mismatch. Previously a 'could not verify' (missing
  checksums.txt, archive not listed, or no sha256 tool) fell through and
  installed an unverified binary. Offline hosts can opt in with
  CBM_ALLOW_UNVERIFIED_UPDATE=1 (loud warning).
- startup update check (mcp.c): add CBM_NO_UPDATE_CHECK=1 opt-out so
  restricted-egress / air-gapped setups can disable the GitHub call.
- UI server (http_server.c): send a strict Content-Security-Policy on the
  document. Egress is pinned to 'self' (connect-src/font-src/img-src), so
  the browser refuses any external request when viewing a proprietary
  codebase. script-src allows blob: (three.js/troika spawn blob: workers
  that importScripts blob URLs); blob: is local and does not widen egress.
- build (Makefile.cbm): link -ldl on non-Windows. SQLite's dlopen/dlsym
  live in a separate libdl on glibc < 2.34, breaking the link otherwise.
- embed-frontend.sh: add .ttf/.otf content types for vendored fonts.

Signed-off-by: lahiru <lahirunirmalx@gmail.com>
Manual source review covering data-exfiltration risk, remote code
execution potential, project legitimacy, and the fixes applied this
round (self-update fail-closed, update-check opt-out, local fonts, and
the UI Content-Security-Policy).

Signed-off-by: lahiru <lahirunirmalx@gmail.com>
This file is produced by scripts/embed-frontend.sh during the --with-ui
build (base64 of the bundled graph UI); it is a build artifact, not
source, and should not be tracked.

Signed-off-by: lahiru <lahirunirmalx@gmail.com>
Rendering the whole graph (299k nodes / 1.07M edges) OOM-killed the server
building the layout and overwhelmed the browser. Replace it with a
semantic-zoom explorer that represents every node while only ever
materializing one level at a time.

Backend:
- cbm_store_expand_tree: group nodes one level below a qualified_name
  prefix and aggregate cross-group relationship edges into weighted
  super-edges. Parameterized SQL with a range predicate on the
  (project, qualified_name) index; memory-safe at any repo size.
- /api/graph?project=&parent=: aggregated children + super-edges with
  Fibonacci-sphere positions, behind an in-memory memo cache (the index
  is static per run: cold ~6s once, warm ~10ms).
- search params gain an optional include_labels whitelist (label IN (...));
  cbm_layout_compute takes a label arg and balances multi-label requests
  per label so one label cannot crowd out the rest.

Frontend:
- useGraphData fetches /api/graph per level; GraphTab drills on click with
  breadcrumb navigation up the path.
- LevelList: aim-free clickable list of the current level. The 3D bloom
  glow is far larger than the clickable sphere, so clicks on the halo
  missed; also enlarge the hit spheres and widen node spread.

Verified: ASan suite (no sanitizer hits); headless-Chrome CDP confirms both
list-row and 3D-node clicks drill in with no JS errors.

Signed-off-by: lahiru <lahirunirmalx@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thanks @lahirunirmalx — reviewed this closely and it's genuinely good, and actually security-positive: the drill-down explorer addresses the >10k-node OOM, and the hardening is all welcome (strict CSP, removing the Google Fonts CDN for a fully-offline UI, fail-closed self-update, and the read-only, fully-parameterized /api/graph endpoint on the existing loopback server).

Two things before it can land:

  1. It's currently conflicting with main — please rebase.
  2. Please drop docs/SECURITY_ASSESSMENT.html (~290 lines) — it's a self-authored audit doc that doesn't belong in the feature PR. Happy to take it as a discussion/issue instead.

Once it's rebased and trimmed, this is good to go. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants