OpenSSL's module builds with --openssldir=/app/ssl, but nothing populates /app/ssl/certs, so SSL_CTX_set_default_verify_paths() always fails to find any CA. Point SSL_CERT_FILE at the runtime's real bundle (/etc/ssl/cert.pem) via finish-args instead. Verified with --headless=text against example.com, news.ycombinator.com, and google.com. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| patches | ||
| .gitignore | ||
| README.md | ||
| Taskfile.yml | ||
Ladybird Flatpak — local build helper
This repo does not maintain its own Flatpak manifest for
Ladybird. It builds
upstream's own manifest instead:
Meta/CMake/flatpak/org.ladybird.Ladybird.json,
maintained by the LadybirdBrowser team and built + tested on every single commit and PR
via their own
.github/workflows/flatpak.yml
(both x86_64 and aarch64, currently green).
Why not our own manifest
An earlier version of this repo (see the vcpkg-approach branch) built an independent
manifest from scratch, driving Ladybird's normal vcpkg-based build inside Flatpak's
network-less sandbox. That got genuinely close — a real CI run compiled 68 of 69 vcpkg
ports successfully, including Skia — after solving a long chain of non-obvious problems
(vcpkg's own bootstrap binary, googlesource's non-reproducible archive endpoint, a Dagger
cache-volume bug, bubblewrap needing privileged nesting, and more; see that branch's
README for the full account). Partway through fixing the very last vendoring gap, it
turned out upstream had already solved the entire problem, more simply, by not using
vcpkg for the Flatpak build at all — every dependency is its own plain Flatpak module
with a git-tag source — and had it running green in CI on every commit already. Building
and maintaining a second, independent manifest against a fast-moving browser codebase,
when a first-party one already exists and stays in lockstep with the code by
construction, isn't a good use of ongoing effort. This repo pivoted to just building that
one instead.
Local build
Requires go-task and distrobox (same
reasoning as before: flatpak-builder has no Homebrew formula and needs deep
ostree/bubblewrap/polkit integration that doesn't suit an immutable host directly).
task install-tools # once: creates the distrobox, installs flatpak-builder,
# installs the org.kde.Platform/Sdk 6.10 runtime this manifest uses
task build # clones LadybirdBrowser/ladybird into ./ladybird if needed (applying
# patches/ -- see "Running it" below), then builds
# Meta/CMake/flatpak/org.ladybird.Ladybird.json
task run # flatpak run org.ladybird.Ladybird
task update pulls the latest ladybird/ checkout and reapplies patches/ (git reset --hard wipes them, same as a fresh clone would). task clean removes local build
artifacts (not the checkout itself).
Build confirmed working end to end on 2026-09-30: task build compiles every
dependency module (angle, skia, openssl, ffmpeg, etc.) and the main Ladybird module
itself, and installs cleanly. Two build-time things worth knowing if you hit them:
flatpak-builderneeds--install-deps-from=flathub(already intask build). Without it, it fails immediately withRequested extension org.freedesktop.Sdk.Extension.llvm20/x86_64/6.10 not installed—llvm20/rust-stableare never published under a branch numbered "6.10" on Flathub (confirmed directly against the remote), so a naive pre-install of the runtime doesn't satisfy the SDK's declared extension points.--install-deps-frommakes flatpak-builder resolve and install them correctly itself; upstream's CI gets this for free because its container image (ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10) already has them deployed.--disable-rofiles-fuse(already intask build) is needed inside the distrobox, same as discovered on thevcpkg-approachbranch — no FUSE mount available in the container. Unlike that branch's Dagger module, this distrobox did not need any extra privilege grant for bubblewrap's per-step sandbox (it just worked), soInsecureRootCapabilitieswasn't needed here — evidently distrobox containers already have sufficient namespace permissions by default on this host.
Running it: three sandbox bugs found, fixed, and verified
At today's pinned master commit (124f0c9871), the app would not run at all with
Linux sandboxing enabled — found three separate bugs doing so, all in Ladybird's own C++
sandbox code, not in packaging. All three are now fixed by patches/sandbox-fixes.patch
(applied automatically by task clone/task update/task apply-patches — not
committed to Ladybird's own repo, since none of this is upstreamed yet) and confirmed
working: a real flatpak run org.ladybird.Ladybird (no --disable-sandbox, no other
flags) starts RequestServer, Compositor, and two WebContent processes, all stay up,
and pages load with no crash loop.
-
Fontconfig loads after the sandbox restricts filesystem access, so it fails — tracked upstream as #11775 (open, filed 2026-09-15, reported against NixOS but is a general Linux-sandbox issue, not Nix-specific — Flatpak's own filesystem restriction triggers the identical failure). The issue includes an unmerged, untested-by-its-author patch (force fontconfig to load its config before the Landlock/seccomp restrictions go up, then hand Skia that already- loaded
FcConfig*instead of letting it load a fresh one from inside the sandbox). Adapted into the patch here (Services/Compositor/SandboxLinux.cpp,Services/RendererSandboxLinux.cpp,Libraries/LibGfx/Font/TypefaceSkia.cpp) to this checkout's currentGfx::GlobalFontConfigsingleton (aget()accessor that didn't exist when the issue's patch was written). Confirmed fixed in isolation: theFontconfig error: Cannot load default config fileline disappeared from a rebuild with just this change, before the other two below were found. -
A second, separate, not-yet-reported bug: even with the fontconfig fix,
WebContentcrash-looped immediately withRuntime error: Landlock must be applied before the process starts a second thread— a check that, pergit log, didn't exist before commit3fea952607(2026-09-19, 11 days before our pin), added as a safety net while fixing an analogous bug inCompositor(GPU driver threads starting before Landlock was applied). That fix reorderedCompositor's startup;WebContentapparently has the same ordering problem and was never updated to match, so it now hits the newly-strict check instead of silently running with the same gapCompositorhad. Root-caused by adding temporary/proc/self/taskdiagnostics: the second thread is OpenSSL's thread pool (OSSL_set_max_threads, for its thread-pool provider) and SDL's gamepad hotplug thread (SDL_Init(SDL_INIT_GAMEPAD)), both called before the sandbox inServices/WebContent/main.cpp. Fixed the same way the precedent did: moved both to afterRendererSandbox::apply_sandbox(). -
Moving SDL's init that late surfaced a third, related issue: its HIDAPI/libusb backend starts its own event thread, which then hits a seccomp violation (
disallowed syscall unknown (294)—sendmmsg, used on the netlink socket libusb opens for device hotplug) thatWebContent's seccomp policy doesn't grant. TriedSDL_HINT_HIDAPI_LIBUSB=0(meant to make HIDAPI prefer the udev/hidraw backend instead of libusb) — did not avoid it in testing (hid_init()still callslibusb_init()regardless; not fully root-caused why). Rather than widen the seccomp policy for a feature most pages never use (and risk over-granting —LibSandbox's existingallow_network()that providessendmmsgis a much broader grant than this narrow case needs), the patch just skips gamepad initialization entirely while sandboxed (--disable-sandboxstill gets it). A real, known tradeoff, not a hidden one: the Gamepad Web API won't work in the sandboxed build until someone scopes a properly narrow seccomp allowance for whatever libusb's event thread actually needs, or finds why the HIDAPI hint didn't take.
If a leftover session from a crashed run leaves the browser refusing to start at all with
Runtime error: connect: Connection refused (errno=111), a stale single-instance
lock/socket is why (hit repeatedly while testing, mostly from timeout-killing a test
run non-gracefully) — task clean-runtime removes it.
Fourth bug: every HTTPS load failed with "SSL verification failed"
Once the sandbox bugs above were fixed and the browser actually ran, every single HTTPS
page failed with Load failed: Request finished with error: SSL verification failed —
reproduced even with --disable-sandbox, so not a sandboxing issue at all. Root cause:
the manifest's openssl module configures --openssldir=/app/ssl, and nothing in the
manifest ever populates /app/ssl/certs with any CA certificates — confirmed empty after
a build. Libraries/LibTLS/TLSv12.cpp falls back to SSL_CTX_set_default_verify_paths()
whenever no app-level custom certificate is set (the normal case), which resolves against
that empty compiled-in default, so every chain fails to verify. The org.kde.Platform
runtime itself does provide a real, current CA bundle at /etc/ssl/cert.pem (confirmed
valid PEM content, readable from inside the sandbox) — OpenSSL's default-path logic checks
the SSL_CERT_FILE/SSL_CERT_DIR environment variables before falling back to its
compiled-in paths, so pointing it at the runtime's bundle is a one-line fix with no C++
changes needed: patches/flatpak-ssl-cert-path.patch adds
--env=SSL_CERT_FILE=/etc/ssl/cert.pem to the manifest's finish-args. Confirmed fixed
with --headless=text: https://example.com, https://news.ycombinator.com, and
https://www.google.com all now fetch and render real page content instead of erroring.
Flathub submission
Not yet submitted anywhere findable (checked flathub/flathub and the flathub org for
an existing org.ladybird.Ladybird as of 2026-09-30). Since upstream owns and actively
maintains this manifest, they're the natural ones to submit it — worth raising with the
LadybirdBrowser maintainers (e.g. as a repo issue) rather than a third party submitting on
their behalf, unless they've already indicated otherwise somewhere this hasn't turned up.