🏡 index : quine-global-labs/flathub-ladybird.git

# Ladybird Flatpak — local build helper

This repo does **not** maintain its own Flatpak manifest for
[Ladybird](https://github.com/LadybirdBrowser/ladybird). It builds
**upstream's own manifest** instead:
[`Meta/CMake/flatpak/org.ladybird.Ladybird.json`](https://github.com/LadybirdBrowser/ladybird/blob/master/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`](https://github.com/LadybirdBrowser/ladybird/blob/master/.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`](https://taskfile.dev) and [`distrobox`](https://distrobox.it/) (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](https://github.com/LadybirdBrowser/ladybird/issues/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.