Flatpak packaging for the Ladybird web browser (work in progress)
Find a file
Philip Peterson 08c8fa6d0c Fix HTTPS loads failing with "SSL verification failed"
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>
2026-09-30 22:43:25 -08:00
patches Fix HTTPS loads failing with "SSL verification failed" 2026-09-30 22:43:25 -08:00
.gitignore Pivot to building upstream's own flatpak manifest 2026-09-30 05:31:09 -08:00
README.md Fix HTTPS loads failing with "SSL verification failed" 2026-09-30 22:43:25 -08:00
Taskfile.yml Fix HTTPS loads failing with "SSL verification failed" 2026-09-30 22:43:25 -08:00

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-builder needs --install-deps-from=flathub (already in task build). Without it, it fails immediately with Requested extension org.freedesktop.Sdk.Extension.llvm20/x86_64/6.10 not installed — llvm20/rust-stable are 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-from makes 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 in task build) is needed inside the distrobox, same as discovered on the vcpkg-approach branch — 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), so InsecureRootCapabilities wasn'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.

  1. 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 current Gfx::GlobalFontConfig singleton (a get() accessor that didn't exist when the issue's patch was written). Confirmed fixed in isolation: the Fontconfig error: Cannot load default config file line disappeared from a rebuild with just this change, before the other two below were found.

  2. A second, separate, not-yet-reported bug: even with the fontconfig fix, WebContent crash-looped immediately with Runtime error: Landlock must be applied before the process starts a second thread — a check that, per git log, didn't exist before commit 3fea952607 (2026-09-19, 11 days before our pin), added as a safety net while fixing an analogous bug in Compositor (GPU driver threads starting before Landlock was applied). That fix reordered Compositor's startup; WebContent apparently 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 gap Compositor had. Root-caused by adding temporary /proc/self/task diagnostics: 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 in Services/WebContent/main.cpp. Fixed the same way the precedent did: moved both to after RendererSandbox::apply_sandbox().

  3. 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) that WebContent's seccomp policy doesn't grant. Tried SDL_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 calls libusb_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 existing allow_network() that provides sendmmsg is a much broader grant than this narrow case needs), the patch just skips gamepad initialization entirely while sandboxed (--disable-sandbox still 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.