August 5, 2026
A Real-Browser-Only Login Bug, DMX Automation at Church, and Bridging the Gap
A login bug that had been quietly haunting GitLab behind rust-proxy for a while finally gave up its actual root cause: an HTTP/2 edge case that no scripted test could ever reproduce, only real browsers. Then a full workday at church rewiring how the lighting, games room, and displays all talk to each other, and dinner with Lisa at her work's Bridging the Gap event to close it out.
The GitLab login bug that only ever broke for real browsers
GitLab behind rust-proxy
had a login failure that made no sense on paper: a 422
InvalidAuthenticityToken, 100% reproducible in real Chrome and Edge, on desktop
and mobile, every single time — and completely unreproducible with curl or a scripted httpx
client using the exact same credentials. That split is the kind of clue that usually means the
bug lives in something a script simply doesn't do the way a real browser does, so the fastest
way to find it was to stop guessing and drive an actual browser engine through the problem:
installed Playwright, ran headless Chromium, and pointed it at the real login form.
The investigation ruled out a long list of reasonable suspects along the way — stale browser
cache, extensions, different networks and devices, IPv6, connection reuse, concurrent request
bursts, special characters in the password, timing — before the real culprit showed up in a
raw TCP byte capture. I temporarily redirected rust-proxy's upstream for the GitLab host to a
throwaway Python socket listener, captured the exact bytes rust-proxy was forwarding, and
restored the real upstream immediately after. Sitting right there in the capture: four separate
cookie: header lines, instead of one. That's legal — RFC 7540 §8.1.2.5 lets an
HTTP/2 client split its Cookie header into multiple fields for better compression,
and real Chromium and Firefox both do it. Curl, httpx, and Node's fetch don't,
which is exactly why every scripted reproduction attempt sailed through. GitLab's HTTP layer,
like most HTTP/1.1 server frameworks, only reads the first Cookie: line off the
wire — and the session cookie happened to be sitting in the second of the four. GitLab was
never seeing a session at all, so it rejected the CSRF token issued for a session it could no
longer see. rust-proxy now merges every split cookie header into one line, per
spec, before forwarding to an HTTP/1.1 upstream — confirmed the fix actually works by watching
that same headless Chromium sail through the password step that used to 422 every time, then
confirmed it for real logging in with a passkey in one browser and 2FA in another.
That capture technique paid off twice more along the way, closing out a few other real gaps
in how rust-proxy talks to upstreams: an HTTP/2 request's absolute-form URI was being forwarded
to HTTP/1.1 upstreams unchanged, which happened to work by accident against nginx but got
correctly rejected as malformed by a stricter upstream (Proxmox's own pveproxy) —
now downgraded to origin-form before forwarding. The real client IP is now forwarded to every
upstream via X-Forwarded-For/-Proto/-Host, instead of
every backend seeing every request as coming from the proxy itself. And a per-host option now
lets an operator downgrade an upstream's own SameSite=None cookies to
SameSite=Lax, for the case where the backend application sets that unconditionally
and modern browsers quietly drop the cookie for it. All four fixes, tests, and a changelog
write-up are pushed.
A church workday wiring BitFocus Companion into Vista DMX and Hubitat
Big day at the church workday: finally got BitFocus Companion talking to Vista DMX to control
the room's lighting directly, which meant pulling a whole layer of DMX calls out of
ProPresenter — ProPresenter's job goes back to just running the presentation, not also driving
lighting cues by hand. On top of that, Companion now watches which Look is active in
ProPresenter and automates the room's lighting and coloring to match it, with the Stream Deck's
own buttons changing color to reflect whatever mode the lights are currently in — so a glance
at the deck tells you the current lighting state without checking the room itself.
Also fixed up the Companion-to-Hubitat
side of things, which controls the room's games, the
prize counter, and the Govee Lightwalls — that communication path had some real issues that
got sorted out during the workday. And closed out a lingering problem from the power outage a
couple of posts back: the Logitech Harmony Hub had broken during that outage and hadn't been
right since, which meant the projector and the stage display screens couldn't be turned on and
off through the normal Hubitat/Companion integration. That's fixed now too, so the whole
chain — Stream Deck button press, through Companion, through Hubitat, out to the Harmony
Hub — turns the projector and stage screens on and off on demand again.