From 8935b81f121f7fba131269d7d3d02e514a0fb25f Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:34:06 +0200 Subject: [PATCH] chore: update version to 1.5.3 --- CHANGELOG.md | 19 +++++++++++++++++++ README.md | 4 +++- VERSION | 2 +- app/admin/telemetry/page.tsx | 2 +- app/api/auth/totp-token-exchange/route.ts | 2 +- lib/plugin-storage.ts | 2 +- lib/plugin-types.ts | 10 +++++----- lib/plugin-validator.ts | 8 ++++---- lib/telemetry/endpoint-guard.ts | 4 ++-- lib/telemetry/state.ts | 2 +- lib/theme-compiler.ts | 16 ++++++++-------- lib/theme-loader.ts | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- stores/theme-store.ts | 2 +- 15 files changed, 52 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4373ed1..77ce99cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 1.5.3 (2026-04-28) + +> **New:** Bulwark Webmail now sends an anonymous instance heartbeat once per day (version, platform, bucketed account counts, feature toggles — no message data, no PII). Disable any time from **Admin → Telemetry** or by setting `BULWARK_TELEMETRY=off`. See the [privacy notice](https://bulwarkmail.org/docs/legal/privacy/telemetry) for the full schema. + +### Features + +- **Telemetry**: Anonymous instance telemetry, on by default. Reports schema version, platform, bucketed account counts, and feature toggles only — disable from the admin UI, with `BULWARK_TELEMETRY=off`, or by clearing the endpoint +- **Telemetry**: Track unique logins (HMAC'd per instance, 90-day retention) so the heartbeat can report bucketed account totals without storing usernames +- **Plugins**: Theme API v2 with token compiler and skin slot +- **Plugins**: Extension preview page and detailed extension info API +- **Calendar**: Right-click context menu on empty calendar space +- **Docker**: Persistent named volume for telemetry data so the instance id and admin's consent choice survive container upgrades + +### Fixes + +- **Security**: Block telemetry endpoint from pointing at internal/loopback hosts (validation + DNS-rebind re-check at fetch time) +- **Security**: Harden plugin config, TOTP token exchange, and branding file serving +- **Mail**: Batch shortcuts now act on the multi-selection when one is present (#228) + ## 1.5.2 (2026-04-27) ### Features diff --git a/README.md b/README.md index 05c7f005..1d46afb6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A modern, self-hosted webmail client for [Stalwart Mail Server](https://stalw.ar [![License: AGPL v3](https://img.shields.io/badge/license-AGPL%20v3-blue.svg?logo=gnu&logoColor=white)](LICENSE) [![Discord](https://img.shields.io/discord/1482128142939455674?color=7289da&label=discord&logo=discord&logoColor=white)](https://discord.gg/tYCujymGrT) -[![Version](https://img.shields.io/badge/version-1.5.2-green.svg?logo=git&logoColor=white)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-1.5.3-green.svg?logo=git&logoColor=white)](CHANGELOG.md) [![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fbulwarkmail%2Fwebmail-blue?logo=docker&logoColor=white)](https://ghcr.io/bulwarkmail/webmail) @@ -53,6 +53,8 @@ A modern, self-hosted webmail client for [Stalwart Mail Server](https://stalw.ar +> **Anonymous telemetry is on by default** since 1.5.3. Each instance sends a daily heartbeat (version, platform, bucketed account counts, feature toggles — no message data, no PII). Disable from **Admin → Telemetry**, by setting `BULWARK_TELEMETRY=off`, or by clearing the endpoint. Full schema: [privacy notice](https://bulwarkmail.org/docs/legal/privacy/telemetry). + ## Overview Bulwark is a full webmail suite – not just an inbox. It bundles the four apps most self-hosters end up wanting on the same login: diff --git a/VERSION b/VERSION index 4cda8f19..8af85beb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.2 +1.5.3 diff --git a/app/admin/telemetry/page.tsx b/app/admin/telemetry/page.tsx index 29ddd72c..ea60ab33 100644 --- a/app/admin/telemetry/page.tsx +++ b/app/admin/telemetry/page.tsx @@ -137,7 +137,7 @@ export default function AdminTelemetryPage() {
Status
- {status.consent === 'pending' && 'Initialising — no heartbeats sent yet.'} + {status.consent === 'pending' && 'Initialising - no heartbeats sent yet.'} {status.consent === 'on' && 'Heartbeats are enabled (default).'} {status.consent === 'off' && 'Heartbeats are off.'} {envOverridden && ( diff --git a/app/api/auth/totp-token-exchange/route.ts b/app/api/auth/totp-token-exchange/route.ts index f311faec..9b1dca47 100644 --- a/app/api/auth/totp-token-exchange/route.ts +++ b/app/api/auth/totp-token-exchange/route.ts @@ -89,7 +89,7 @@ export async function POST(request: NextRequest) { // Pin the upstream URL to the configured JMAP server so an unauthenticated // caller cannot point this route at internal hosts. Only when no server // URL is configured (and the deployment explicitly allows custom JMAP - // endpoints) do we fall back to the user-supplied URL — and even then + // endpoints) do we fall back to the user-supplied URL - and even then // it must resolve to a public address. await configManager.ensureLoaded(); const configuredServerUrl = diff --git a/lib/plugin-storage.ts b/lib/plugin-storage.ts index eefcf4f1..51ef42b8 100644 --- a/lib/plugin-storage.ts +++ b/lib/plugin-storage.ts @@ -88,7 +88,7 @@ export const pluginStorage = { await deleteItem(STORE_THEMES, themeId); }, - // Theme skin CSS — separate store so it can be present/absent independently + // Theme skin CSS - separate store so it can be present/absent independently // of the colour-token CSS (e.g. some v2 themes ship colours only). async saveThemeSkin(themeId: string, skin: string): Promise { await putItem(STORE_THEME_SKINS, themeId, skin); diff --git a/lib/plugin-types.ts b/lib/plugin-types.ts index 1cf90275..b85f6cb7 100644 --- a/lib/plugin-types.ts +++ b/lib/plugin-types.ts @@ -12,7 +12,7 @@ export type ThemeVariant = 'light' | 'dark'; // ─── Manifests ─────────────────────────────────────────────── /** - * Advanced theme fields ("Theme API v2"). All optional and additive — a + * Advanced theme fields ("Theme API v2"). All optional and additive - a * legacy theme that ships only `:root`/`.dark` CSS continues to work. * * When `apiVersion >= 2` (or any of `tokens`/`extends`/`derive`/`density`/ @@ -62,7 +62,7 @@ export interface ThemeManifest { apiVersion?: 1 | 2; /** Inherit tokens/CSS from another installed (or built-in) theme by id. */ extends?: string; - /** Structured colour tokens — compiled into CSS at install time. */ + /** Structured colour tokens - compiled into CSS at install time. */ tokens?: ThemeTokenSet; /** When true, missing standard tokens are derived (e.g. *-foreground from contrast). */ derive?: boolean; @@ -121,10 +121,10 @@ export interface InstalledTheme { author: string; description: string; preview?: string; // data: URI or blob URL - css: string; // compiled CSS text — what gets injected + css: string; // compiled CSS text - what gets injected /** * Optional "skin" CSS shipped by Theme API v2 themes that need to restyle - * actual UI components (toolbars, lists, buttons, etc.) — not just colour + * actual UI components (toolbars, lists, buttons, etc.) - not just colour * tokens. Injected into a separate `