Skip to content

chore(deps): bump the ui group in /src/Dispatch.UI with 11 updates - #3

Closed
dependabot[bot] wants to merge 298 commits into
mainfrom
dependabot/npm_and_yarn/src/Dispatch.UI/ui-f94088bdf1
Closed

chore(deps): bump the ui group in /src/Dispatch.UI with 11 updates#3
dependabot[bot] wants to merge 298 commits into
mainfrom
dependabot/npm_and_yarn/src/Dispatch.UI/ui-f94088bdf1

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 9, 2026

Copy link
Copy Markdown

Bumps the ui group in /src/Dispatch.UI with 11 updates:

Package From To
@microsoft/signalr 8.0.17 10.0.0
@tanstack/react-query 5.101.0 5.101.2
react 18.3.1 19.2.7
@types/react 18.3.31 19.2.17
react-dom 18.3.1 19.2.7
@types/react-dom 18.3.7 19.2.3
react-router-dom 7.17.0 7.18.1
recharts 2.15.4 3.9.2
@vitejs/plugin-react 4.7.0 6.0.3
typescript 5.9.3 7.0.2
vite 6.4.3 8.1.4

Updates @microsoft/signalr from 8.0.17 to 10.0.0

Release notes

Sourced from @​microsoft/signalr's releases.

.NET 10.0

Release

What's Changed

Full Changelog: dotnet/aspnetcore@v10.0.0-rc.2.25502.107...v10.0.0

.NET 10.0 RC 2

Release

What's Changed

... (truncated)

Commits

Updates @tanstack/react-query from 5.101.0 to 5.101.2

Release notes

Sourced from @​tanstack/react-query's releases.

@​tanstack/react-query-devtools@​5.101.2

Patch Changes

@​tanstack/react-query-next-experimental@​5.101.2

Patch Changes

  • Updated dependencies []:
    • @​tanstack/react-query@​5.101.2

@​tanstack/react-query-persist-client@​5.101.2

Patch Changes

  • Updated dependencies []:
    • @​tanstack/query-persist-client-core@​5.101.2
    • @​tanstack/react-query@​5.101.2

@​tanstack/react-query@​5.101.2

Patch Changes

  • Updated dependencies []:
    • @​tanstack/query-core@​5.101.2

@​tanstack/react-query-devtools@​5.101.1

Patch Changes

  • Updated dependencies []:
    • @​tanstack/query-devtools@​5.101.1
    • @​tanstack/react-query@​5.101.1

@​tanstack/react-query-next-experimental@​5.101.1

Patch Changes

  • Updated dependencies []:
    • @​tanstack/react-query@​5.101.1

@​tanstack/react-query-persist-client@​5.101.1

Patch Changes

  • Updated dependencies []:
    • @​tanstack/query-persist-client-core@​5.101.1
    • @​tanstack/react-query@​5.101.1

@​tanstack/react-query@​5.101.1

Patch Changes

... (truncated)

Changelog

Sourced from @​tanstack/react-query's changelog.

5.101.2

Patch Changes

  • Updated dependencies []:
    • @​tanstack/query-core@​5.101.2

5.101.1

Patch Changes

  • Updated dependencies [9eff92e]:
    • @​tanstack/query-core@​5.101.1
