┌────────────────────────────┐
│When Bulwark Webmail Disowns│
│ You: Tracking Down Session │
│  Cache Desynchronization   │
│ 2026-09-05                 │
│                            │
├────────────────────────────┤
│ << Back to Blog            │
└────────────────────────────┘
╔══════════════════════════════════════╗
║  When Bulwark Webmail Disowns You:   ║
║     Tracking Down Session Cache      ║
║          Desynchronization           ║
║ 2026-09-05                           ║
║                                      ║
╠══════════════════════════════════════╣
║ << Back to Blog                      ║
╚══════════════════════════════════════╝
╔══════════════════════════════════════════════════════════╗
║ When Bulwark Webmail Disowns You: Tracking Down Session  ║
║                 Cache Desynchronization                  ║
║ 2026-09-05                                               ║
║                                                          ║
╠══════════════════════════════════════════════════════════╣
║ << Back to Blog                                          ║
╚══════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════╗
║        When Bulwark Webmail Disowns You: Tracking Down Session Cache         ║
║                              Desynchronization                               ║
║ 2026-09-05                                                                   ║
║                                                                              ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ << Back to Blog                                                              ║
╚══════════════════════════════════════════════════════════════════════════════╝

When Bulwark Webmail Disowns You: Tracking Down Session Cache Desynchronization

Table of Contents

  1. The Mysterious White Screen of Death
  2. Is It the IMAP Backend or the Frontend?
  3. The Redis Session Store Investigation
  4. Cookie Domain Scoping Across Subdomains
  5. The Durable Fix
  6. References

The Mysterious White Screen of Death

A few hours after resolving our BGP anycast mail routing issue, our webmail users encountered a bizarre failure mode on https://spam.house:

Users could enter their username and password on the login screen. Authentication succeeded with an HTTP 200 OK. But the moment the webmail inbox attempted to render:

  • The screen went completely blank.
  • The browser console logged: TypeError: Cannot read properties of undefined (reading 'mailboxes').
  • No folders appeared, and the spinning loader froze indefinitely.

Even stranger: if a user opened an incognito window and logged in, everything rendered perfectly. But the moment they closed the browser and reopened it an hour later, the blank screen returned.


Is It the IMAP Backend or the Frontend?

We checked the backend mail server logs inside the Stalwart container:

2026-09-05T14:16:10Z [INFO] [JMAP] User authenticated: admin@spam.house
2026-09-05T14:16:10Z [INFO] [JMAP] Mailbox/get returned 12 mailboxes (OK)

The mail daemon was answering JMAP and IMAP queries without errors. The server returned all 12 mailboxes (Inbox, Sent, Drafts, Trash, Archive, etc.) in a single JSON payload.

So why was Bulwark's frontend complaining that mailboxes was undefined?


The Redis Session Store Investigation

Bulwark maintains an active session and cache layer backed by Redis to avoid spamming the core mail server with redundant folder listing queries on every page refresh.

We inspected the Redis cache keys for an affected session:

redis-cli -h 127.0.0.1 -p 6379 keys "sess:*"

We dumped the cached JSON state:

redis-cli get "sess:admin@spam.house"
{
  "user": "admin@spam.house",
  "token": "eyJhbGciOi...",
  "cache_version": 2,
  "state": "stale"
}

Notice the key: "state": "stale", with no mailboxes array!

When a session lived longer than the cache TTL (3,600 seconds), Bulwark marked the cache entry as stale and triggered a background async refresh task.

However, because we had recently adjusted reverse proxy timeouts in Caddy, the background refresh was timing out at 2.0 seconds before the JMAP socket responded. Bulwark caught the timeout, left the cache object in a truncated half-state, and served the incomplete JSON back to the frontend JavaScript.

The frontend client expected response.data.mailboxes to always be an array. When handed the truncated stale payload, it choked on an unhandled undefined dereference and crashed the React rendering tree!


There was a second contributing factor: Cookie domain leakage.

Because our reverse proxy was serving both spam.house (Bulwark webmail) and admin.spam.house (Stalwart management portal), session cookies were set with:

Set-Cookie: session_id=...; Domain=.spam.house; Path=/; Secure; HttpOnly

Notice the leading dot: Domain=.spam.house. In standard browser cookie rules, a cookie set for .spam.house is transmitted to all subdomains, including admin.spam.house!

When users switched tabs between the webmail and the admin panel, the two applications were overwriting each other's session IDs with incompatible JWT formats, poisoning the Redis session lookup table on every tab switch!


The Durable Fix

The fix required addressing both the frontend fallback and the cookie domain isolation:

1. Scope Cookies Strictly to Hostnames

In Caddy, we stripped the global domain wildcard and pinned cookies strictly to the exact host:

spam.house {
    # Ensure session cookies do NOT leak to admin.spam.house
    header Set-Cookie "Domain=spam.house"
}

2. Defend Against Truncated Cache Payloads

In Bulwark's caching layer, we patched the session retrieval logic:

  • If a cached session is marked stale and a refresh times out, always fall back to a synchronous direct fetch from the mail daemon rather than serving the broken cache object.
  • Added defensive null checks in the frontend client so missing mailbox lists trigger an automatic retry rather than a blank screen crash.

We flushed the poisoned Redis sessions:

redis-cli flushdb

And reloaded the application. Users refreshed their browsers, their complete mailboxes rendered instantly, and tab switching between the admin console and webmail worked without a single glitch.


References