Three files carried a literal NUL byte inside a string: the admin
check's cache key in `app/api/admin/auth/route.ts`, the global search
hit key in `lib/global-search/types.ts` and two TNEF fixtures in
`lib/__tests__/tnef.test.ts`. Git treats any file containing a NUL as
binary, so it showed no diffs for them, never normalised their line
endings, and reported every pull request that touched the admin route
as a conflict even when a text merge was clean.
The escape produces the same string at runtime. The admin route was
stored with CRLF while git saw it as binary; it is now stored with LF
like the rest of the tree, so its diff covers the whole file (the only
content change is on line 70).
The Stalwart admin check only bailed while `allowCustomJmapEndpoint` was
on. A context cookie minted while it was on keeps naming the
user-chosen server, and the envelope has no expiry, so after the admin
turned the switch off that server could still answer the admin probe
and mint a Bulwark admin session. The check now also requires the
cookie's server to be an admin-configured one.
The Stalwart admin check probed `x:Account/query`. Stalwart's default
tenant-admin role carries `sysAccount*`, so an administrator of a single
tenant passed and, in the default `auto` mode, was signed into the
Bulwark dashboard, which configures the whole instance for every
tenant. The `auto|password|off` switch added for #870 left the default
at `auto`, so the advisory never held on a default install.
The probe is now `x:Tenant/query`, which needs `sysTenantQuery`: by
default only the server-wide superuser holds it.
The setup wizard and the admin sign-in set their session cookies by two
different rules. The wizard matches Secure to the request - X-Forwarded-
Proto from the proxy, else the URL scheme - because browsers drop Secure
cookies on plain HTTP and an unconditional Secure broke setup over HTTP.
The admin cookie kept `secure: NODE_ENV === 'production'`, so on the same
HTTP deployment the wizard worked and admin sign-in failed silently: the
cookie was set, the browser threw it away, every call came back 401.
Both now share one helper, lib/security/request-protocol.ts, and the admin
route passes its request in. Behind a TLS proxy that forwards the proto
nothing changes: the cookie is Secure as before. Callers that pass no
request keep the production default.
Drops the 0.15 REST management API and routes all account/auth/crypto/
principal operations through Stalwart 0.16's schema-driven JMAP
endpoint via a single passthrough (/api/account/stalwart/jmap).
- New client helper `stalwartJmap` + typed `requireResult`
- account-security-store rewritten against x:AccountPassword, x:AppPassword,
x:AccountSettings, x:Account (with currentSecret for TOTP ops)
- Client-side TOTP setup via `otpauth`; server-generated app password
secrets shown once on create
- Admin check switched to /api/account permissions
(sysAccountQuery/sysTenantQuery/sysSystemSettingsGet)
- Removed sieve vacation-overwrite workaround (fixed upstream #1251)
- Deleted old REST routes, StalwartClient, stale tests; added new
tests for passthrough + store