Files
SRCmail/vnc/VNC-CHANGES.md
T
Bernd RodlerandClaude Opus 4.8 e9746fcf78 feat(plugins): admin review panel for scanner findings
The overrideWarnings escape hatch added alongside the bundle scan was
API-only: an admin uploading a crypto plugin through the web form hit a
400 with canOverride and had no way to act on it, which left S/MIME and
PGP bundles uninstallable through the UI.

Hold the rejected file client-side and show the findings — pattern per
file — with "Install anyway" and "Cancel". Proceeding re-posts the same
file with overrideWarnings, so the decision stays explicit and lands in
the audit log. The route now echoes accepted findings back on success so
the confirmation says how many were waved through rather than reporting a
bare install.

Also replaces a dead `data.warnings` read with the live `findings` field;
the route never returned `warnings` on success, so that branch never ran.

Completes B-01.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 09:03:14 +02:00

5.6 KiB

VNC-CHANGES — VNCmail+ divergence log

VNCmail+ is a fork of bulwarkmail/webmail (AGPL-3.0). This file records every intentional divergence from upstream so that merging new upstream releases stays a triage exercise, not an archaeology dig.

Rules of the fork

  1. Keep upstream files unmodified whenever possible. Prefer env vars (branding), the vnc/overrides/ layer, and additive files over editing files inside app/, components/, lib/, stores/.
  2. Every edit to an upstream file gets a one-line entry below — path, what, why. No silent edits.
  3. Branches: main tracks upstream releases (kept clean); dev is the VNC integration + Vercel deploy branch; vnc/* are feature branches.
  4. Syncing upstream: git fetch upstream && git merge upstream/main onto main, then merge main into dev. Resolve using this log.

Divergences

Date File / area Change Why
2026-08-03 vnc/ (new) Added VNC customization dir + this log Fork bootstrap
2026-08-03 VNCMAIL-SETUP.md (new) Vercel deploy runbook Deploy on Vercel as project "VNCmail+"

| 2026-08-03 | deploy/k8s/ (new) | k8s manifests + runbook for microk8s deploy | Bulwark is stateful → runs as a container w/ persistent volumes, not Vercel serverless | | 2026-08-03 | .gitignore | ignore deploy/k8s/secret.yaml | keep the real env secret out of git | | 2026-08-03 | public/branding/*.svg (new) | VNCmail wordmark (on-dark + on-light) | VNC logo (placeholder — swap official SVG) | | 2026-08-03 | public/fonts/*.woff2 (new) | DM Sans 400/500/700 + Syne 700/800 (OFL, self-hosted) | VNClagoon typography | | 2026-08-03 | lib/builtin-themes.ts | add builtin-vnclagoon theme (navy+cyan, DM Sans/Syne, @font-face) | VNClagoon brand theme | | 2026-08-03 | lib/admin/types.ts | DEFAULT_THEME_POLICY.defaultThemeIdbuiltin-vnclagoon | make VNClagoon the default theme | | 2026-08-03 | stores/theme-store.ts | default theme/resolvedThemedark | dark-first per VNClagoon styleguide | | 2026-08-03 | lib/builtin-themes.ts | add builtin-src theme (Swiss red on white, light-first) | 2nd brand theme (SRC Advisory); VNClagoon stays default | | 2026-08-03 | public/branding/src-logo.svg (new) | SRC mountain mark | SRC brand (placeholder — swap official) | | 2026-08-03 | lib/plugin-types.ts | add optional logoLightUrl/logoDarkUrl to InstalledTheme | per-theme brand logos | | 2026-08-03 | lib/theme-logo.ts (new) | resolveThemeLogo() helper | pick active theme's logo, fall back to global | | 2026-08-03 | lib/builtin-themes.ts | set logos on vnclagoon (wordmark) + src (mark) | logo switches with the brand theme | | 2026-08-03 | app/(main)/[locale]/login/page.tsx | login logo uses active theme's logo | brand-switch on login | | 2026-08-03 | components/layout/navigation-rail.tsx | nav-rail logo uses active theme's logo | brand-switch in app |

Note: Vercel was tried and abandoned on 2026-08-03. Bulwark writes to a local data dir (/app/data/*); Vercel serverless has a read-only filesystem → crash (ENOENT /var/task/data). VNCmail+ now deploys as a Docker image (ghcr.io/brvncde-dotcom/vncmail-plus-*) on Kubernetes (microk8s) at vncmail.sandbox.vnc.de, with 4 persistent volumes — see deploy/k8s/. A microfrontends integration was also added and reverted the same day.

| 2026-08-03 | lib/stalwart/auth-context.ts | give jmap_stalwart_ctx a 6-hour maxAge (was session-cookie → expired on tab close) | session survival across browser restarts | | 2026-08-03 | lib/builtin-themes.ts | add srcSkin (MD3 component overrides: shape scale, filled buttons, text fields, cards, dialogs, state layers, switches, login card); add @font-face + typography to builtin-src; bump to v1.1.0 | SRC theme: keep colors + fonts, apply MD3 design system | | 2026-08-04 | lib/plugin-sandbox/loader.ts | B-04 security fix — gate hook registration on granted permissions via new HOOK_PERMISSIONS map; refused hooks are skipped, logged and counted | info.hooks is self-reported by the sandbox, so an untrusted plugin could claim onRenderEmailBody and replace any rendered email body without holding email:render-takeover. Consent copy gated what the user was asked, not what the host allowed. | | 2026-08-04 | lib/plugin-sandbox/host-api.ts | export hasPermission() (was module-private) | one source of truth for the permission rule — the loader gate and the RPC gate must not drift apart | | 2026-08-04 | app/api/admin/plugins/route.ts | B-01 — scan all .js/.mjs in the bundle (was entrypoint only); return structured findings + canOverride; allow admin overrideWarnings=true with a plugin.install.scan_override audit entry; echo accepted findings on success | hard-reject on eval(/new Function(/innerHTML = made every crypto plugin uninstallable (minified openpgp.js/pkijs trip it), while only scanning the entrypoint left a trivial bypass. Route is already admin-authenticated, so the scan is defence-in-depth, not a trust boundary. | | 2026-08-04 | app/(main)/admin/_tabs/plugins.tsx | B-01 (UI) — scanner-findings review panel: holds the rejected file, lists pattern-per-file, offers "Install anyway" / "Cancel"; success message reports how many findings were accepted | without this the override was API-only — an admin uploading a crypto bundle through the web form hit a 400 they could not act on. Also replaces a dead data.warnings read (never returned by the route) with the live findings field. |

(append new rows as you diverge)