Skip to content

Fix cross-host outbox delivery + High-severity NuGet vulns (3.1.1)#170

Merged
JasonMWebb merged 4 commits into
mainfrom
bugfix/outbox-cross-host-immediate-dispatch
Jul 14, 2026
Merged

Fix cross-host outbox delivery + High-severity NuGet vulns (3.1.1)#170
JasonMWebb merged 4 commits into
mainfrom
bugfix/outbox-cross-host-immediate-dispatch

Conversation

@JasonMWebb

Copy link
Copy Markdown
Collaborator
  • Cross-host outbox fix: adds OutboxOptions.ImmediateDispatch (default true, back-compat). When false, commit persists but skips Phase-3 in-process dispatch/mark, so a separate poller is the sole dispatcher — fixes silent cross-host delivery loss. Plus defense-in-depth (no mark-processed when no in-process producer matched).
  • EFCore fix: DeleteProcessedAsync/DeleteDeadLetteredAsync DateTimeOffset comparison now SQLite-translatable.
  • Security: Microsoft.OpenApi → 2.7.5 (CVE-2026-49451); SQLitePCLRaw.lib.e_sqlite3 → 3.53.3 (CVE-2025-6965).
  • Docs/CI: Outbox topology guide, docs version 3.1.1, 3.1.1 changelog; CI publishes production only on version-tag push (no more pre-releases). Website npm vulns deferred to a follow-up spec.
  • Verified: test suites pass; dotnet list --vulnerable clean; docs build OK.

In a producer-host / separate-poller-host topology, the unit of work's
best-effort post-commit dispatch (Phase 3) marked outbox rows processed
on the producing host even when no subscriber consumed the event. The
poller on the other host only claims rows where ProcessedAtUtc IS NULL,
so it never saw them and cross-host delivery was silently lost.

Add OutboxOptions.ImmediateDispatch (default true, fully back-compat).
When false, OutboxEventRouter.RouteEventsAsync skips Phase 3 entirely
(no in-process dispatch, no MarkProcessedAsync), leaving the durable
poller as the sole dispatcher/marker. Producer-only hosts set it false.

As defense-in-depth, RouteEventsAsync no longer marks a row processed
when no in-process producer matched the event. This is intentionally
narrow: an in-memory producer that no-ops on no subscriber still counts
as dispatched, so ImmediateDispatch remains the cross-host fix.

Also fix EFCoreOutboxStore.DeleteProcessedAsync/DeleteDeadLetteredAsync,
whose DateTimeOffset comparison could not be translated by the SQLite
provider; the comparison is now evaluated client-side (mirroring
ClaimAsync) while the null-check stays in the query.

Docs: add the "Outbox Producer/Processor Topology" conceptual guide
covering the three-phase commit, the cross-host invariant, and when/by
whom rows are marked processed; cut Docusaurus docs version 3.1.1.
Add a future-enhancement spec proposing a first-class producer/processor
topology API.
Resolve two High-severity advisories flagged by dotnet list --vulnerable:

- Microsoft.OpenApi 2.4.1 (CVE-2026-49451, GHSA-v5pm-xwqc-g5wc), pulled
  transitively via Swashbuckle 10.x on the net10 target of
  RCommon.Authorization.Web. Pin the patched 2.x (2.7.5) directly; net8/net9
  use Swashbuckle 8/9 -> OpenApi 1.x and are unaffected.
- SQLitePCLRaw.lib.e_sqlite3 2.1.11 (CVE-2025-6965, GHSA-2m69-gcr7-jv3q),
  the native SQLite shipped transitively into the SQLite-backed test
  projects. Override the native binary to 3.53.3 (SQLite >= 3.50.2); the
  managed core is untouched (SQLite's C ABI is backward-compatible).

Website (Docusaurus) npm advisories are deferred: the fix requires a 3.4->3.10
upgrade that breaks 69 swizzled theme components. Tracked in a follow-up spec.
These are build-time-only tooling for a static site (no code ships to visitors).

Add a 3.1.1 changelog entry (current docs + 3.1.1 snapshot; 2.4.1 preserved)
covering the outbox fix, the EFCore SQLite cleanup fix, and these security fixes.
The publish job previously ran only on pushes to main, and the package
version comes from MinVer. On an untagged main commit MinVer resolves to a
pre-release (currently 3.1.0-alpha.3.N), so merging a PR could never produce
a clean production release — MinVer only emits a production version when HEAD
sits exactly on a release tag.

Trigger the workflow on version-tag pushes (NNN.NNN.NNN) and allow the publish
job to run for tag refs. Pushing tag 3.1.1 now builds and publishes the exact
production version. Rolling pre-release publishing on main pushes is unchanged.
Gate the publish job to version-tag pushes only. Pushes to main now run
build-and-test for validation but no longer publish, so MinVer's pre-release
versions (e.g. 3.1.0-alpha.3.N from untagged main commits) are never pushed to
NuGet. Production releases are cut solely by pushing a version tag (e.g. 3.1.1).
@JasonMWebb JasonMWebb merged commit 63ad76e into main Jul 14, 2026
3 checks passed
@JasonMWebb JasonMWebb deleted the bugfix/outbox-cross-host-immediate-dispatch branch July 14, 2026 08:04
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