fix: forward caller-supplied Authorization header in H1 and H2c paths#3
Conversation
|
Looks like CI failure is known flake. |
|
Updated PR pushed — addressed your two points:
Note: I could not run the tests locally because the flare test suite has pre-existing nightly incompatibilities ( The test names follow the existing convention (fork_server loopback against an in-process HttpServer, same as the bearer-auth tests above). |
|
Sounds good. I'll verify. Will update b2 once released in stable. |
GitHub Actions' ``ubuntu-latest`` image moved from Ubuntu 22.04 to
24.04, and the April 24 Mojo nightly
(``mojo 1.0.0b1.dev2026042405``) crashes immediately inside its
own runtime on 24.04. The first test of the suite,
``tests/test_net.mojo``, prints its banner and then segfaults
before any user code runs:
test_net.mojo — IpAddr, SocketAddr, error types
============================================================
#0 0x... libKGENCompilerRTShared.so+0x6f0eb
ehsanmok#1 0x... libKGENCompilerRTShared.so+0x6c2c6
ehsanmok#2 0x... libKGENCompilerRTShared.so+0x6fd97
ehsanmok#3 0x... libc.so.6+0x45330
#4 0x...
mojo: error: execution crashed
Process completed with exit code 1.
The crash reproduces 100% on ``ubuntu-latest`` (``Image: ubuntu-24.04``,
``Image Release: ubuntu24/20260413.86``) on Mojo 1.0.0b1.dev2026042405
and is independent of flare's source: the same compiler build runs
clean on Ubuntu 22.04 (this repo's dev box, glibc 2.35) and on
macOS 14. It's a Mojo runtime / glibc-2.39 ABI issue.
Workaround: pin both Linux jobs to ``ubuntu-22.04`` so they keep
running while Modular re-cuts a 24.04-clean nightly:
- ``.github/workflows/ci.yml`` (the ``Tests`` matrix).
- ``.github/workflows/fuzz.yml`` (the nightly fuzz matrix —
same ``mojo`` runtime, would hit the same segfault as soon
as the cron fires).
Docs CI (``.github/workflows/docs.yaml``) stays on
``ubuntu-latest`` because mojodoc is compile-only and doesn't
exercise the runtime; once the previous commit's source fix lands
the docs build is healthy on 24.04.
When upstream ships a 24.04-clean nightly, revert this commit
(or just ``-os: [ubuntu-22.04, macos-14]`` -> ``ubuntu-latest``)
and a green nightly CI run will confirm.
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
…2405 segfault)
The April 24 ``mojo 1.0.0b1.dev2026042405`` nightly crashes 100%
inside ``libKGENCompilerRTShared.so`` on every Linux GitHub Actions
runner before any user code runs. The very first test of the
suite (``tests/test_net.mojo``) prints its banner and then SIGSEGVs
in the Mojo runtime:
test_net.mojo — IpAddr, SocketAddr, error types
============================================================
#0 0x... libKGENCompilerRTShared.so+0x6f0eb
ehsanmok#1 0x... libKGENCompilerRTShared.so+0x6c2c6
ehsanmok#2 0x... libKGENCompilerRTShared.so+0x6fd97
ehsanmok#3 0x... libc.so.6+0x45330
mojo: error: execution crashed
Process completed with exit code 1.
Reproduced on both ``ubuntu-latest`` (Ubuntu 24.04, which is what
``ubuntu-latest`` resolves to right now) AND ``ubuntu-22.04`` —
ruling out the OS-pin workaround that ``5e28b84`` was attempting.
The previous nightly ``mojo 1.0.0b1.dev2026042317`` ran the same
code-base green (the cron-triggered CI run at 2026-04-24T03:00 on
commit ``6ea8a05`` proves that), but pixi-build resolves
``mojo-compiler`` for the inline ``json`` build separately from
flare's runtime ``mojo`` and grabs the latest available — so even
pinning flare's ``mojo`` to ``1.0.0b1.dev2026042317`` leaves
``json.mojopkg`` built against the broken nightly, and the loader
then refuses it with
Mojo package is incompatible: package
'/home/.../json.mojopkg' version 26.3.0.dev2026042405 is
newer than compiler version 26.3.0.dev2026042317
The right fix is the same one ``v0.4.0`` already shipped: pin
flare AND json AND ``recipe.yaml`` to the last known-good
combination, which is
mojo = "==0.26.3.0.dev2026042005"
json = { git = ..., tag = "v0.1.3" }
mojo-compiler = "==0.26.3.0.dev2026042005" (in recipe.yaml)
``json v0.1.3``'s own recipe also pins ``mojo-compiler`` to
``0.26.3.0.dev2026042005`` (verified via ``gh api
repos/ehsanmok/json/contents/recipe.yaml?ref=v0.1.3``), so the
build env that pixi-build solves for ``json`` ends up identical
to flare's runtime env. CI of the
``Release v0.4.0: pin mojo to 0.26.3.0.dev2026042005, json to
v0.1.3`` commit (``e438400``) was green on
``ubuntu-latest`` + ``macos-14`` with this exact triple.
Verified locally
----------------
- ``pixi run format-check``: clean.
- ``pixi run tests``: 26/26 test files green, 460 + 18 examples
pass, total 5m14s.
- ``pixi run -e dev mojodoc ./flare --out-dir /tmp/doc-out``:
documentation builds in 6s, no errors.
- json package on disk now reports
``...dev2026042005`` (rather than ``...dev2026042405``);
``flare.http.HttpServer.bind(...)`` round-trips a port.
Revert this commit once Modular re-cuts a clean nightly and
flare's ``v0.4.2`` / next-release commit re-unpins mojo + json
the way ``3c96790`` did for v0.3.0.
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
… crash)
The nightly Fuzz workflow's ``ubuntu-latest`` job has been
crashing 100% on the Linux runner for the last two scheduled
runs (#64 / #65, 2026-04-26 / 2026-04-27 02:00 UTC), with the
documented Mojo-runtime-on-Ubuntu-24.04 signature:
✨ Pixi task (fuzz-ws in fuzz): mojo -I . fuzz/fuzz_ws_frame.mojo
[mozz] fuzzing WsFrame.decode_one()...
#0 0x... libKGENCompilerRTShared.so+0x6dc0b
ehsanmok#1 0x... libKGENCompilerRTShared.so+0x6ade6
ehsanmok#2 0x... libKGENCompilerRTShared.so+0x6e8b7
ehsanmok#3 0x... libc.so.6+0x45330
mojo: error: execution crashed
Same shape as the ``5e28b84`` (``ci: pin Linux runner to
ubuntu-22.04``) crash that was reverted in ``e9ae53e`` once
``ci.yml``'s test workload returned to ``ubuntu-latest``-clean.
The fuzzer's hot loop -- millions of small ``WsFrame.decode_one``
calls inside mozz's runner -- still trips the same
``libKGENCompilerRTShared.so`` -> ``libc.so.6+0x45330`` path on
24.04 / glibc 2.39 even though the unit-test workload no longer
does. macOS-14 keeps passing on the same nightly + the same
harness; this is an Ubuntu-24.04-only defect.
This commit:
- ``.github/workflows/fuzz.yml``: change matrix from
``[ubuntu-latest, macos-14]`` to ``[ubuntu-22.04, macos-14]``.
Adds an in-place comment explaining the crash signature, the
asymmetric scope (fuzz only, ci.yml stays on ubuntu-latest),
and the revert condition (next 24.04-clean Mojo nightly).
We do NOT pin ``ci.yml``'s Linux runner here -- the test
workload runs cleanly on ubuntu-latest with the cert-path fix
in the previous commits, and pinning it would be premature.
Tests:
- macOS-14 leg of Fuzz already green on the same nightly, so the
crash is genuinely runner-shape-specific.
- ubuntu-22.04 ran the full fuzz suite green for #59-#62 before
the runner-pin revert; the fuzz harness itself hasn't changed.
Co-authored-by: Cursor <cursoragent@cursor.com>
…rash on 24.04)
Follow-up to ``ci: re-pin Linux fuzz runner to ubuntu-22.04
(libKGENCompilerRTShared crash)``: the same Mojo-runtime-on-
Ubuntu-24.04 crash that hit fuzz now also hits ``ci.yml``.
The first push after the fuzz-only pin (run #25011238337,
commit ``2ddfd33``) reproduced the crash on ``ubuntu-latest``
(Image: ubuntu-24.04, Image Release rolled forward overnight)
during ``pixi run tests`` -- ``tests/test_net.mojo`` segfaults
before any user code runs:
test_net.mojo — IpAddr, SocketAddr, error types
============================================================
#0 0x... libKGENCompilerRTShared.so+0x6dc0b
ehsanmok#1 0x... libKGENCompilerRTShared.so+0x6ade6
ehsanmok#2 0x... libKGENCompilerRTShared.so+0x6e8b7
ehsanmok#3 0x... libc.so.6+0x45330
#4 0x... 7f854c0102fe
mojo: error: execution crashed
Process completed with exit code 1.
Per-frame offsets are byte-identical to the fuzz crash signature
documented in the previous commit -- same Mojo runtime defect,
just exposed by a different workload (the test suite's startup
path rather than the fuzzer's hot loop). The previous commit's
narrower scope was wrong: pinning only fuzz.yml doesn't cover
ci.yml because the GH-Actions ``ubuntu-latest`` image had drifted
between the prior successful run (CI #108, head ``838e205``,
2026-04-26 03:00 UTC) and today's push, taking the test workload
into the crashing region too.
Macos-14 keeps passing on the exact same Mojo nightly + the exact
same test source -- this is a Linux-runtime / glibc-2.39 ABI
issue, not a flare-side regression. The ``test_tls_acceptor``
fix (``fix(tls): drop hardcoded /Users/ehsan paths...``) and
the cert-generation step (``ci: generate self-signed cert
before Tests step``) still hold; they were confirmed clean on
macos-14 in this same run before the Linux job aborted.
What changes:
- ``.github/workflows/ci.yml``: matrix swaps from
``[ubuntu-latest, macos-14]`` to ``[ubuntu-22.04, macos-14]``.
Adds an in-line comment with the crash signature and the
revert condition, mirroring the comment block fuzz.yml now
carries.
The docs workflow stays on ``ubuntu-latest`` because mojodoc is
compile-only -- it doesn't exercise the Mojo runtime and was
green on the same head in this same set of pushes.
Co-authored-by: Cursor <cursoragent@cursor.com>
…ump CI runner to macos-15 The macOS-14 CI lane was crashing inside ``libKGENCompilerRTShared.dylib`` on the very first call to ``_brotli_available()`` and again under ``compress_brotli`` / ``decompress_brotli``. Same per-frame offsets every run (#0 .dylib+0x40528, ehsanmok#1 .dylib+0x3dba8, ehsanmok#2 .dylib+0x40f84, ehsanmok#3 libsystem_platform.dylib+0x1804e5764), reproduces 3/3 locally on ``Darwin arm64 25.3.0`` / Mojo nightly ``1.0.0b1.dev2026042717``. Linux happens to be lazier about reclaiming the local on this nightly, so the bug only fires on macOS — but it's a real lifetime bug in flare, not a Mojo runtime flake the way the prior advisory commits framed it. Root cause: the ``_file_exists`` helper in ``flare/http/middleware.mojo`` and the brotli compress/decompress paths in ``flare/http/encoding.mojo`` all opened a function-local ``OwnedDLHandle``, fetched a function pointer via ``lib.get_function[def(...) thin abi("C") -> c_int](...)``, then called the pointer without ever referencing ``lib`` again. Mojo's ASAP-destruction rule lets the runtime drop the handle right after the last read; the destructor calls ``dlclose``, the dylib is unmapped, and the cached function pointer dangles into freed memory by the time we invoke it. The next call segfaults inside the runtime call helper. Fix: route every FFI call through a helper that takes the handle as ``read lib: OwnedDLHandle`` (borrow), so the dylib's lifetime is anchored to the outer scope where ``lib`` is owned. This is the same idiom the zlib path already uses (``_do_compress``, ``_do_decompress``, ``_do_decompress_deflate``); brotli and ``_file_exists`` were the two spots that hadn't been ported. - ``flare/http/middleware.mojo``: introduce ``_flare_fs_access(read lib, addr)`` and call it from ``_file_exists``. Comments document the ASAP-destruction trap so the next contributor doesn't reintroduce it. - ``flare/http/encoding.mojo``: introduce ``_do_compress_brotli`` and ``_do_decompress_brotli`` with borrowed handles; the public ``compress_brotli`` / ``decompress_brotli`` open the handle and delegate while it's still in scope. CI: ``.github/workflows/ci.yml`` switches the macOS leg from ``macos-14`` to ``macos-15`` (Sequoia, arm64) so the runner matches the host SDK we develop against. The advisory + 3-attempt retry workarounds are gone (they were never in this commit's parent — those landed on top of v0.6.0 and were reset out alongside this fix). macOS is mandatory again. Verified locally: ``pixi run tests`` exits 0 with 961 tests passing across 60 test files plus 36 examples on this MacBook. ``mojo format`` clean. Co-authored-by: Cursor <cursoragent@cursor.com> Made-with: Cursor
…dancy in Router.get Closes critique TL;DR section 1 sub-items ehsanmok#2 (manual ``__init__`` exposure) and ehsanmok#3 (``Router.get[Extracted[H]]`` turbofish redundancy): * The four-trait conformance line every ``Extracted[H]``-mounted handler struct used to write -- ``(Copyable, Defaultable, Handler, Movable)`` -- collapses to a single name ``HandlerExtractor`` via Mojo trait composition. * The turbofish ``router.get[Extracted[GetUser]]("/users/:id", Extracted[GetUser]())`` was over-specified all along: today's Mojo infers ``H`` from the runtime arg, so ``router.get("/users/:id", Extracted[GetUser]())`` works. Surface changes: * ``flare/http/handler.mojo`` -- new ``trait HandlerExtractor(Copyable, Defaultable, Handler, Movable): pass``. Pure trait composition; no new methods, no new behaviour. The four conformances exactly match what ``Extracted[H]`` (``H: Copyable & Defaultable & Handler & Movable``) requires + the parametric ``Router.get[H: Handler & Copyable & Movable]`` overload consumes for boxed-handler registration. * ``flare/http/__init__.mojo`` -- re-exports ``HandlerExtractor`` from the ``handler`` module alongside ``Handler`` / ``HandlerInfallible`` / ``WithRaises`` / ``CancelHandler`` / ``ViewHandler`` / ``FnHandler``. * ``flare/prelude.mojo`` -- adds ``HandlerExtractor`` to the re-exported ``Handler`` family. Updates the prelude docstring's beginner snippet to use the working ``srv.serve(r^)`` shape (the prior ``num_workers=4`` shape there was only ever valid for Copyable handlers, which ``Router`` isn't yet -- see the v0.7 single-worker overload commit + the v0.7.x roadmap for Router-Copyable). * ``examples/intermediate/extractors.mojo`` -- migrates ``GetUser`` from ``(Copyable, Defaultable, Handler, Movable)`` to ``(HandlerExtractor)``, drops the turbofish on the ``Router.get`` registration, drops the now-unused ``Handler`` import, and adds a docstring paragraph explaining why the manual ``__init__`` body is still required (Mojo doesn't yet auto-derive a no-arg constructor from per-field ``Defaultable`` impls -- once it lands, the body collapses to ``pass``). * ``tests/test_handler_extractor.mojo`` -- new, 4 tests: - ``test_handler_extractor_satisfies_handler_bound`` -- a ``HandlerExtractor`` struct flows through a ``[H: Handler]``-generic helper. - ``test_handler_extractor_flows_through_extracted`` -- ``Extracted[H]`` accepts a ``HandlerExtractor`` struct as its type argument. - ``test_handler_extractor_registers_on_router_without_turbofish`` -- ``router.get("/users/:id", Extracted[H]())`` compiles + serves end-to-end without an explicit ``[Extracted[H]]`` turbofish. - ``test_handler_extractor_serves_directly`` -- the struct itself is a regular ``Handler`` and ``probe.serve(req)`` works on a manually populated instance. * ``pixi.toml`` -- ``test-handler-extractor`` task registered + added to the ``tests`` aggregate after ``test_request_factories``. The remaining manual no-arg ``__init__`` body (``self.id = PathInt["id"]()`` etc.) is a Mojo language constraint -- ``@fieldwise_init`` doesn't auto-derive the ``Defaultable`` constructor from per-field ``Defaultable`` impls. Once Mojo ships that, the body collapses to ``pass`` without any flare code change. Verification: ``pixi run mojo -I . examples/intermediate/extractors.mojo`` emits the expected 5-line trace (3 GET responses + the sanitised 400 path + ``OK.``); ``pixi run test-handler-extractor`` prints all 4 OK lines. ``mojo format`` clean. Co-authored-by: Cursor <cursoragent@cursor.com>
HttpClient._do_request() (H1) and _send_h2c_via_upgrade() (H2c) were
silently dropping the caller-supplied Authorization header, blocking
per-request auth patterns (AWS SigV4, OAuth2 rotation, HMAC signing).
The fix mirrors the existing _build_h2_request_headers guard (line 1619):
only skip the caller's Authorization when _auth_header is already set.
When the client has a stored credential, that wins; when it does not,
the caller's per-request Authorization reaches the wire.
Also fixes an UnsafePointer[UInt8](unsafe_from_address=0) usage in
flare/net/_libc.mojo — nightly now requires Optional[UnsafePointer]
to model nullable pointers.
Adds three regression tests in tests/http/test_unified_http_client.mojo:
1. caller Authorization reaches the wire on H1
2. caller Authorization reaches the wire on H2c
3. no duplicate Authorization when both client credential and
caller header are set
test_no_duplicate_authorization_h1 passed a relative url="/" to HttpClient.send() against a base_url client. send() treats req.url as absolute (base_url joining only happens in get/post), so it raised a URL-parse error before the dedup path ran, failing on both CI legs. Mirror the sibling forwarding tests: build a full request URL with the variadic String(...) constructor and drop the base_url. The H1/H2c Authorization-forwarding fix is unchanged. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
|
Ok the CI is fixed. Had to force push (with lease) to rebase this. |
Problem
HttpClient._do_request()and_send_h2c_via_upgrade()silently drop theAuthorizationheader when callers set it viareq.headers.set("Authorization", ...). The header never reaches the wire.This blocks legitimate per-request auth patterns:
Root cause
Two methods unconditionally filtered
authorizationfrom forwarded caller headers, expecting auth to always flow throughHttpClient._auth_header:_do_request()line 713 — the main HTTP/1.1 path_send_h2c_via_upgrade()line 1854 — the H2C upgrade pathFix
H1 path: Remove
authorizationfrom the skip condition entirely. When_auth_headeris empty (the default), callers must be able to setAuthorizationviareq.headers.H2c path: Only skip caller's
Authorizationwhen_auth_headeris already set — matching the existing pattern in_build_h2_request_headers()(line 1619-1623).Both paths remain compatible with
BasicAuth/BearerAuth: those set_auth_headerwhich is still emitted on line 707-708 (H1) and 1847-1848 (H2c).Testing
Reproduced against moto S3 mock server. Before the fix, any request with a per-request Authorization header returns 403 (header absent from wire). After the fix, the header reaches the server and requests succeed.