Commits
  • 610e8d1 ci: Version Packages (#10996)
  • 1f84256 docs: document the select typing caveat for parallel-queries hooks (#10984)
  • b809297 ci: Version Packages (#10977)
  • ccc843e test({react,preact}-query/useQueries): move type-only tests to 'useQueries.te...
  • 4154613 test({react,preact}-query/useMutation): split 'should handle conditional logi...
  • 8bb5fde test({react,preact}-query/useMutation): split 'should pass meta to mutation' ...
  • 87426a3 test(react-query): replace deprecated 'toBeCalledTimes' with 'toHaveBeenCalle...
  • feb1efd test(*): move 'vi.useRealTimers' to the end of 'afterEach' so cleanup runs un...
  • See full diff in compare view

Updates react from 18.3.1 to 19.2.7

Release notes

Sourced from react's releases.

19.2.7 (June 1st, 2026)

React Server Components

19.2.6 (May 6th, 2026)

React Server Components

19.2.5 (April 8th, 2026)

React Server Components

19.2.4 (January 26th, 2026)

React Server Components

19.2.3 (December 11th, 2025)

React Server Components

19.2.2 (December 11th, 2025)

React Server Components

19.2.1 (December 3rd, 2025)

React Server Components

19.2.0 (Oct 1, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

  • <Activity>: A new API to hide and restore the UI and internal state of its children.
  • useEffectEvent is a React Hook that lets you extract non-reactive logic into an Effect Event.
  • cacheSignal (for RSCs) lets your know when the cache() lifetime is over.
  • React Performance tracks appear on the Performance panel’s timeline in your browser developer tools

New React DOM Features

... (truncated)

Changelog

Sourced from react's changelog.

19.2.7 (June 1, 2026)

React Server Components

19.2.6 (May 6, 2026)

React Server Components

19.2.5 (March 18, 2026)

React Server Components

19.2.4 (Jan 26, 2026)

React Server Components

19.2.3 (Dec 11, 2025)

React Server Components

19.2.2 (Dec 11, 2025)

React Server Components

19.2.1 (Dec 3, 2025)

React Server Components

19.2.0 (October 1st, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for react since your current version.


Updates @types/react from 18.3.31 to 19.2.17

Commits

Updates react-dom from 18.3.1 to 19.2.7

Release notes

Sourced from react-dom's releases.

19.2.7 (June 1st, 2026)

React Server Components

19.2.6 (May 6th, 2026)

React Server Components

19.2.5 (April 8th, 2026)

React Server Components

19.2.4 (January 26th, 2026)

React Server Components

19.2.3 (December 11th, 2025)

React Server Components

19.2.2 (December 11th, 2025)

React Server Components

19.2.1 (December 3rd, 2025)

React Server Components

19.2.0 (Oct 1, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

  • <Activity>: A new API to hide and restore the UI and internal state of its children.
  • useEffectEvent is a React Hook that lets you extract non-reactive logic into an Effect Event.
  • cacheSignal (for RSCs) lets your know when the cache() lifetime is over.
  • React Performance tracks appear on the Performance panel’s timeline in your browser developer tools

New React DOM Features

... (truncated)

Changelog

Sourced from react-dom's changelog.

19.2.7 (June 1, 2026)

React Server Components

19.2.6 (May 6, 2026)

React Server Components

19.2.5 (March 18, 2026)

React Server Components

19.2.4 (Jan 26, 2026)

React Server Components

19.2.3 (Dec 11, 2025)

React Server Components

19.2.2 (Dec 11, 2025)

React Server Components

19.2.1 (Dec 3, 2025)

React Server Components

19.2.0 (October 1st, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for react-dom since your current version.


Updates @types/react-dom from 18.3.7 to 19.2.3

Commits

Updates react-router-dom from 7.17.0 to 7.18.1

Changelog

Sourced from react-router-dom's changelog.

v7.18.1

Patch Changes

v7.18.0

Patch Changes

Commits

Updates recharts from 2.15.4 to 3.9.2

Release notes

Sourced from recharts's releases.

v3.9.2

What's Changed

New Contributors

Full Changelog: recharts/recharts@v3.9.1...v3.9.2

v3.9.1

What's Changed

New Contributors

Full Changelog: recharts/recharts@v3.9.0...v3.9.1

v3.9.0

What's Changed

Animations

3.9 comes with new animations! There are several bug fixes and what's best, all animations are now fully customizable.

See the animations guide on https://recharts.github.io/en-US/guide/animations/

... (truncated)

Commits
  • b345105 3.9.2
  • f27779c npm i
  • 85f9369 chore(deps-dev): bump prettier from 3.8.4 to 3.9.4 (#7520)
  • 52a2a89 fix(Sankey): avoid exponential depth traversal on dense graphs (#7479)
  • 8a056c1 chore(deps-dev): bump rollup from 4.61.1 to 4.62.2 (#7527)
  • 2af6ec6 chore(deps): bump immer from 11.1.8 to 11.1.9 (#7526)
  • 6f10d53 docs: clarify custom labels and ticks need SVG elements (#7524)
  • c04f1a7 chore(deps-dev): bump lint-staged from 17.0.7 to 17.0.8 (#7521)
  • 69c7a96 chore(deps-dev): bump glob from 11.1.0 to 13.0.6 (#7522)
  • 6efaf16 chore(deps): bump es-toolkit from 1.47.0 to 1.49.0 (#7515)
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for recharts since your current version.

Install script changes

This version adds prepare script that runs during installation. Review the package contents before updating.


Updates @types/react from 18.3.31 to 19.2.17

Commits

Updates @types/react-dom from 18.3.7 to 19.2.3

Commits

Updates @vitejs/plugin-react from 4.7.0 to 6.0.3

Release notes

Sourced from @​vitejs/plugin-react's releases.

plugin-react@6.0.3

No release notes provided.

plugin-react@6.0.2

Allow all options in reactCompilerPreset (#1189)

This is a type only change. Only compilationMode and target options were available for reactCompilerPreset.

plugin-react@6.0.1

Expand @rolldown/plugin-babel peer dep range (#1146)

Expanded @rolldown/plugin-babel peer dep range to include ^0.2.0.

plugin-react@6.0.0

Remove Babel Related Features (#1123)

Vite 8+ can handle React Refresh Transform by Oxc and doesn't need Babel for it. With that, there are no transform applied that requires Babel. To reduce the installation size of this plugin, babel is no longer a dependency of this plugin and the related features are removed.

If you are using Babel, you can use @rolldown/plugin-babel together with this plugin:

 import { defineConfig } from 'vite'
 import react from '@vitejs/plugin-react'
+import babel from '@rolldown/plugin-babel'
export default defineConfig({
plugins: [


react({



  babel: {



    plugins: ['@babel/plugin-proposal-throw-expressions'],



  },



}),





react(),



babel({



  plugins: ['@babel/plugin-proposal-throw-expressions'],



}),

]
})

For React compiler users, you can use reactCompilerPreset for easier setup with preconfigured filter to improve build performance:

 import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import react, { reactCompilerPreset } from '@vitejs/plugin-react'
+import babel from '@rolldown/plugin-babel'
export default defineConfig({
plugins: [
</tr></table>

... (truncated)

Changelog

Sourced from @​vitejs/plugin-react's changelog.

6.0.3 (2026-06-23)

6.0.2 (2026-05-14)

Allow all options in reactCompilerPreset (#1189)

This is a type only change. Only compilationMode and target options were available for reactCompilerPreset.

6.0.1 (2026-03-13)

Expand @rolldown/plugin-babel peer dep range (#1146)

Expanded @rolldown/plugin-babel peer dep range to include ^0.2.0.

6.0.0 (2026-03-12)

6.0.0-beta.0 (2026-03-03)

Remove Babel Related Features (#1123)

Vite 8+ can handle React Refresh Transform by Oxc and doesn't need Babel for it. With that, there are no transform applied that requires Babel. To reduce the installation size of this plugin, babel is no longer a dependency of this plugin and the related features are removed.

If you are using Babel, you can use @rolldown/plugin-babel together with this plugin:

 import { defineConfig } from 'vite'
 import react from '@vitejs/plugin-react'
+import babel from '@rolldown/plugin-babel'
export default defineConfig({
plugins: [


react({



  babel: {



    plugins: ['@babel/plugin-proposal-throw-expressions'],



  },



}),





react(),



babel({



  plugins: ['@babel/plugin-proposal-throw-expressions'],



}),

]
})

For React compiler users, you can use reactCompilerPreset for easier setup with preconfigured filter to improve build performance:

 import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import react, { reactCompilerPreset } from '@vitejs/plugin-react'
</tr></table> 

... (truncated)

Commits
  • 640fd35 release: plugin-react@6.0.3
  • 889efb0 fix(deps): update all non-major dependencies (#1249)
  • 6c57dd4 fix(plugin-react): use '/' base in bundledDev preamble to fix non-root base p...
  • 3cc33a7 fix(deps): update react-related dependencies (#1245)
  • c0f7c7f docs: mention the Biome rule in the "Consistent components exports" section (...
  • cd80f0f fix(deps): update all non-major dependencies (#1241)
  • e38acca fix(deps): update all non-major dependencies (#1227)
  • 9a9bb26 perf(react): improve react compiler preset so that slightly more modules are ...
  • 6535b55 release: plugin-react@6.0.2
  • bf0e43b feat(react): whitelist debugging-options (#1189)
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for @​vitejs/plugin-react since your current version.


Updates typescript from 5.9.3 to 7.0.2

Release notes

Sourced from typescript's releases.

TypeScript 6.0.3

For release notes, check out the release announcement blog post.

Downloads are available on:

TypeScript 6.0

For release notes, check out the release announcement blog post.

Downloads are available on:

TypeScript 6.0 Beta

For release notes, check out the release announcement.

Downloads are available on:

Commits
Maintainer changes

This version was pushed to npm by microsoft1es, a new releaser for typescript since your current version.


Updates vite from 6.4.3 to 8.1.4

Release notes

Sourced from vite's releases.

v8.1.4

Please refer to CHANGELOG.md for details.

v8.1.3

Please refer to CHANGELOG.md for details.

v8.1.2

Pl...

Description has been truncated

Chris Muench and others added 30 commits June 13, 2026 08:35
- Build: ubuntu-latest with a mcr.microsoft.com/mssql/server:2022 service container (the Data test
  fixture's 90s readiness wait covers startup); release job on ubuntu-latest too.
- Installers: windows-latest builds the MSI + bundle; ubuntu-latest lints install.sh.
GitHub-hosted runners are free for public repos and always available, unlike the offline self-hosted runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l name for Bal); opt actions into Node 24

- WiX v6 ships the Bal/bundle extension dll as WixToolset.BootstrapperApplications.wixext, so the bundle
  build must reference that name, not WixToolset.Bal.wixext (which failed with WIX0144 'could not be found').
- Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 to clear the Node.js 20 deprecation warning on the actions/* @v4.
…orts Ubuntu 24.04; Windows fwlink already 2025)

- Linux install.sh: mssql-server-2025 repo (Ubuntu 24.04 supported), key at /usr/share/keyrings to match
  the 2025 list's signed-by. Reverts the ubuntu-22.04 CI pin back to ubuntu-latest.
- Windows: the SSEI fwlink already resolves to SQL 2025 Express; noted in the bootstrap.
…MTP presets; fix Linux installer spool layout

Providers (spec §8): native Amazon SES (AWS SDK, raw MIME), Postmark/Resend/SMTP2GO (HTTP JSON), SparkPost
(raw MIME via email_rfc822). Enum + RelayProviderSchema + factory wiring + UI fields; SMTP-preset dropdown
in the relay editor fills host/port/TLS for ~10 common providers (SES/Brevo/Gmail/M365/Postmark/Resend/
SendGrid/SMTP2GO/SparkPost/Mailjet) so any of them works via the generic SMTP provider. Factory tests cover
all five new types.

Installer fixes (found via the full-install CI smoke):
- Resolve a relative spool dir against the content root, not the process CWD (Windows services run in
  system32; the systemd unit's CWD was a read-only /etc) — fixes 'Access to ./.dispatch-spool denied'.
- Linux: single data dir (/var/lib/dispatch) holds appsettings + spool (mirrors Windows ProgramData);
  systemd WorkingDirectory + ReadWritePaths updated.

150 tests green; UI builds.
…iness to the service manager

The Windows MSI install failed (1603): the service started but never signalled 'running' to the SCM, so
the MSI's ServiceControl start timed out and rolled back (app log showed 'Hosting environment: Production'
logged repeatedly as SCM retried). .NET hosts must call UseWindowsService() for SCM integration; added
UseSystemd() too and set the unit to Type=notify (also closes the §16.3 drift). Both are no-ops when run
interactively. Linux full-install already passed; this fixes the Windows end-to-end.
…s installer

Signs Dispatch.msi + the DispatchSetup.exe Burn bundle via SignPath Foundation
(free for OSS). Skipped until repo vars SIGNPATH_ORGANIZATION_ID/PROJECT_SLUG/
POLICY_SLUG + secret SIGNPATH_API_TOKEN are set, so the unsigned artifact stands
in the meantime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add release.yml: on a v* tag, build the version-stamped Windows installer
(DispatchSetup.exe + Dispatch.msi) and a self-contained linux-x64 tarball, then
publish a GitHub Release with SHA256SUMS and auto-generated notes. Windows
artifacts are Authenticode-signed via Azure Artifact Signing when the repo is
provisioned (AZURE_SIGNING_* vars/secrets); the Burn bundle engine is detached,
signed, reattached, then the bundle is signed. Skipped (unsigned) until then.

- installer wxs: Version is now overridable via -d Version=<tag> (defaults 1.0.0)
- install.sh: --prebuilt <dir> installs the self-contained tarball without the
  .NET SDK/Node; systemd unit resolved next to the script for tarball layout
- installers.yml: drop the dormant SignPath step (CI installer builds stay
  unsigned; signing happens only at release time)
- build.yml: remove the partial tag release job (superseded by release.yml)
- docs/RELEASING.md: how to cut a release + provision Azure signing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a linux-tarball job that publishes the self-contained linux-x64 build and
uploads it as a downloadable artifact on every push — no tag/release needed to
grab a tarball for testing install.sh --prebuilt.

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

Add a multi-stage Dockerfile (multi-arch amd64/arm64) and a docker-compose.yml
that runs Dispatch + Azure SQL Edge together (arm64-native on Apple Silicon):
`docker compose up --build` → dashboard on :8420, API :8421, SMTP :2525.

Fix the seeded source-IP allow-list defaults, which were loopback-only and made
the dashboard unreachable on every real deployment shape (headless servers have
no local browser; containers NAT every request to the bridge gateway):
- webui.allowed_cidrs / api.allowed_cidrs -> empty (allow all); these are gated
  by the dashboard password and API keys, with CIDR as optional hardening
- listener.allowed_cidrs -> loopback + RFC1918 + IPv6 ULA, so same-host/LAN/
  Docker apps can submit mail without shipping an open internet relay

Verified end-to-end on Apple Silicon: /health 200, SQL connected, SMTP intake
250 OK + spooled. Core 55 + Web 53 tests green. Spec §1.1 deltas updated.

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

The deeper cause of the unreachable-dashboard bug: ListenerOptions/ApiOptions
EffectiveAllowedCidrs silently rewrote an empty list back to loopback-only, so
the seeded allow-all defaults (and any operator who cleared the list) were
overridden — the ingestion API was loopback-only too. Both middlewares already
treat empty as allow-all, so make EffectiveAllowedCidrs a pass-through and
default the ConfigCache keys to empty; the safe baseline now lives solely in the
seeded ConfigDefaults (listener = loopback + private ranges).

Add the test coverage that would have caught this:
- CidrMailboxFilter: private/loopback allowed, public denied, empty = allow-all
- WebAuthMiddleware: outside-list 403, inside allowed, empty-list regression guard
- ConfigDefaults: seed-defaults guard (webui/api allow-all, listener private-only)

Also add a container HEALTHCHECK (curl /health) to the Dockerfile.

Verified in the running container: /health 200, ingestion API 401 (not 403),
HEALTHCHECK healthy. Core 65 + Web 57 tests green.

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

release.yml: add a docker job that builds + pushes a multi-arch (amd64+arm64)
image to ghcr.io/<owner>/dispatch-smtp-relay on a v* tag (semver + latest tags,
buildx + QEMU, GHA layer cache); skipped on dry-run dispatch. Grants packages:write.

README: add a Docker section (docker compose for local testing; docker run from
GHCR with env config) covering the multi-arch image and the container-aware
allow-list defaults.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- release.yml: version the Windows assets (DispatchSetup-<ver>-x64.exe,
  Dispatch-<ver>-x64.msi) so each release's downloads are distinguishable
- README install docs corrected to match reality:
  - Windows: real bundle behavior (DISPATCHSQL instance, skipped if present),
    DispatchLog DB (was "DispatchQueue"), silent install via /quiet, existing-SQL
    via the MSI + SQLCONN (dropped fabricated --silent/--server/--auth flags)
  - Linux: install from the self-contained tarball with --prebuilt (dropped the
    nonexistent `curl | bash install.sh` asset); note arm64 -> external SQL/Docker
  - Quick Start: default dashboard is http://localhost:8420 (https only with a
    cert), default SMTP port 2525 (not 25/587)
  - appsettings holds connection string + Web UI TLS cert (not "only the
    connection string")
- RELEASING.md: versioned asset names + GHCR image row

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile remaining drift found by auditing claims against the code:
- Providers: add Amazon SES, Postmark, Resend, SparkPost, SMTP2GO everywhere
  (features, relay table with real required fields, supported-providers, structure)
- Remove the "CSV export" claim (feature was dropped, spec §1.1)
- SMTP listener defaults: ports 2525 (not 25/587), allow-list = loopback+private
  ranges (not 127.0.0.1/32), max message size 0/no-limit (not 25 MB)
- Security: dashboard is HTTPS-when-cert-configured (not "HTTPS-only" with auto
  self-signed); API keys + admin password are bcrypt-hashed, provider/SMTP
  secrets AES-256-GCM (Linux/macOS) or DPAPI (Windows)
- Upgrading: in-place MSI MajorUpgrade + additive migrations + manual drain
  endpoint (the old auto version-detect/drain/rollback flow isn't implemented)
- Requirements: SQL Server 2025 Express bundled; arm64 -> Docker/external SQL
- Project structure: WiX MSI + Burn bundle (not "WiX v5"), Dockerfile added
- Hero line: SMTP default 2525 (25/587 for production)

Verified against code via audit: all referenced files/links exist; routing
rules + /api/routing/simulate and the drain endpoint do exist; provider field
names match RelaySettings schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Fix the settings location: the UI sections are "Retention" + "Storage
  maintenance" (not "Storage & Retention")
- Don't overstate configurability: the 6-hour purge schedule and the
  retrying/test-message retention are fixed defaults; the rest are editable
- Add the facts the section omitted: 6h schedule, captured (Local) 7-day
  retention, on-demand purge (POST /api/purge/run), size target 9.0 GB, and the
  separate disk-pressure protection (throttle -> 4xx refuse -> recover)

Audited licence-faq.md and CONTRIBUTING.md against the code — both accurate
(MailgunProviderTests / RelayProviderFactory / RelayProviderSchema all exist),
left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline factual fixes:
- WiX Toolset v5 -> v6 (build pins 6.0.2)
- DispatchQueue -> DispatchLog everywhere except the rename note
- Linux install examples: SQL Server 2025 / Ubuntu 24.04 (were 2022/22.04)
- Solution structure: real test projects (no Dispatch.Integration.Tests)

New §1.1 deltas (authoritative) for structural drift found by auditing §3-18:
- Full provider set (adds Amazon SES, Postmark, Resend, SparkPost, SMTP2GO;
  Appendix A "future" ones are shipped; default relay provider is Unconfigured)
- Windows install is a WiX Burn bundle chain (InstallSqlExpress launcher + MSI),
  not a WinForms wizard; cert generation is Linux-only
- Ingestion API is HTTP-only (no api.tls_* keys); webui TLS keys live in
  appsettings not the config table; no webui.require_auth; config table also has
  listener.server_name + purge.captured_retention_days
- §10.7/§11.5 pseudocode signatures are illustrative (actual signatures differ)
- SMTP source-IP denial occurs at MAIL FROM, not the greeting banner
- Documents two not-yet-implemented items vs spec: SMTP AUTH brute-force lockout
  (§17.10) and instant API-key revocation (§17.4 — honored up to the 30s cache TTL)

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

Close the two gaps the spec audit surfaced (§17.4, §17.10):

- ApiKeyCache.Invalidate(keyId): the DELETE /api/keys/{id} revoke endpoint now
  evicts the key from the verification cache, so it stops working immediately
  instead of lingering for up to the 30s TTL.
- SmtpAuthThrottle: per-source-IP SMTP AUTH lockout (5 failures -> 60s), refusing
  AUTH without hitting the credential store while locked; mirrors LoginThrottle.
  Wired into ConfiguredUserAuthenticator (records success/failure by source IP).

Tests: ApiKeyCache invalidation (evicts only the matching id), SmtpAuthThrottle
(lockout after 5, success resets, per-IP), and ConfiguredUserAuthenticator (a
locked IP is refused without a store call). Core 70 + Web 59 green.

Updates the §1.1 deltas: both are now implemented (were documented as gaps).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- SECURITY.md: private disclosure via GitHub Security Advisories, what to
  include, scope (open-relay/allow-list bypass, credential/spool exposure, auth
  bypass, etc.), and a map of where security controls live for reviewers.
- RELEASING.md: a one-time "first release" step to flip the GHCR package to
  public (packages are private by default) so anonymous docker pull works.

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

Live end-to-end testing showed the single-message status endpoint resolved a
message by spool id for ANY valid key, leaking another key's status/provider/
timing if the (random) id was known. Only the list endpoint was key-scoped.

Now scoped per key (spec §7.4): the spool fast-path checks the .meta's ApiKeyId,
and GetBySpoolIdAsync filters relay_log by api_key_id. A non-owning key gets 404.
A null key id keeps the unscoped lookup for internal callers.

Test: GetBySpoolId_is_scoped_to_the_calling_key (Data, live SQL). Validated live
in the container: owner 200, other key 404. Data 25 + Web 59 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the coverage gap surfaced by the audit — previously only "the factory
builds them" was tested. Add request-building + error-mapping tests (stubbed
HttpMessageHandler, no live calls):

- Postmark: endpoint + X-Postmark-Server-Token, default MessageStream, and the
  important non-zero-ErrorCode-on-HTTP-200 -> permanent failure path
- Resend: Bearer auth, id parse, 429 transient, 401 permanent
- SparkPost: raw-MIME (email_rfc822) to US/EU endpoints, api-key header, 5xx transient
- SMTP2GO: api_key in body, email_id parse, 5xx transient, missing key permanent
- Amazon SES: required-setting validation (can't wire the AWS SDK client in a unit test)

Shared StubHttpHandler + message helper in ProviderTestSupport. Providers 34 green.

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

Edge-case coverage from the adversarial pass:
- Ingestion multipart with TWO attachments (csv + binary png): asserts both are
  preserved in the spooled .eml with filenames + exact bytes intact, alongside
  the text body.
- Malformed 'from' address -> 400 (not 500): pins the handler's parse-exception
  guard so bad envelope input can't 500.
- MessageLog_all_filter_fields_are_injection_safe: complements the existing
  FromDomain [Theory] by exercising ToDomain/RelayName/IngestSource and the
  LIKE-based Subject/Tag filters with DROP/DELETE payloads — literal, no match,
  table intact.

Note: spool crash-recovery (RecoverOrphans) and corrupt-.meta quarantine are
already covered in SpoolWorkerPoolTests, so not duplicated. Web 61 + Data 26 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chris Muench and others added 20 commits July 1, 2026 20:24
#4: Pin all third-party GitHub Actions in release.yml (the job that holds
DISPATCH_UPDATE_SIGNING_KEY + Azure OIDC + the release token) to full
commit SHAs with a version comment: azure/trusted-signing-action,
azure/login, softprops/action-gh-release, docker/*. A hijacked mutable
tag can no longer run in the signing/publish job. Add .github/dependabot.yml
(github-actions + npm + nuget, grouped) so pinned SHAs still get bumped.

#5: Verify the appliance's inputs before baking them in (build-appliance.sh):
- Ubuntu cloud image: verify SHA256SUMS's detached GPG signature against
  the pinned Ubuntu Cloud Image signing key, then verify the image hash
  against it. Fail-closed.
- packages-microsoft-prod.deb: pin its SHA256 so a swapped bootstrap .deb
  can't install a rogue repo/key. (Individual packages are already apt
  signature-verified; this closes the bootstrap gap.)

Addresses audit findings #4, #5.

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

#6 Decompression bomb: the 2GiB cap was on the compressed upload only.
Add CopyCappedAsync (3GiB ceiling) and use it when extracting the payload
and when unwrapping a GitHub-wrapped .zip, so a gzip/zip bomb can't fill
the disk before the hash check. Reported as a friendly rejection.

#7 Session invalidation on password change: a changed admin password now
bumps a monotonic credential epoch (webui.session_epoch); the auth cookie
carries the epoch it was issued under, and OnValidatePrincipal rejects
cookies with an older epoch (cached, lag-tolerant strict-older check). The
acting admin's own cookie is re-issued so they stay signed in while every
other session is dropped. First-run setup doesn't bump (no prior sessions).

Addresses audit findings #6, #7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#10 Windows updater: replace Expand-Archive with a manual extraction that
validates every zip entry resolves strictly under the install dir before
writing (Expand-Archive/older .NET don't reject '..' entries), guarding
the SYSTEM-level apply against a zip-slip in a malicious release archive.

#9 Ingestion API: set the per-request MaxRequestBodySize to
MaxMessageBytes (+ framing overhead) before reading, so an upload without
Content-Length (chunked) is aborted while streaming instead of being
buffered into several in-memory copies; return 413 on BadHttpRequestException.

Addresses audit findings #9, #10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- release.yml: fail the build if DISPATCH_UPDATE_SIGNING_KEY is unset
  instead of publishing an unsigned (always-rejected) upgrade package.
- Ingestion: reject a subject containing control chars up front
  (CRLF header-injection defense-in-depth over MimeKit's encoding).
- SecurityHeaders: add Permissions-Policy disabling camera/mic/geo/usb/payment.
- auth.tsx: password placeholder now says min 12 (matches server policy).
- api.ts sendJson: tolerate a non-JSON error body so the HTTP status
  surfaces instead of a parse error.
- install.sh: correct the stale UMask=0177 comment to 0077.

Addresses audit low/info items (M1, F2, placeholder, Permissions-Policy,
robustness, comment drift).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SQL Server Express install can run 15-20 minutes with a progress bar that
appears frozen, which looks stuck. Give the ExePackage a DisplayName the
bootstrapper shows as the current progress item, reassuring the user it's
normal and to wait.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Step 1 of in-product licensing. Add Dispatch.Core/Licensing: a
LicenseVerifier that authenticates license keys entirely offline (no
call-home), mirroring the FluxDeploy scheme - a 6-byte payload + 64-byte
ECDSA P-256 (SHA-256, IEEE-P1363) signature, Crockford-Base32 encoded as
a typeable XXXXX-XXXXX-... key, verified against an embedded SPKI public
key. Binary licensed/not (no editions); edu is just a free-issued valid
key. Payload: seqId | expiryMonth (0=perpetual) | reserved | flags.
LicenseStatus.Licensed = signature valid AND not expired; fails closed.

The embedded dispatch-license-public.pem is a DEV key; the production
keypair will be generated in the issuer tool and its public half embedded
here (private key never committed). 9 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verify() now checks the signature over payload||machineId, so a key issued
for one install fails on any other (reinstall gets a fresh GUID; a leaked key
won't verify elsewhere) - all still offline, no call-home. Adds an embedded
seqId revocation list (dispatch-revoked-licenses.txt) shipped with each
release, and a Revoked flag on LicenseStatus (Licensed = valid && !expired &&
!revoked). Embeds the real P-256 license public key (DEV key replaced).
13 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- MachineIdentity: mints a stable per-install GUID (SQL config) that keys are
  node-locked to; surfaced for the dashboard.
- LicenseService: evaluates the stored key against this machine + a 30-day
  first-run grace into a LicenseSnapshot (Operational / EnforcementActive);
  validates + stores pasted keys.
- LicenseGate + LicenseWorker: worker re-evaluates on a timer (and on demand)
  and flips the gate; SMTP intake, HTTP ingestion, and the relay worker refuse/
  pause new mail when enforced. Spool + dashboard stay up so a key recovers it.
- Config keys license.{key,machine_id,first_run_utc}; DI in AddDispatchWeb.
- 6 new license-service tests + gate enforcement test; full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GET/POST /api/license: status (machineId, state, expiry, grace, revoked)
  and key entry; POST refreshes the enforcement gate immediately + audits.
- UI: System -> License page (shows Machine ID to send at purchase, license
  state + banner, paste-key box); nav + route wired.
- Messaging: retire the "SMTP relay" tagline for "self-hosted email relay",
  drop "open-source"/"no license check" framing, keep "no call home/offline".
  Homepage GitHub links removed. Docs product-name normalized to "Dispatch".

Note: binding LICENSE file, license.md/contributing/SPEC legal wording, and
the remaining docs GitHub links (downloads/issues/security) are left for a
deliberate licensing + distribution-channel decision - not fabricated here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the AGPL-3.0 + Commons Clause LICENSE file and licence-faq.md; the
product is proprietary, licensed per install via an offline node-locked key,
so no open-source license applies. Reconcile the references that claimed AGPL
or linked to the removed file: README badge + Licence section, the docs
License page, SPEC.md license lines, and the RELEASING signing note.

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

Delete CONTRIBUTING.md and the docs Contributing page; drop the contribution
rows from SPEC.md and the "more providers welcome" invite from the providers
overview. README's provider-adding steps are kept under a neutral "Adding a
provider" dev note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the GitHub Releases download links (private repo - not public) with
https://dispatchrelay.app/download across the deployment docs, and route
security reports to security@dispatchrelay.app in the README to match the
docs. No github.com links remain in the docs site.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove website/ (Astro landing + Starlight docs) and the GitHub Pages workflow
from this private repo - the docs now live in the public-facing dispatch-docs
repo (docs.dispatchrelay.app) and the marketing landing in dispatch-website
(dispatchrelay.app), both on Cloudflare Pages. Keeping a public site's build
out of the private commercial repo is the point. README doc links repointed to
docs.dispatchrelay.app; repo URLs updated to CinderHillsDev/dispatch-relay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous commit accidentally staged the Astro build cache (website/.gitignore
was removed with the split). Remove it - website/ is fully gone now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The product repo (relay service + dashboard + appliance + installers) gets a
more descriptive name. GitHub repo + remote renamed; README clone/badge URLs
and clone dir updated. Assemblies/service names (Dispatch.*, dispatch.service)
are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Node 22 is now in Maintenance LTS (critical fixes only); 24 is the current
Active LTS. Updates the UI-build Node in all workflows and the Dockerfile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Dashboard: a global LicenseBanner (in the Layout, every page) - amber during
  the first-run grace with a day countdown, red once enforcement is active or
  the key is expired/revoked; hidden when licensed; polls so a pasted key clears
  it without a reload.
- Tests: LicenseWorker (gate open during grace, closes past grace, reopens after
  a valid key) + a CidrMailboxFilter enforcement test (MAIL FROM refused when the
  gate is active). Core 129 / Web 98 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dispatch is now free and open source under the Apache License 2.0. Removed
the entire offline license-key system:

- Delete src/Dispatch.Core/Licensing/ (verifier, service, worker, gate,
  machine-identity, embedded public key + revocation list), the /api/license
  endpoint, the dashboard License page/banner, and the 3 license test files.
- Remove the three runtime enforcement points that paused/refused mail when
  "unlicensed past grace": SMTP intake (CidrMailboxFilter), HTTP API intake
  (ApiMessageHandler), and the relay worker (SpoolWorkerPool).
- Drop DI registrations, the LicenseWorker hosted service, the license.*
  config keys, and the csproj embedded-resource entries.
- Add Apache-2.0 LICENSE; update README, docs/SPEC.md, docs/RELEASING.md.

Build clean, all 312 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps the ui group in /src/Dispatch.UI with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@microsoft/signalr](https://github.com/dotnet/aspnetcore) | `8.0.17` | `10.0.0` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.101.0` | `5.101.2` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `18.3.1` | `19.2.7` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.3.31` | `19.2.17` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `18.3.1` | `19.2.7` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `18.3.7` | `19.2.3` |
| [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.17.0` | `7.18.1` |
| [recharts](https://github.com/recharts/recharts) | `2.15.4` | `3.9.2` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.7.0` | `6.0.3` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.9.3` | `7.0.2` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `6.4.3` | `8.1.4` |


Updates `@microsoft/signalr` from 8.0.17 to 10.0.0
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](dotnet/aspnetcore@v8.0.17...v10.0.0)

Updates `@tanstack/react-query` from 5.101.0 to 5.101.2
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.101.2/packages/react-query)

Updates `react` from 18.3.1 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react)

Updates `@types/react` from 18.3.31 to 19.2.17
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 18.3.1 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-dom)

Updates `@types/react-dom` from 18.3.7 to 19.2.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `react-router-dom` from 7.17.0 to 7.18.1
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@7.18.1/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.18.1/packages/react-router-dom)

Updates `recharts` from 2.15.4 to 3.9.2
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](recharts/recharts@v2.15.4...v3.9.2)

Updates `@types/react` from 18.3.31 to 19.2.17
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 18.3.7 to 19.2.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@vitejs/plugin-react` from 4.7.0 to 6.0.3
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.3/packages/plugin-react)

Updates `typescript` from 5.9.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/commits)

Updates `vite` from 6.4.3 to 8.1.4
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.1.4/packages/vite)

---
updated-dependencies:
- dependency-name: "@microsoft/signalr"
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.101.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ui
- dependency-name: react
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: react-dom
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: react-router-dom
  dependency-version: 7.18.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ui
- dependency-name: recharts
  dependency-version: 3.9.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: ui
- dependency-name: vite
  dependency-version: 8.1.4
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: ui
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Jul 9, 2026
@dependabot @github

dependabot Bot commented on behalf of github Jul 10, 2026

Copy link
Copy Markdown
Author

This pull request was built based on a group rule. Closing it will not ignore any of these versions in future pull requests.

To ignore these dependencies, configure ignore rules in dependabot.yml

@dependabot
dependabot Bot deleted the dependabot/npm_and_yarn/src/Dispatch.UI/ui-f94088bdf1 branch July 10, 2026 03:38
chrismuench added a commit that referenced this pull request Jul 20, 2026
Full security and performance review of the merged multi-database work. Security
found no exploitable issues (details below); performance found one real correctness-
of-claims bug and two efficiency issues. Fixed:

PERF #1 (high) - the ingest hot path was NOT pooled in production. DI registered the
non-pooled AddDbContextFactory while only DispatchDbContextFactory.Create (used by
the migrator and tests) was pooled. So the measured ~3,500 writes/sec came from the
pooled TEST path, while production - SpoolWorkerPool creating a context per
lifecycle-event insert and counter upsert, from many threads - ran the non-pooled
path at roughly a third of that. My own comment claimed DI registered pooled; it did
not. This is the exact "benchmark measures the wrong path" trap this whole effort
kept surfacing, this time in my own number. Now AddPooledDbContextFactory, and
DependencyInjectionTests asserts the resolved factory is pooled so it cannot drift
again.

PERF #2 (medium) - the Message Log dashboard read (PageAsync, the main list) grouped
by (spool_id, CASE WHEN spool_id='' THEN id ELSE 0), whose CASE key is non-sargable:
EXPLAIN showed USE TEMP B-TREE FOR GROUP BY, i.e. a full sort of every matched row,
seconds per page on a large table under the default broad filter. Split into a
sargable GROUP BY spool_id arm UNIONed with the anonymous (empty-spool) rows - EXPLAIN
now serves the grouping straight from IX_relay_log_spool_id with no temp B-tree.
Results are identical; the dedup test is strengthened to two denials (empty spool_id
rows must each stay their own message, never collapse) and passes on all four engines.

PERF #3 (low) - the storage admin page called GetTableSizeBytesAsync twice (relay_log,
audit_log); on SQLite each runs a whole-file sampling pass including a linear COUNT,
so it ran twice per page load. Added GetTableSizesBytesAsync (default loops; SQLite
overrides to one pass) and the report now asks for both at once.

SECURITY - reviewed and clean:
  * SQL injection: the only raw SQL left in the repositories is the counter upsert,
    whose column is enum-derived and values are parameters. All user input goes
    through EF LINQ (parameterised). Provider raw SQL carries only fixed table names
    (SafeIdentifier-guarded) and admin-config database names (per-engine escaped).
  * Command exec: migrate-database's chown uses ArgumentList (no shell). Hardened
    anyway to an absolute /usr/bin/chown path rather than PATH lookup, since it runs
    as root.
  * Credential exposure: connection strings are never logged - only the provider enum.
  * Encrypted config: AES-256-GCM, 600-perm key, decryption failure caught and
    treated as unset. Copied as ciphertext by the migrator, never decrypted in transit.
  * Auth vs collation: the case-sensitive collation STRENGTHENS API-key lookup - on a
    stock case-insensitive SQL Server install, key ids would match case-insensitively
    and the unique index would collide near-misses. Constant-time bcrypt paths intact.

72 repository tests pass on SQLite, PostgreSQL, MariaDB and SQL Server.

Co-authored-by: Chris Muench <chris@MacBook-Air.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant