Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

AppImage ecosystem archaeology — evidence-backed corrections and additions to the successor checklist

What this is. A conformance-checklist-style report on what a decade of the upstream AppImage record says about building a successor format, written to be merged into APPIMAGE-SUCCESSOR-CHECKLIST.md, which is vendored into this repository so that every item this document cites can be checked against the exact text it was written against. It exists because that checklist was researched almost entirely from the AnyLinux side and therefore encodes AnyLinux's own framing of what the problems are. This document corrects that bias from the other side of the record: 4,587 issues and pull requests across ten upstream repositories, 2013–2026.

Ground rule, inherited and tightened. Every factual claim below cites a specific issue, pull request, commit, source file or command output by URL, with its number, state and date as observed on 2026-09-05. Where an outcome could not be determined it is marked unresolved rather than guessed. Where a maintainer concluded something, it says so; where a commenter asserted something, it says that instead. Closed ≠ solved and open ≠ unsolved are applied throughout, and both cases appear below.

The successor must stay compatible with existing AppImages. That constraint is what makes most of this document load-bearing rather than academic: nearly every finding here is a place where the existing ecosystem will silently reject, mis-detect, or half-integrate a new image, and where the checklist as written does not yet have a gate.


Table of contents

  1. Synthesis — what the archive says that the AnyLinux docs do not
  2. Corrections — where the existing checklist is wrong
  3. Method and corpus
  4. Feature-matrix deltas
  5. Non-goals to add (§3)
  6. Tripwires to add (§4)
  7. Conformance environments to add (§5)
  8. Budget corrections (§6)
  9. New and sharpened checklist items, Gate 0 → Gate 12
  10. Findings table
  11. Open questions and unresolved threads
  12. Appendix — reproducing this research
  13. Verification pass

1. Synthesis

The AnyLinux documentation tells a story about portability: bundle glibc, execute ld.so directly, fix /proc/self/exe, and fall back FUSE → user namespaces → extract-and-run. That story is true and the upstream archive confirms it in detail — including that upstream is still, in 2026, telling people to build on Ubuntu 22.04 because "the host system is too new" (linuxdeployqt#638, closed 2026-07-02).

But the archive is mostly a record of a different problem, and it is not the one AnyLinux describes. Upstream's decade of pain is overwhelmingly about the boundary between the image and everything that is not the image: the ELF header the kernel and ld.so read before anything runs; the ELF sections that carry signatures and update information; the reference library (libappimage) that every desktop integration path funnels through; the shared-mime-info entry that decides whether a file manager even knows what the file is; the .zsync control file that names where bytes get written. Those are the parts nobody owns, nobody tests, and everybody breaks.

Three things fall out of that, and they are the three findings I consider most important.

First: the checklist's flagship security promise is arithmetically impossible as specified, and upstream already measured why. Gate 3's P3.1 requires signature verification before the payload executes, on by default, fail closed; §6 budgets that at "≤ 50 ms added to cold launch". The AppImage signature model — including the one written into the normative spec today — signs a SHA-256 over the entire file. In AppImageSpec#35 (open, labelled wontfix), @piegamesde posted openssl speed numbers showing SHA-256 at ~464 MB/s on one core, and probonopd (AppImage's creator, MEMBER) replied "with a 5 GB AppImage (like a game) it would be multiple seconds, which is too much. And think spinning disks"; TheAssassin (MEMBER; by his own account the author of "most of the core work in the last years", AppImageSpec#32) agreed the overhead is severe on slow disks and proposed a first-launch-only check instead. At 464 MB/s a 50 ms budget buys ~23 MB of payload. Even assuming a very generous 2 GB/s with SHA-NI, it buys ~100 MB — less than a single Electron reference workload. The successor cannot get verify-before-exec by bolting a hash onto Gate 3; it needs a Merkle/fs-verity-shaped payload designed into Gate 1, so that verification cost is O(bytes actually read) and the 50 ms figure becomes independent of image size. That is a format decision, not a runtime one.

Second: the type-2 magic bytes are a live, upstream-acknowledged format bug that AnyLinux inherits verbatim, and the registered MIME type is welded to it. The spec says a type-2 AppImage MUST contain 0x414902 at offset 8. Offset 8 is e_ident[EI_ABIVERSION] and bytes 9–10 are the first two bytes of e_ident[EI_PAD], so a released runtime carries e_ident = 7f 45 4c 46 02 01 01 00 41 49 02 00 … with EI_OSABI = ELFOSABI_SYSV (readelf -h on type2-runtime's runtime-x86_64, verified 2026-09-05). Three different consumers read those bytes and reject the file, in three different places, and only by separating them does the fix become obvious. All three were re-derived from source and measured for this document; the reproductions are in §12.

glibc's ld.so rejects the bytes — but only for objects the loader opens itself: DT_NEEDED libraries, dlopen(3), or an explicit ld.so ./file. sysdeps/gnu/ldsodefs.h defines VALID_ELF_ABIVERSION(osabi,ver) as ver == 0 || (osabi == ELFOSABI_GNU && ver < LIBC_ABI_MAX); because AppImage runtimes are ELFOSABI_SYSV, the operative rule for them is simply must be zero — the GNU feature-level branch never applies. The same condition in open_verify() (elf/dl-load.c) additionally memcmps EI_PAD against zero, so bytes 9–15 are exactly as unusable as byte 8. Measured on glibc 2.39/x86-64, ld.so ./file refuses a binary carrying the runtime's e_ident with "ELF file ABI version invalid" — but the kernel's execve path does not re-verify e_ident at all, so those same bytes execute normally, in a static binary as much as a dynamic one. The magic alone does not stop an AppImage from launching on a normal host.

binfmt_misc, on the qemu path. qemu-user's own ELF loader ignores these bytes entirely — its elf_check_ident() (linux-user/elfload.c) checks magic, class, data and version only, so anyone who greps it for the check will not find one. What rejects the image is the binfmt_misc registration that scripts/qemu-binfmt-conf.sh installs: its masks compare e_ident bytes 8–15 against zero, while byte 7, EI_OSABI, is masked out entirely (on every architecture but LoongArch64, which compares its top six bits). The MIPS family and LoongArch64 exempt byte 8 (qemu commit 77d119dd33, 2022-03, "mips: allow nonzero EI_ABIVERSION"), but no architecture exempts bytes 9–15, and an AppImage puts I (0x49) at byte 9 — so the match fails on every registered architecture, qemu is never invoked, and the foreign-arch image falls through to a native exec that cannot work. That is bubbleguuum's 2025-07-13 report in #828 of an aarch64 AppImage under qemu on opensuse/leap:15.5: "The solution was to patch the 3 ELF header bytes at offset 8 with dd."

AppImageLauncher's own binfmt_misc registration, on the container path — which is what AppImageKit#828 is actually about. On 2018-04-26 AppImageLauncher began shipping a binfmt_misc registration keyed on exactly these bytes — :appimage-type2:M:8:AI\x02::@CMAKE_INSTALL_PREFIX@/bin/AppImageLauncher: in resources/binfmt.d/appimage.conf.in (commits aea4938e and 40a557a5; type 1 added 0c754150, 2018-05-08). TheAssassin filed #828 three months later, opening with "A few weeks ago, I first noticed that some AppImages no longer run in Docker containers." binfmt_misc is kernel-global, but a path-based interpreter — no F flag — is resolved in the executing mount namespace, so inside a container, where /usr/bin/AppImageLauncher does not exist, execve returns ENOENT and the shell prints "No such file or directory" or "bad ELF interpreter: No such file or directory". Zeroing the magic makes the file stop matching the registration, which is precisely why that workaround works and why nothing else in the container needs changing. This reproduces exactly (§12): with the registration installed, a binary carrying the runtime's e_ident fails in ubuntu:14.04 and ubuntu:24.04 while the identical binary without the magic runs; upstream's fix was the F (fix binary) flag added in 2022-08, whose commit message states the purpose outright — "fix all issues related to using AppImages in Docker containers with AppImageLauncher installed on the host system … the kernel loads the binary from the host once instead of looking for it upon every launch in every cgroup/namespace/chroot where it may not be available" — and which is the "brand new workaround" TheAssassin pointed #828's reporters at on 2022-10-02.

The issue — "AppImages don't work in Docker containers any more (cause: magic bytes)" — was filed by TheAssassin in 2018-07, is still open, was confirmed as still reproducing in 2025-11, and probonopd's reply there is "Only once there will be a type 3 format this can be resolved." The fix was designed in AppImageSpec#26 (move the magic to a fixed 0x400, agreed on IRC in 2022-05) and never implemented. AnyLinux ships the same bug: uruntime's build tool seeks to byte 8 and writes AI\x02 (xtask/src/main.rs, add_magic). And the escape is not free: application/vnd.appimage in shared-mime-info is defined by that offset. On this machine's installed database:

$ grep -a -A4 'application/vnd.appimage' /usr/share/mime/magic
[50:application/vnd.appimage]
>1=\x00\x03ELF
1>8=\x00\x01A
2>9=\x00\x01I
3>10=\x00\x01\x02

So a successor that moves its magic loses MIME identification on every already-installed desktop until a new shared-mime-info entry propagates, and one that keeps offset-8 magic ships a known-broken header. The checklist's P1.1 and P2.3 currently say nothing about either horn.

Third: "ecosystem compatibility" is not a thumbnailer problem, it is a libappimage problem — and the experiment has already been run, at one-tenth the magnitude, with a documented outcome. On 2023-07-07 appimagetool changed its default compression to zstd (commit 810916a858, "Use zstd compression by default" — identified in the thread below by FreeCAD's own maintainer). Deployed libappimage still did not support it sixteen months later, so when the first widely distributed zstd images reached users in 2024-11, FreeCAD 1.0, Navicat and Postman AppImages stopped integrating with AppImageLauncher, failing with "Squashfs image uses (null) compression, this version supports only xz, zlib" (AppImageLauncher#674, 4 reactions). The advice users gave each other was "The only solution at the moment is to remove AppImageLauncher from your system." It took roughly three months to close. DwarFS is that same experiment with a different filesystem: on 2025-10-31, in AppImageSpec#36, TheAssassin wrote "I've just come across an AppImage using DwarFS in the wild. libappimage cannot integrate those", pointing at AppImageLauncher#752 (open since 2025-10-29). He also named the fix and the blocker: "we do not have anyone with capacity to implement DwarFS in libappimage, the reference implementation for desktop integration", and asked for a PR. The single highest-leverage upstream action available to a successor project is therefore not a thumbnailer — it is a libappimage payload-format patch plus the extraction-based fallback that already exists as an unmerged community PR (AppImageLauncher#771, open since 2026-03-15).

Two further themes run through the whole corpus and deserve naming here because they change how the checklist should be read.

The trust story upstream is not "nothing exists"; it is "something exists, it is TOFU, it was broken for three years, and AnyLinux dropped it." Signature validation on update was implemented and merged (AppImageUpdate#75, merged 2018-07-26) with an explicitly transitive trust model — trust the public key embedded in the AppImage you already have, require the next one to be signed by the same key — and TheAssassin explicitly rejected a trust store: "I don't want to introduce a 'trusted keyring' at all. It makes everything overly complicated" (AppImageUpdate#16). Meanwhile the AppImageKit-side reference verifier reported BAD signature on correctly-signed images from 2019-04 until 2022-02 (AppImageKit#949, bug, high-priority; fixed by merged PR#1090), during which at least one real publisher gave up on signing entirely (AppImageKit#1010: "Going back to unsigned images for the time being… AppImage signature validation doesn't seem to work at all anyway"). AnyLinux's fork of the updater then removed what did work: the pkgforge AppImageUpdate README lists "No GPG signature verification (not implemented)" under limitations and describes integrity as "SHA1 verification". The checklist records both vanilla and AnyLinux as ❌ for signed updates; the truth is upstream ⚠️ → AnyLinux ❌, i.e. a regression the checklist does not record, in a gate whose whole purpose is "do not regress".

Almost everything the successor proposes as new was proposed upstream, designed, and lost — usually to capacity, occasionally to a governance disagreement between the project's two members. A mount-free metadata header between the ELF and the payload: designed by TheAssassin in AppImageSpec#32 (2021), prototyped as TheAssassin/type3-runtime, then moved out of the AppImage organisation. A sandbox permissions file: AppImageSpec#5, open since 2016-11-01, with probonopd sketching a concrete privileges.conf in 2024 and nothing shipping. An SBOM: AppImageSpec#25 (2019) — asked for explicitly to track vulnerable bundled libraries and satisfy licence obligations — answered with "Not sure it should be part of the AppImage spec, though." Reproducible builds: pkg2appimage#8, open since 2015-11 with zero comments. GPL corresponding-source provision: linuxdeploy#13, opened by TheAssassin in 2018 with a complete design, never implemented. A normative signature specification: an unmerged pull request since 2020-03 (AppImageSpec#30). The successor's differentiators are not novel ideas; they are a backlog with citations, which is good news for design risk and bad news for anyone assuming they are easy.


2. Corrections

These are the places where the historical record contradicts the checklist as written. Each states which side the evidence favours.

C1 — Verify-before-exec cannot meet the 50 ms budget with whole-file hashing

Checklist says: P3.1 "Signature verified before the payload executes; fail closed"; P10.3 "verify-before-exec + Landlock adds ≤ 50 ms to cold launch".

Record says: the spec's signature is over a SHA-256 of the whole file (draft.md, type 2). AppImageSpec#35 contains the measurement (SHA-256 ≈ 464 MB/s single-core), the maintainer's rejection of per-launch hashing ("too much", "think spinning disks"), and the counter-proposal (verify once, at first launch, in the integration layer). The issue is labelled wontfix.

Evidence favours: the checklist's goal (fail-closed verification before exec) over upstream's resignation, but only if the mechanism changes. Whole-file hashing at launch is genuinely unaffordable; a Merkle tree over payload blocks with a signed root, verified lazily as blocks are read, is affordable and is what dm-verity/fs-verity/casync do. Action: move the requirement into Gate 1 as a format property (P1.16 below), restate the budget as "≤ 50 ms, independent of payload size", and keep a separate, explicit full-verification path for first install.

C2 — Signed updates: vanilla is understated and AnyLinux is a regression

Checklist says: signed updates are absent in both vanilla and AnyLinux.

Record says: AppImageUpdate#75 (merged 2018-07-26) implements signature validation on update with a transitive-trust model, and AppImageUpdate#146 confirms it applies to self-updating AppImages that bundle the updater. pkgforge's Rust rewrite dropped it (README: "No GPG signature verification (not implemented)").

Evidence favours: correcting the matrix to vanilla ⚠️ (TOFU on the embedded key, no rotation, no revocation) and AnyLinux ❌. This matters because Gate 1 forbids regressions against AnyLinux, but here AnyLinux is itself the regression — a successor that matches AnyLinux on this row is behind a 2018 upstream capability.

C3 — The musl non-goal has an out-of-date reason and a correct conclusion

Checklist says: musl "has allocator performance problems", citing the AnyLinux FAQ, which cites type2-runtime#116.

Record says: #116 measured uruntime-musl at 2.0 s vs uruntime-glibc at 1.5 s, and was resolved by changing the allocator, not the libc: @Samueru-sama, 2025-03-04, "VHSgunzo switched back the uruntime to musl but this time using mimalloc and the regression is gone". Benchmarking of the upstream mimalloc port (type2-runtime#120) measured the removed regression at 18% with the default block size and 25% with 1 MiB blocks; the issue was closed 2025-03-06.

Evidence favours: keeping musl as a non-goal for the bundled application libc — the load-bearing reasons are the proprietary NVIDIA driver, the inability to dlopen glibc-linked host libraries, and minimal size savings, all stated in the AnyLinux FAQ — while dropping "allocator performance" from the rationale, because a static musl runtime with mimalloc is demonstrably fine and is what uruntime ships.

C4 — The vanilla baseline for thumbnails and tooling is overstated

Checklist says: vanilla type-2 has thumbnails ✅ and third-party tooling compatibility ✅ (baseline).

Record says: there has never been an official GNOME thumbnailer (AppImageKit#203; TheAssassin, 2018-11-11: the XDG thumbnailer "is only run when launching an AppImage, or when using tools like AppImageLauncher"). The working GNOME thumbnailer is a separate third-party project (azubieta/appimage-gnome-thumbnailer, shipped as .debs). And the "baseline" tooling compatibility broke for vanilla images inside the vanilla toolchain in 2024 (AppImageLauncher#674), and vanilla images still fail to execute in two environments that are common rather than exotic (AppImageKit#828): foreign-architecture emulation, where the e_ident magic fails qemu-binfmt-conf.sh's mask match, and containers on a host that registers a binfmt_misc entry inside e_ident — which, until 2022, is what installing AppImageLauncher did. See §1; the second is worth stating precisely because it means the vanilla baseline could be broken by installing a vanilla integration tool.

Evidence favours: downgrading both vanilla cells to ⚠️. This makes the successor's job harder to overstate and easier to measure: the baseline you must not regress against is lower than the checklist assumes, and it is not a stable target.

C5 — Tripwire T7 conflicts with P2.5 and with the normative spec

Checklist says: T7 is a hard CI failure if a produced image contains $APPDIR/usr; P2.5 requires consuming usr/-prefixed AppDirs from linuxdeploy/pkg2appimage.

Record says: the spec RECOMMENDS the ./usr/ prefix and FHS conventions for it (draft.md, "The payload application"), and real projects depend on it — go-appimage#315 (open) is GIMP's maintainers reporting breakage precisely because go-appimage put some dependencies outside usr/. The same thread documents a second hazard the checklist misses: on usrmerge hosts (/lib/usr/lib) a tool that copies "at the same path as on the build system" produces a split tree, and libpthread.so.0 ends up unfindable.

Evidence favours: keeping AnyLinux's design ($APPDIR is the prefix — the FAQ's reasoning is sound and lets you patch a normal /usr-prefixed build) but re-scoping the tripwire: forbid the dependency, not the directory. T7 should read "no image the successor produces may require $APPDIR/usr for correct operation, and no successor tooling may emit $APPDIR/usr/... path assumptions", with P2.5 gaining an explicit pass criterion that a converted legacy AppDir satisfies the re-scoped T7. As written, T7 makes the imported form unrepresentable and the two items cannot both be green.

C6 — The explicit trust store overrules an upstream decision and must say so

Checklist says: P3.3 "TOFU on first run, per-publisher pinning, explicit trust store, revocation."

Record says: upstream implemented TOFU-on-embedded-key and explicitly declined a keyring (AppImageUpdate#16, TheAssassin: "Why have users add keys to their 'keyring' on every computer? It doesn't really add any value"). probonopd's counter-position across AppImageKit#839 and #939 was a peer-produced web of trust, which TheAssassin rejected as unworkable. Neither shipped.

Evidence favours: keeping the trust store, but the checklist must state the argument it is overruling, because the counter-argument is real: a per-machine keyring is state that TOFU deliberately avoids, and the actual observed failure of TOFU is not "no keyring" — it is key rotation, which today is a dead end (see C7). A trust store that exists only to enable rotation and revocation is defensible; one that requires users to curate keys will reproduce the outcome upstream predicted.

C7 — Rollback protection is the wrong lifecycle risk to lead with

Checklist says: the update-lifecycle gap is downgrade attacks (P3.4).

Record says: the observed, reported-in-2026 failure is the opposite — AppImageUpdate#260 (2026-04-30): rotating the signing key produces VALIDATION_KEY_CHANGED "without an option to disregard the key mismatch and proceed", and probonopd closed it the next day with "Yes, correct. This works as designed." A publisher who rotates keys strands every existing installation.

Evidence favours: keeping P3.4 and adding a rotation requirement with equal weight (P3.11 below). Rollback protection with no rotation path is a bricking mechanism.

C8 — A SquashFS profile is not enough, the codec is the compatibility axis

Checklist says: P2.4 retains a "SquashFS build profile … for maximum-compat publishing".

Record says: deployed libappimage (the version in Debian stable, Ubuntu LTS, Fedora) supports only xz and zlib SquashFS; zstd-compressed SquashFS already broke it for months (AppImageLauncher#674). "SquashFS" is not the compatibility axis; the compression codec is.

Evidence favours: tightening P2.4 to "SquashFS with xz or zlib", and testing against distro-packaged consumers rather than git builds — which is also the lesson of type2-runtime#121, where a new runtime broke Krita on systems carrying the stable, distro-packaged AppImageLauncher and TheAssassin's answer was "use the alpha releases".

C9 — The GPL-3 objection to DwarFS is factually wrong

Record says: in AppImageSpec#44 a commenter argued "dwarfs is incredibly performant, but gpl3 makes it impossible to use with proprietary programs packaged in appimage." The DwarFS LICENSE says otherwise: "the code used to read DwarFS images is licensed under the MIT license, while the code used to write DwarFS images is licensed under the GPL-3.0 license … you can use mkdwarfs to create images and then use these images with only the MIT-licensed code."

Evidence favours: DwarFS is usable in a runtime concatenated with a proprietary payload. The real (and much smaller) obligation is on the builder: a build tool that bundles mkdwarfs is distributing GPL-3.0 code and inherits its terms. Record it in Gate 3's licence work, not as a blocker on Gate 1.

C10 — A non-finding, recorded deliberately: AppRun exit codes were never broken

Included because the discipline cuts both ways. §2.10 lists exit codes as "⚠️ mostly" for vanilla, and the closed-unmerged PR AppImageKit#559 ("Return the app's return value") looks like a lost fix. Reading the thread (AppImageKit#558) shows it was correctly rejected: AppRun execvps the payload, so the process is replaced and the exit status is the payload's by construction; the PR's author agreed and closed it himself. Do not add a gate item for this. The real process-semantics defects are elsewhere — mount lifetime versus child processes, and orphaned FUSE daemons (P1.14, P0.7 below).


3. Method and corpus

Everything was fetched through the sanctioned proxies (api.gh.pkgforge.dev for the GitHub REST API, api.rv.pkgforge.dev for raw files), saved to disk, and analysed locally.

Full state=all harvest of /issues and /pulls, paginated to exhaustion:

Repo Issues PRs Closed-unmerged PRs
AppImage/AppImageKit 916 373 62
TheAssassin/AppImageLauncher 641 113 20
probonopd/linuxdeployqt 504 135 13
AppImageCommunity/pkg2appimage 316 265 38
probonopd/go-appimage 255 147 35
linuxdeploy/linuxdeploy 254 77 15
AppImageCommunity/AppImageUpdate 202 58 9
AppImage/appimagetool 75 48 9
AppImage/type2-runtime 52 97 26
AppImage/AppImageSpec 42 17 2
Total 3,257 1,330 229

1,126 of those issues are open, plus 126 open pull requests — 1,252 open items in total. The oldest items in the corpus are AppImageKit's first three pull requests, from 2013-12-10; the oldest non-PR issue is AppImageKit#4 ("Get rid of the glib dependency"), 2013-12-21. The newest issue is from 2026-08-26 (AppImageLauncher#791) and the newest pull request from 2026-09-03 (go-appimage#403).

Pull requests were separated from issues by the presence of the pull_request key, so the issue counts above exclude PRs. The PR counts come from /pulls, which is authoritative: /issues silently omits a handful of pull requests altogether — exactly 11 across this corpus (AppImageKit #383; AppImageLauncher #121, #123; linuxdeployqt #139, #140, #258, #389; go-appimage #97; AppImageUpdate #134, #136, #138). A count taken from /issues alone is short by that many.

Phase 1 built a JSONL index over all 4,587 items (number, title, state, labels, dates, comment count, reactions, author, merged_at). Phase 2 reviewed all 229 closed-unmerged PR titles exhaustively, then scored the remainder by engagement, stale-closure and label, and ran the keyword sweep over titles. Phase 3 deep-read ~90 threads including full comment histories, plus the normative spec, the AnyLinux documentation set, and the uruntime/sharun sources.

Two primary sources were read directly rather than through issue trackers, because several claims turn on their exact text: the AppImageSpec working draft (280 lines; newest commit 51c2a146, 2026-04-03; revision history still ends at "Draft | 2016-09-18"), and uruntime's xtask/src/main.rs and src/main.rs.

Coverage gaps, stated explicitly. pkg2appimage's 316 issues are dominated by per-application recipe requests and were triaged by keyword rather than read; the same is true of the long tail of linuxdeployqt "it doesn't build" reports. libappimage, appimaged, AppImageHub, appimage.github.io and docs.appimage.org were not harvested — several findings below point into libappimage's tracker and would likely deepen with a pass over it. No content was posted to any upstream repository.


4. Feature-matrix deltas

Cells to change in §2, with the evidence. Legend unchanged (✅ / ⚠️ / ❌ / 🎯).

4.1 Corrections to existing rows

§ Row Cell Current Corrected Evidence
2.3 Signed updates (via §2.9) Vanilla ⚠️ TOFU on embedded key, implemented 2018 AppImageUpdate#75 merged 2018-07-26; #16
2.9 Signed updates AnyLinux regression vs vanilla pkgforge/AppImageUpdate README
2.3 Key distribution / trust model Vanilla ❌ none ⚠️ public key embedded in .sig_key, transitive trust; no rotation, no revocation AppImageSpec#29; AppImageUpdate#260
2.8 Thumbnails in file managers Vanilla ⚠️ only via an installed integration layer; no official GNOME thumbnailer; often only after first launch AppImageKit#203
2.8 Third-party tooling compatibility Vanilla ✅ baseline ⚠️ baseline is itself unstable AppImageLauncher#674; type2-runtime#121
2.2 Filesystem — SquashFS profile Successor 🎯 "SquashFS profile still buildable" 🎯 SquashFS + xz/zlib only for the compat profile AppImageLauncher#674
2.11 Build inputs pinned Vanilla and actively unpinnable — the runtime is fetched from a mutable continuous tag appimagetool#57, #117

4.2 Rows to add

§ Capability Vanilla AnyLinux Successor 🎯 Evidence
2.1 Magic bytes outside e_ident AI\x02 at offset 8 ❌ identical 🎯 fixed offset ≥ 0x40, ≤ 0x800; new shared-mime-info magic submitted AppImageSpec#26; AppImageKit#828; uruntime add_magic
2.1 Payload-format self-identification ⚠️ implicit (read FS magic at offset) ⚠️ same (DWAR/hsqs) 🎯 declared in the header and probeable at the payload offset AppImageSpec#10, #36; uruntime get_image
2.1 Mount / FUSE-daemon lifecycle correctness ❌ leaks under systemd; orphaned daemons; FUSE mount exhaustion ⚠️ uruntime cleans up extract dirs; mount reuse configurable 🎯 zero residue after exit on every rung, asserted type2-runtime#99, #138; AppImageKit#1211
2.1 Concurrency / multi-user on the extract path ❌ predictable shared $TMPDIR path, no uid ⚠️ inherits the design 🎯 per-uid, 0700, $XDG_RUNTIME_DIR-preferred AppImageKit#1215; AppImageKit#452
2.4 Confinement without a SUID helper ⚠️ userns rung exists in uruntime; not in upstream runtime 🎯 required; SUID fusermount optional everywhere type2-runtime#71, #137
2.4 Sandbox profile authority ❌ n/a ❌ n/a 🎯 manifest requests, host policy grants; intersection enforced AppImageKit#839
2.8 Capability probing without execution ❌ unknown --appimage-* option launches the app ❌ same 🎯 unknown option exits non-zero; --capabilities --json AppImageSpec#60
2.8 Declared minimum host requirements 🎯 min kernel / ISA level / GL-Vulkan needs in the header pkg2appimage#20; AppImageKit#913
2.8 argv[0] dispatch to alternate payload binaries 🎯 first-class; declared in the manifest AppImageKit#419; AppImageSpec#8
2.3 Corresponding-source provision for bundled GPL libs 🎯 machine-readable manifest + fetcher linuxdeploy#13
2.9 Update writes only to the target path ❌ destination filename taken from server-controlled .zsync ❌ inherits zsync 🎯 sibling temp file + rename(2) onto the original path only AppImageUpdate#160
2.9 Signing-key rotation ❌ hard failure, no path forward ❌ n/a (no verification) 🎯 rotation certificate signed by the outgoing key AppImageUpdate#260
2.11 Reproducible runtime build 🎯 byte-identical runtime and image type2-runtime#133; AppImageKit#625

5. Non-goals to add

Class-E findings — designs that were tried or argued out upstream with reasoning that still holds. Add to §3 so a future agent does not reinvent them.

  • A SUID helper as a confinement or mounting dependency. Alpine removed firejail with the rationale quoted in AppImageKit#152: "it has an atrocious security record, and the idea of a SUID program being used to provide improved security is fundamentally flawed." The same issue records that bwrap cannot consume a FUSE mount directly — you must mount first, then sandbox, which is what aisap does. Design the ladder accordingly.
  • Hashing the whole image on every launch. AppImageSpec#35 (wontfix) has the measurement and both maintainers' rejection. Use an incremental construction instead; see C1.
  • Depending on binfmt_misc registration for correctness. It is how the integration layer catches non-executable and already-executable images (AppImageLauncher#96, #26), it requires loop-execution hacks by the maintainers' own account, and the binfmt-bypass technique it forces is flagged by commercial endpoint security as "Defense Evasion via Process Hollowing / ElfExecutedFromMemory" (AppImageLauncher#380, which the maintainer declined to change). A successor may support binfmt; it must not require it.
  • Downloading sandbox profiles from a network service. TheAssassin, 2018-08-16: "There is no chance to download profiles automatically for applications from the internet, because how would you securely match an application and the profile? There are no 'one fits it all' profiles" (AppImageKit#839).
  • A generic sandbox profile as the default. Same thread, TheAssassin 2018-08-16: "Firejail currently tries to use some 'generic' profile, but it's not secure at all, and doesn't really restrict anything. This just doesn't work." probonopd agreed: "It is essentially a placeholder." In AppImageKit#452 the same maintainer adds the rule that follows from it: "Without knowing what exactly the sandboxing does, it shouldn't be advertised as 'security feature'" — which is tripwire T10 restated from the other side.
  • Two canonical locations for the same metadata. probonopd, on appimagetool#55 (metainfo.xml vs appdata.xml): "if we allow 2 different file paths in AppImages, then all tools that verify AppImages or integrate them into the system would have to check 2 different file paths as well." Accept one canonical path; normalise on import.
  • A verifier that only checks a signature is well-formed. TheAssassin, AppImageKit#949: "you first need to trust the key, then you can trust valid signatures made with it. But that 'trust the key' bit isn't implemented at all by this validate tool. So the tool is pretty pointless." Any --verify surface must state, in its output, which key it trusted and why.
  • An unversioned media type. TheAssassin warned against application/vnd.appimage in 2017 (AppImageKit#144): "there are going to be future versions of the AppImage specification. You should rather introduce some versioned type, e.g. appimage.type2." He was overruled; the consequence is that a successor cannot be distinguished from a type-2 by MIME type alone. Do not repeat it.

6. Tripwires to add

Wire into CI as hard failures, continuing §4's numbering.

  • T11 — Magic or format bytes written anywhere inside e_identEI_OSABI (byte 7), EI_ABIVERSION (byte 8) or EI_PAD (bytes 9–15). Rationale: AppImageSpec#26, AppImageKit#828.
  • T12 — The updater opening for write any path other than the target image path or a sibling temporary file in the same directory. Rationale: AppImageUpdate#160.
  • T13 — A build that consumed any artifact not pinned by content hash, or that treated a non-2xx HTTP response as success. Rationale: appimagetool#57, #44.
  • T14 — An unrecognised --<namespace>-* option causing the payload to launch instead of exiting non-zero. Rationale: AppImageSpec#60.
  • T15 — A mount point or extraction directory that is group/world-writable, or whose name does not include the invoking uid. Rationale: AppImageKit#1215, AppImageKit#452.
  • T16 — Any mount, FUSE daemon, or extraction directory belonging to the image still present 5 seconds after its last payload process has exited. Rationale: type2-runtime#99, #138, AppImageKit#1211.
  • T17 — SHA-1 or MD4 used as the authority for integrity anywhere in the chain. Transport hints (zsync's rolling checksums) are permitted only when the assembled result is independently authenticated by a modern hash. Rationale: the current updater's integrity is SHA-1 (pkgforge/AppImageUpdate README); GPG's own digest defaulted to SHA-1 in the AppImage signing path (AppImageUpdate#75, TheAssassin's note to self).
  • T18 — A signature computed over a hex string rather than the digest bytes, or a fixed-capacity key/signature field. Rationale: AppImageKit#1010 (@ssaavedra: "you are NOT signing the digest of the AppImage. You are currently signing the hex-string of the digest"; .sig_key is a fixed 8192-byte pre-allocated section and real keys overflow it).

7. Conformance environments to add

Continuing §5's numbering. Every one of these is a reported failure environment, not a hypothetical.

# Environment Why it is in the matrix Evidence
E16 Debian stable, Ubuntu LTS and Fedora with the distro-packaged libappimage / AppImageLauncher / appimaged, never a git build This is where format changes actually break, and where the fix is not available AppImageLauncher#674, #752, type2-runtime#121
E17 Two unprivileged users launching the same image concurrently on one host Predictable shared $TMPDIR path with no uid → second user gets fopen error: Permission denied AppImageKit#1215
E18 qemu-user foreign-arch emulation with qemu-binfmt-conf.sh registrations installed; and a container on a host carrying a binfmt_misc entry keyed inside e_ident (AppImageLauncher's :appimage-type2:M:8:AI\x02: is the deployed example) The two places the e_ident collision actually manifests, both as ENOENT. Both preconditions are load-bearing: a container that merely lacks /dev/fuse reproduces a different failure ("fuse: device not found", already covered by E5) and will pass this environment vacuously, which is how the collision stayed mis-attributed for years AppImageKit#828; §1
E19 Managed endpoint running EDR/AV that alerts on memfd execution uruntime executes its bundled squashfuse/dwarfs helpers via memfd_exec; NO_MEMFDEXEC=1 is the escape hatch and must be tested AppImageLauncher#380; uruntime README
E20 Non-interactive systemd service unit, and a session terminated by SIGKILL Mounts and FUSE daemons leak; the per-user FUSE mount limit is eventually exhausted type2-runtime#99, AppImageKit#1211
E21 usrmerge host (/lib/usr/lib) and a split-/usr host, same AppDir Path-copying tools produce different, sometimes broken, internal layouts go-appimage#315

Add to the reference workloads: an application that launches a second binary from its own AppDir and outlives the launcher (a slicer, a browser with a helper process). That case is documented in detail by PrusaSlicer's maintainer in AppImageKit#419 and is not covered by any current workload.


8. Budget corrections

Metric Checklist target Correction Evidence
Verify-before-exec + confinement overhead ≤ 50 ms added to cold launch ≤ 50 ms and independent of payload size; requires incremental verification, not whole-file hashing. State the full-verify path's cost separately (≈ image size ÷ ~460 MB/s per core as a floor) AppImageSpec#35
Metadata/thumbnail read ≤ 5 ms, one open + pread Unchanged, plus "without executing the image" and "without the payload's filesystem driver" AppImageSpec#32, #60
Update download for a patch release ≤ baseline zsync bytes Baseline must be SquashFS-xz — measured, not assumed (§11.2) — but the stated reason changes. DwarFS at the tuning AppImages actually use (-S26) reuses 25–29 % of the previous image against SquashFS-xz's 98.3 %, and neither block size nor file ordering fixes that alone: 64 MiB → 1 MiB buys two points, --order=path at 64 MiB is negative. Together at 1 MiB they reach 96.2 %, and -S16 reaches 98.4 % on its own — each at a cost in image size (+8.5 % and +19 %). So the budget must be two numbers, not one: delta bytes and image size, published per release, since every delta improvement here is bought with size AppImageSpec#32, #44; measurement in §11.2
Cold launch within ~±300 ms of native Add a rung-by-rung breakdown. Measured on Brave/OnlyOffice/Inkscape, pre-extracted launch beat FUSE-mounted launch by 1.0–1.3 s (e.g. Brave 3.210 s → 1.960 s) — the extract rung is not always the slow one type2-runtime#71
(new) FUSE mounts per user ≤ 1 per running instance; 0 after the last payload process exits type2-runtime#99
(new) Runtime size ≤ 3 MiB is the number upstream considers acceptable; the current type-2 runtime is "way under 1 MiB" and TheAssassin's stated concern is that "a hundred AppImages shouldn't occupy half a GiB of runtimes" AppImageSpec#36, #32

9. New and sharpened checklist items

Numbering continues within each gate; nothing existing is renumbered or deleted. Format follows the checklist: - [ ] **ID** Statement. **Pass:** objective criterion. plus the finding that motivates it.

Gate 0 — Baseline harness

  • P0.6 Stand up a consumer conformance harness that runs every candidate image through the deployed integration stack — distro-packaged libappimage/AppImageLauncher, appimaged, Gear Lever, AM/appman, and the Nautilus/Dolphin/Thunar thumbnailers — not git builds. Pass: a per-consumer × per-distro pass/fail matrix emitted by ./conformance run --consumers, reproduced in CI on E16. (F-002, F-003, F-011)
  • P0.7 Assert the mount/daemon lifecycle. Pass: after (a) normal exit, (b) SIGKILL of the payload, (c) a systemd oneshot unit, and (d) 200 sequential launches, /proc/self/mountinfo contains no image-owned mount and the per-user FUSE mount count returns to its pre-test value. T16 fires on a purpose-built leaking image. (F-020, F-021, F-022)
  • P0.8 Concurrency and multi-user harness for the extract-and-run rung. Pass: eight concurrent launches of the same image by one user, plus two different uids launching it simultaneously on a shared host, all succeed with no EACCES and no partial-extraction reuse. (F-023)
  • P0.9 Endpoint-security smoke test. Pass: a documented table of which execution paths (memfd_create+execveat, binfmt_misc, SUID fusermount) trigger alerts on at least one commercial EDR, and a supported, tested non-memfd fallback for each. (F-024)

Gate 1 — Parity

  • P1.12 Format magic MUST NOT occupy any byte of e_ident — in practice EI_OSABI (byte 7), EI_ABIVERSION (byte 8) and EI_PAD (bytes 9–15) — and MUST sit at a fixed offset ≥ 0x40 and ≤ 0x800 so shared-mime-info can sniff it. Pass: readelf -h shows e_ident bytes 7–15 all zero; ld.so ./image does not fail with "ELF file ABI version invalid"; the image runs unpatched (a) under qemu-user for a foreign architecture with qemu-binfmt-conf.sh registrations installed and (b) inside ubuntu:14.04 and opensuse/leap containers on a host that has AppImageLauncher's binfmt_misc entry registered; and the offset is documented in the spec. The two parenthesised preconditions are what make the test discriminating — without them both containers and emulation pass a known-broken header, and "runs on a host whose ld.so validates EI_ABIVERSION" is not a test at all, because the kernel never re-verifies e_ident for a program it execves (§1). (F-001)
  • P1.13 Payload format is identifiable twice: declared in the header and probeable by reading the first bytes at the payload offset. Pass: a ≤ 50-line third-party prober, written from the spec with no access to the implementation, reports the payload format and offset for squashfs, dwarfs and the successor's native format, without executing the file. (F-004, F-005)
  • P1.14 Process-lifetime parity: the runtime must not terminate the payload's descendants, and a binary launched from $APPDIR by the payload must keep working after the launcher exits. Pass: exit status equals the payload's on all three fallback rungs; SIGTERM/SIGINT reach the payload; a double-forked child started from $APPDIR still resolves its libraries and runs to completion; no orphaned FUSE daemon (T16). (F-020, F-021, F-025)
  • P1.15 --why must report not only which rung was used but which observable semantics changed on it. Pass: on the user-namespace rung, --why --json reports the uid/gid mapping and PID-namespace state, and the harness asserts that a process-inspecting workload (nvtop-class) either works or is reported as degraded — never silently wrong. (F-026)
  • P1.16 The payload is verifiable incrementally: a Merkle tree (or equivalent) over payload blocks whose root is covered by the signature, so that verification cost is proportional to bytes read. Pass: launching a 5 GiB image adds ≤ 50 ms of verification to cold launch; flipping any single byte in the payload causes the first read that touches it to fail closed; a full verification mode exists and is used at install time. (F-006 — this is the format change that makes P3.1 and P10.3 achievable; see C1)

Gate 2 — Ecosystem compatibility

This is where upstream's record is densest and where the existing checklist is thinnest.

  • P2.7 The maximum-compatibility publishing profile is SquashFS with xz or zlib, not merely "SquashFS". Pass: an image built with this profile integrates with the libappimage shipped in the current Debian stable, Ubuntu LTS and Fedora releases without upgrading it. (F-002)
  • P2.8 Land payload-format support and an extraction-based fallback in libappimage itself. Pass: a PR is open against libappimage implementing the successor's format; AppImageLauncher#752 reproduces green with it; the fallback path (shell out to the image's own --appimage-extract) is implemented, as TheAssassin proposed and AppImageLauncher#771 prototypes. (F-003, F-011, F-012)
  • P2.9 Capability probing must never execute the payload. Pass: an unrecognised --<namespace>-* option exits non-zero with a machine-readable error and does not launch the application (T14); --capabilities --json enumerates supported operations; a consumer can decide what an image supports without running it. (F-007)
  • P2.10 Media-type identity is versioned and additive. Pass: a shared-mime-info XML defining the successor type at its new magic offset is submitted upstream with a test file, per the HACKING requirements that AppImageKit#144 documents; existing application/vnd.appimage handlers still match compatibility-profile images; the documented fallback (detected as a plain ELF executable, still runnable) is tested on a host with an old MIME database. (F-008)
  • P2.11 The thumbnail source is a regular file in the header — never a symlink into the payload — and renders with no mount, no fork and no payload-format knowledge. Pass: legacy images whose .DirIcon is a symlink convert cleanly (the failure mode is "WARNING: Unable to extract non regular file .DirIcon"); a 20-line thumbnailer produces output in ≤ 5 ms on E1–E15. (F-009, F-010)
  • P2.12 Declare minimum host requirements in the header: minimum kernel, required CPU ISA level, minimum host glibc if any host library is used, and GL/Vulkan expectations. Pass: --doctor refuses or warns before exec rather than the app dying with SIGILL or symbol not found; the field is populated automatically at build time. (F-013, F-014)
  • P2.13 Exactly one canonical AppStream location; legacy locations are accepted on import and rewritten. Pass: usr/share/metainfo/$ID.metainfo.xml is canonical, appdata.xml is normalised on ingest, and no consumer has to check two paths. (F-074)
  • P2.14 Publish and maintain the compatibility statement of P2.6 as machine-readable data with a dated last-verified field per consumer, regenerated by P0.6. Pass: the table in P2.6 is generated, not hand-written, and stale rows are visible. (F-002, F-003)

Gate 3 — Trust

  • P3.8 The signed region is defined positively — an explicit list of (offset, length) spans — not as "the whole file minus whichever sections a given tool happens to skip". Pass: two independent implementations, one written from the spec alone, compute identical digests for a corpus of 100 signed images. (F-016, F-017)
  • P3.9 Sign the digest bytes, not their hex representation, and store key and signature in length-prefixed, variable-capacity fields. Pass: a 4096-bit key carrying three subkeys and two user IDs signs and verifies; T18 fires on a fixed-capacity field. (F-018)
  • P3.10 No unauthenticated mutable field. Fields that must change after signing (update information, mirror lists) live in a separately signed, replay-protected distribution record. Pass: re-pointing an image's update URL without re-signing is detected; the legitimate use case — "I have a server with 1,000 AppImage files. Now my domain name changes and I don't want to re-create all of the AppImages" — is supported without weakening the main signature. (F-016)
  • P3.11 Key rotation is a first-class operation: a rotation certificate signed by the outgoing key introduces the incoming key. Pass: an installed image updates across a rotation without manual re-download; a rotation not signed by the outgoing key is refused; the current dead-end behaviour (VALIDATION_KEY_CHANGED with no path forward) is a regression test. (F-019)
  • P3.12 The updater derives its destination path only from the local target, never from server-supplied metadata. Pass: a .zsync control file naming .bashrc cannot cause a write outside the target image path; updates land via a sibling temp file plus rename(2); T12 fires on a purpose-built hostile control file. (F-027)
  • P3.13 Every build input is immutable and hash-pinned: runtime, mksquashfs/mkdwarfs, zsyncmake, desktop-file-validate, and any downloaded package. No rolling tag anywhere in the dependency graph; a non-2xx response fails the build. Pass: a lockfile with digests exists; the build fails closed on mismatch; T13 fires when the lockfile is bypassed. (F-028, F-029, F-030)
  • P3.14 The reference verifier is a shipped, tested, first-class tool, exercised in CI against images from every supported builder. Pass: verification runs on each release image in CI; a deliberate divergence between builder and verifier fails the build. (F-017)
  • P3.15 Machine-readable corresponding-source provision for bundled copyleft components: component name, version, upstream source URL, distro source-package identity, and which files in the image came from it. Pass: --sources emits it; a fetcher retrieves every listed source archive; the mapping from bundled file → source package is complete. (F-031)

Gate 4 — Confinement without root

  • P4.8 The permission manifest is a request, not a grant. The enforcing side computes the intersection of the manifest, system policy and user policy; a manifest may narrow the default but never widen it. Pass: an image declaring full filesystem access still runs confined under a stricter host policy, and the achieved set — not the requested set — is what --doctor reports (T10). (F-032)
  • P4.9 The manifest is inside the signed region and readable via P2.2 without mounting. Pass: editing the manifest invalidates the signature; an installer displays permissions before first run without executing the image. (F-032, F-033)
  • P4.10 Portals are not sufficient on their own; the manifest must express what portals cannot. Pass: named regression tests for (a) sidecar files created next to a chosen file (SQLite -journal/-wal), (b) popen of a user-configured command, (c) permissions that differ by command-line invocation, and (d) non-UTF-8 filenames. (F-034)
  • P4.11 Runtime state never lives at a predictable, world-writable path. Pass: mount and extraction targets are under $XDG_RUNTIME_DIR (or a per-uid 0700 directory), the name includes the uid, and E17 is green; T15 fires otherwise. (F-023, F-035)
  • P4.12 No SUID binary is required for mounting or confinement. Pass: on a host with no SUID fusermount present at all, the image still runs via user namespaces or extraction, and --why reports which; E5 and E15 green. (F-026, F-036)

Gate 5 — Kill the hardcoded-path tax

  • P5.7 usrmerge correctness: the rule set resolves /lib/usr/lib aliasing so the produced tree is self-consistent regardless of the build host's layout. Pass: images built on a merged-/usr host and a split-/usr host have byte-identical internal layouts for the same inputs. (F-037)
  • P5.8 gettext/TEXTDOMAINDIR is covered by the generic rule and applied unconditionally, not only when some tool happens to emit a wrapper script. Pass: a localised GTK application is fully translated with no plugin installed and no wrapper script generated. (F-038)
  • P5.9 TLS trust-store discovery is part of the rule set: honour SSL_CERT_FILE and SSL_CERT_DIR, then the documented per-distro path list, with no insecure fallback. Pass: an HTTPS-using workload succeeds on Debian, Fedora, openSUSE, Clear Linux and Alpine images with no host-specific configuration and no verification bypass. (F-039)

Gate 6 — Driver ABI that ages

  • P6.6 A named, tested policy per host-boundary library — at minimum libwayland-client, the GLVND trio (libGL/libGLX/libGLdispatch/libOpenGL), libdrm, the Vulkan loader/ICDs and the NVIDIA blob — stating for each whether it is bundled, host-sourced, or conditionally selected, with a regression test. Pass: each library has a rule, a rationale and a test; the capability probe of P6.1 drives the selection. (F-040)

Gate 7 — Storage across apps

  • P7.5 Any change to payload layout — cross-app dedup store, new filesystem, new compression — is benchmarked for delta size, not only for cold size and launch time. Pass: patch-release delta bytes ≤ the SquashFS-xz baseline for the same AppDir change; results published in the format of AppImageSpec#44's benchmark matrix. The measurement in §11.2 shows this gate is passable with DwarFS but not cheaply, and gives the two configurations worth testing first: -S20 --order=path (96.2 % reuse, +8.5 % image) and -S16 (98.4 %, +19 %), against the -S26 default's 25–29 %. Neither block size nor ordering alone is sufficient, so the pass criterion must bind both metrics: delta bytes ≤ the SquashFS-xz baseline and image size within the §6 budget, measured on the same AppDir change. Publish the pair per workload; a delta figure quoted without its size cost is not a result. (F-041, F-042)

Gate 8 — Architecture reach

  • P8.5 Architecture is determined from the ELF headers of the payload binaries, never from file output heuristics or filenames. Pass: an AppDir containing a PNG that file misreads as an i386 binary still produces a correctly-tagged image; changing the file version does not change the result. (F-043)
  • P8.6 Multilib follows FHS architecture-specific directories, and mixing architectures inside one library directory is a build error. Pass: a 64-bit application with a 32-bit helper builds and runs on E8; a deliberately mixed directory fails the build with a clear message. (F-044)

Gate 9 — Integration and lifecycle

  • P9.6 argv[0] dispatch: a symlink named after a declared payload binary runs that binary. Pass: ln -s app.image editcap && ./editcap -h runs the bundled editcap; the set of dispatchable names is declared in the manifest; --install creates exactly those symlinks and --uninstall removes exactly those. (F-045, F-046)
  • P9.7 Desktop-entry generation is escaping-correct. Pass: with an application name and a filesystem path both containing spaces and non-ASCII characters, the generated entry launches on GNOME and KDE: Exec= is quoted, TryExec= escapes spaces as \s, the .desktop file carries the executable bit, and redundant localised keys are stripped. (F-047, F-048)
  • P9.8 MIME and URL-scheme registration is reversible and non-destructive. Pass: entries written into mimeapps.list are tagged so they can be removed; a pre-existing hand-written mimeapps.list is preserved byte-for-byte across install → uninstall; update-desktop-database is invoked rather than default handlers being overwritten. (F-049, F-050)
  • P9.9 Integration must not require the image to have been executed. Pass: on a freshly downloaded, non-executable file, a consumer can obtain name, version, icon, categories, MIME types and thumbnail. (F-010, F-007)
  • P9.10 --uninstall enumerates leftover user state from declared paths in the manifest, not from guesses. Pass: the report lists the application's real $XDG_CONFIG_HOME, $XDG_DATA_HOME and $XDG_CACHE_HOME subdirectories as declared; nothing outside the declaration is offered for deletion; nothing is deleted without consent. (F-051, F-052)

Gate 10 — Performance

  • P10.5 Report launch performance per rung and for N concurrent instances. Pass: cold launch published for FUSE-mount, user-namespace-mount and extract-and-run rungs on the same image and hardware; RSS and page-cache duplication documented when the same image is launched N times, since each mount is independent. (F-026, F-025)

Gate 11 — Spec, governance, migration

  • P11.6 Publish a normative signature specification with test vectors — the artifact that has existed as an unmerged pull request upstream since 2020-03. Pass: the document defines the signed region positively, the digest input, the encoding of key and signature fields, and ships golden test vectors that two implementations agree on. (F-016, F-017, F-018)
  • P11.7 The spec enumerates permitted payload formats with their magic and a process for adding one. "A filesystem that the ELF part can mount" is not implementable by a consumer. Pass: a registry section exists; a conforming consumer can reject an unknown payload format legibly instead of failing with a filesystem-driver error. (F-004, F-005)
  • P11.8 The format version and the media type are both versioned, with a written compatibility policy and a stated cadence. Pass: a consumer can determine the format version without executing the image; the policy states what may change in a minor version and what forces a major one. (F-008, F-053)
  • P11.9 Named upstream engagement targets, each tracked in the scoreboard: (a) payload-format support + extraction fallback in libappimage; (b) a shared-mime-info entry with a test file; (c) the normative signature specification; (d) SOURCE_DATE_EPOCH/mkfs-time support in appimagetool; (e) a reproducible runtime build. Pass: issue/PR links present and their state recorded. (F-003, F-008, F-016, F-029, F-054)

Gate 12 — Ship

  • P12.6 The honest "what this still does not do" section must include the semantics the format cannot preserve, each with the measurement that shows it: mount lifetime versus detached child processes, uid/gid and PID-namespace deviations on the userns rung, endpoint-security alerts on memfd execution, and page-cache duplication across concurrent instances. Pass: each item names the test that demonstrates it. (F-020, F-025, F-024, F-026)

10. Findings table

Classes, per the research brief: A solved by AnyLinux · B open, already covered by a gate · C open, not covered · D forgotten or abandoned · E deliberately rejected, reasoning holds. State and dates as observed 2026-09-05. Threads marked (deep-read) had their full comment history read; the rest are cited from verified index metadata (number, title, state, dates, labels) only.

Class D — forgotten or abandoned (highest value)

ID Repo # State Finding Implication for the successor
F-001 AppImageSpec #26 open, 2019-03-24 (deep-read) Magic bytes collide with e_ident[EI_ABIVERSION] (byte 8) and EI_PAD (bytes 9–10). Three independent rejecters, separated and measured in §1: glibc's ld.so for objects it opens itself (ver == 0 required at ELFOSABI_SYSV, plus an EI_PAD-must-be-zero memcmp) — not the kernel execve path; binfmt_misc masks from qemu-binfmt-conf.sh, which is what breaks the qemu path (the qemu-user ELF loader itself ignores these bytes); and AppImageLauncher's own binfmt_misc registration at offset 8, which is what broke Docker. Fix designed (fixed offset 0x400, agreed on IRC 2022-05, elf-padder/linker-script prototypes exist); never implemented P1.12, T11 — and T11 is right to cover all of EI_OSABI, EI_ABIVERSION and EI_PAD, because open_verify() checks the pad bytes too. Also see AppImageKit#828 (open since 2018-07, reproduced 2025-11) and #144 for how the offset was chosen
F-006 AppImageSpec #32 closed same day, 2021-05-14 (deep-read) TheAssassin designed exactly the successor's Gate 2: "an easier-to-parse 'AppImage header' structure located in between ELF runtime and payload(s)… implements strict versioning… Thumbnails should in the future be moved into a section", prototyped as TheAssassin/type3-runtime, then moved out of the AppImage org. Also names the constraint that kills naïve ELF sections: "signature checking on the runtime is relatively complex, as sections in the middle of a file have to be skipped" P1.1, P1.16, P2.2, P3.8. Do not use ELF sections for post-signing mutable data; use a versioned header region with a positively-defined signed span
F-016 AppImageSpec #29 open, 2019-11-05 (deep-read) .digest_md5 and .sig_key are not in the spec (confirmed by reading draft.md in full) and were introduced without documentation. probonopd: "How come that undocumented stuff like this creeps in?" Two implementations zero different section sets. The .upd_info mutability requirement is stated and unresolved P3.8, P3.10, P11.6
F-017 AppImageSpec #30 open PR, 2020-03-24, unmerged (deep-read) A contributor wrote the normative signature specification. Still unmerged six years on; the last maintainer word (2023-08-29) is "@TheAssassin and I need to fact-check it… it's questionable whether we want to document the current behavior, or whether we want to simplify it first" P11.6. The successor's spec has a starting text; it also has a cautionary tale about who reviews it
F-032 AppImageKit #839 open, 2018-08-14 (deep-read) A complete chain-of-trust design for statically proving an image's confinement, never implemented. Contains the unresolved maintainer disagreement: TheAssassin "the only viable way to distribute profiles for applications is to put those into the AppImages" vs probonopd "a 'bad' app could simply ship a 'relaxed' profile… we can trust the system, but not the app" P4.8, P4.9. The checklist takes one side implicitly; the manifest must be a request, signed, intersected with host policy
F-033 AppImageSpec #5 open, 2016-11-01 (deep-read) The sandbox "privileges" file, open for ten years. probonopd sketched a concrete privileges.conf (network, read_home, write_home, audio, wayland, x11, dbus) in 2024-07; third-party aisap implements permission levels and an [X-App Permissions] Level= desktop key P4.1, P4.9. Prior art for the manifest's key set exists; so does a deployed third-party vocabulary worth aligning with
F-045 AppImageSpec #8 open, 2017-02-09 (deep-read) Invoking alternate payload binaries (Wireshark's capinfos/editcap, ImageMagick's tools). probonopd: "way more functionality than we can have in runtime.c"; a design was proposed and never built P9.6
F-046 AppImageKit #419 open, 2017-06-30 (deep-read) The argv[0] half of the same request. TheAssassin: "It would just bloat the runtime, as it is a totally optional feature." The thread also contains PrusaSlicer's detailed analysis of the mount/child-process lifetime failure (see F-025) P9.6, P1.14
F-028 pkg2appimage #8 open, 2015-11-15, zero comments (deep-read) "Make the builds reproducible." Eleven years, no discussion P3.6, P3.13
F-013 pkg2appimage #20 open, 2015-12-16, zero comments (deep-read) "Save required GLIBC as metadata", with the extraction command in the body. Never done P2.12
F-029 AppImageKit #625 open, 2018-01-14, pr-welcome (deep-read) Reproducible builds; the mkfs timestamp was identified as the only real blocker and a fix was merged, then reopened. Still open. Downstream: appimagetool#72 (SOURCE_DATE_EPOCH, open), type2-runtime#133 (runtime build not reproducible, open) P3.6, P3.13
F-031 linuxdeploy #13 open, 2018-08-13 (deep-read) GPL corresponding-source provision, opened by TheAssassin with a full design (a file listing library name, version, source origin, and which files in the bundle came from which source package, plus a source-download tool). Never implemented P3.15. Note this is strictly more than the checklist's --licenses
F-053 AppImageSpec #25, #24 open, 2019-01-24 (deep-read) A KDE developer asked for metadata about bundled modules — explicitly to know "if bundled software might have known security issues" and to satisfy licence obligations — i.e. an SBOM, in 2019. probonopd: "Not sure it should be part of the AppImage spec, though" P3.7, P11.8
F-009 AppImageKit #578#594 #578 closed unmerged; #594 merged 2017-12-20 (deep-read) The XDG thumbnailer landed, but the review surfaced that .DirIcon is frequently a symlink, producing "WARNING: Unable to extract non regular file .DirIcon" and a munmap_chunk(): invalid pointer crash in appimaged P2.11. The thumbnail source must be a regular file
F-010 AppImageKit #203 closed 2018-11-20 (deep-read) "Write GNOME thumbnailer" was closed by pointing at a third-party project outside the AppImage org. TheAssassin, 2018-11-11: the XDG thumbnailer "is only run when launching an AppImage, or when using tools like AppImageLauncher" P2.11, P9.9, and matrix correction C4
F-036 type2-runtime #137 open PR, 2025-11-01, unmerged (deep-read) Backport of uruntime's user-namespace mount fallback into the upstream runtime. Blocked on a copyright review and probonopd's "are there any known real world use cases for which the added complexity from this code path is justified?" P4.12. The rung AnyLinux relies on is not upstream, and the successor should expect to own it
F-054 AppImageKit #978 open, 2019-07-04, zero comments (deep-read) "Review licenses for software embedded or shipped otherwise in AppImages", opened by TheAssassin. Never actioned P3.7, P3.15
F-055 linuxdeploy #287 open PR, 2024-07-25, unmerged (deep-read) Runtime excludelist updating + --ignore-excludelist. Root problem: excludelist changes require a new tool release to "trickle down" to every user Evidence that the excludelist is a distribution failure, not only a content one — supports the checklist's rejection of the doctrine
F-056 linuxdeploy #160 closed unmerged, 2021-01-03 (deep-read) TEXTDOMAINDIR for gettext; rejected because "The wrapper script is only ever generated when needed", then exiled to a separate misc-plugins repository P5.8. The GTK locale problem has a known fix that never became default
F-057 AppImageKit #130, #119, #304 #130 opened 2016-04-03, closed unmerged 2016-11-16; #304 open since 2016-11 Multi-architecture images: an implementation PR in 2016 and a request to embed other-architecture runtimes, both unlanded P8.1. Prior art exists for the fat-image gate

Class C — open, not covered by the checklist

ID Repo # State Finding Implication
F-027 AppImageUpdate #160 open, 2020-10-29, bug, with a proof-of-concept video (deep-read) An attacker who can influence the update channel can set the .zsync filename to .bashrc; updating an AppImage from $HOME overwrites the dotfile. Unfixed for six years P3.12, T12. This is the single most serious unpatched defect found
F-007 AppImageSpec #60 open, 2026-03-31 (deep-read) Gear Lever's maintainer: consumers cannot safely probe capabilities because "if an app does not implement one of those commands, it might just launch when I am trying to extract the desktop file" P2.9, T14. A consumer-side hazard the checklist does not mention
F-023 AppImageKit #1215 open, 2022-09-19 (deep-read) Extract-and-run uses $TMPDIR/appimage_extracted_$MD5DIGEST — no uid, no pid. Concurrent runs race; a second user on the same host gets fopen error: Permission denied and cannot run the image at all. TheAssassin declined to fix ("NO_CLEANUP … It's not a solution", "requires quite some complexity"); probonopd suggested randomising P4.11, T15, E17
F-035 AppImageKit #452 open, 2017-08-14 (deep-read) An AppArmor abstraction contributed by a user, with the note "This is not perfect due how AppImage works (well, it loads files from /tmp where anyone can write)". TheAssassin: the planned type 3 "will no longer rely on /tmp but use some per-user tempdir (e.g., /run)" P4.11. The fix is named upstream and unbuilt
F-020 type2-runtime #99 open, 2024-11-23 (deep-read) Under systemd, mounts are never released: "This ends up accumulating a bunch of stale fuse mounts and after long enough the mount limit is reached and no more appimages can be run." Reproduced with int main(){return 0;} P0.7, P1.14, T16, E20
F-021 type2-runtime #138 open PR, 2025-11-08 (deep-read) Orphaned FUSE daemon after exit; the keepalive_pipe design is caught between closing too early (breaks AppRun, #119) and too late (leaks) P1.14, T16
F-022 AppImageKit / AppImageLauncher #1211, AIL#730 closed / open "fusermount: too many FUSE filesystems mounted" and "The lite launcher spammed fuse mounts until FUSE overload happened" — the same resource-exhaustion class from two directions P0.7, T16
F-025 AppImageKit #419 open (deep-read) PrusaSlicer's maintainer documents that (a) launching the same image twice mounts the payload twice with no sharing, and (b) a child process started from the mounted $APPDIR dies when the launcher exits, because the FUSE daemon is torn down via keepalive_pipe. Their workaround was to launch a second copy of the whole AppImage P1.14, P10.5, P12.6
F-024 AppImageLauncher #380 closed 2020-12-21 (deep-read) Commercial EDR classifies the binfmt-bypass memfd execution as "Defense Evasion via Process Hollowing / IOA: ElfExecutedFromMemory", severity High. The maintainer declined to change it. uruntime uses memfd_exec for its bundled helpers (NO_MEMFDEXEC=1 opts out) P0.9, E19. The successor's noexec /tmp story runs into corporate endpoint policy
F-019 AppImageUpdate #260 closed 2026-05-01 (deep-read) Key rotation produces VALIDATION_KEY_CHANGED with no way to proceed; probonopd: "Yes, correct. This works as designed." P3.11, correction C7
F-018 AppImageKit #1010 open, 2019-11-25 (deep-read) .sig_key is a fixed 8192-byte pre-allocated section in a pre-compiled runtime; keys with subkeys/extra UIDs overflow it ("cannot embed key in AppImage: size exceeds reserved ELF section size"). A real publisher abandoned signing over it. @ssaavedra additionally shows the implementation signs the hex string of the digest, not the digest P3.9, T18
F-037 go-appimage #315 open, 2024-11-28 (deep-read) On usrmerge hosts, copying "at the same path as on the build system" splits dependencies between AppDir/lib/<triplet> and AppDir/usr/lib; GIMP and others break; a second reporter hit it in 2025-11 with libpthread.so.0 P5.7, E21
F-042 AppImageSpec #44 open, 2024-08-08, help wanted (deep-read) probonopd's proposed 45-cell benchmark matrix explicitly includes zsync efficiency (delta update size) as a third axis alongside size and startup time. Community results exist (CSV and tables in-thread) but the matrix was never completed P7.5, budget correction
F-034 AppImageSpec #5 open (deep-read) A detailed argument for why portals alone cannot express an application's real needs, from @zzo38: popen of a command chosen interactively or in a config file; non-Unicode filenames; sidecar files derived from the selected name (a SQLite journal or WAL next to the chosen database); resources named in configuration, arguments or environment; and permissions that differ per invocation. A separate commenter adds the packaging objection that portals "force developers to modify their application just for AppImages" P4.5 → P4.10
F-050 AppImageSpec #22 open, 2019-01-02 (deep-read) MIME-type registration data is unspecified; libappimage copies XML from any mime/ subdirectory, which the reporter flags as probably wrong. "PRs welcome!" P9.8

Class B — open, covered, but sharpened by a concrete failure

ID Repo # State Finding Sharpens
F-002 AppImageLauncher #674 opened 2024-11-18, closed 2025-02-10, 4 reactions (deep-read) appimagetool switched its default compression to zstd on 2023-07-07 (commit 810916a858, named in-thread by FreeCAD's maintainer); deployed libappimage supports only xz and zlib, so the breakage surfaced ~16 months later when zstd images reached users — FreeCAD 1.0 (2024-11), then Navicat (2024-12-31) and Postman (2025-01-29) — and took ~3 months to close. The (null) in the error is diagnostic, not incidental, and it names the root cause exactly. The text is squashfuse's SQFS_BADCOMP handler in util.c, which prints sqfs_compression_name(comp); that indexes a table declared [SQFS_COMP_MAX] (16) but initialised only for the ids the build knows, so an id with no entry reads back an implicitly-zeroed slot — NULL, which printf renders (null). "zstd" was added to that table in squashfuse 5986f1d2, 2017-03-27. libappimage pinned squashfuse at 1f98030 — a commit from 2016-10-10, five months before that — and held the pin until 2024-11-25, when 44e4db94 ("Update squashfuse and add support for Zstd") moved it, five days after TheAssassin wrote in this thread "update is in the pipeline already. I just need a few days." So a build merely lacking the zstd decompressor would have said "uses zstd compression"; (null) means the pinned name table predated zstd support by seven years. TheAssassin: "It's libappimage that needs to be updated in the distribution." User advice: "The only solution at the moment is to remove AppImageLauncher from your system" P2.4 → P2.7, P0.6, E16. The lag, not the switch, is the finding: a tooling default and its deployed consumers were 16 months apart
F-003 AppImageSpec #36 open, 2022-10, 80 comments (deep-read) TheAssassin, 2025-10-31: "I've just come across an AppImage using DwarFS in the wild. libappimage cannot integrate those… I'd appreciate a PR to libappimage that implements DwarFS." And: "The spec should finally specify exactly the payload format. Right now, there is no mention of squashfs… we essentially are living in the wild west." probonopd's adoption bar: whether a new format "would justify creating work for all the tools on awesome-appimage to adopt it. Which is quite a high bar" P2.3, P2.6 → P2.8, P11.7
F-011 AppImageLauncher #752 open, 2025-10-29 A real DwarFS AppImage failing "Failed to register AppImage in system via libappimage" — the concrete instance behind F-003 P2.8 — a named regression test
F-012 AppImageLauncher #771 open PR, 2026-03-15, unmerged (deep-read) Community PR adding extraction-based fallback integration when the libappimage path fails — exactly the mechanism TheAssassin described ("libappimage should have a fallback mode to just use the AppImage in question… --appimage-extract (which should be a mandatory parameter)") P2.8
F-004 AppImageSpec #10 open, 2017-04-10 (deep-read) probonopd confirms the type-2 spec is filesystem-agnostic by design and that a consumer should "get the offset to the beginning of the filesystem… and then determine from the magic bytes of that filesystem what it actually is." An alternative zip-payload implementation existed P1.13, P2.3, P11.7
F-005 uruntime src/main.rs current (source read) get_image() reads 4 bytes at the payload offset and matches "DWAR" or "hsqs". This is the de-facto probe and should be standardised P1.13, P11.7
F-008 AppImageKit #144 closed 2017-11-23, 92 comments (deep-read) Origin of the offset-8 magic: bytes 8–15 were believed "Unused/padding". Registered application/x-iso9660-appimage (shared-mime-info commit 01fa61fc, 2016-06-27) and application/vnd.appimage (c643cab2, 2017-09-07). TheAssassin objected to the unversioned name and was overruled. shared-mime-info's rule is "Magic offset must be as small as possible… the worst case scenario for ISO images is 32k inside the file. This is too big for a sniff buffer"; the largest offset then in the database was ~2112 P1.12, P2.10
F-030 appimagetool #57 closed 2024-08-05, bug (deep-read) The runtime is fetched at build time from a mutable continuous tag with no hash and no signature; a 404 body (Not Found) was concatenated into the image and reported as "Success". Fixed by adding HTTP status checking; cryptographic verification remains open in #44 P3.13, T13
F-038 appimagetool #117 open PR, 2025-11-01 (deep-read) A supply-chain-hardening PR removed its own hash verification with the note "Continuous releases update regularly, making hash pinning impractical" P3.13 — the mutable-tag release model structurally prevents pinning
F-039 appimagetool #24, #29 closed; PR unmerged (deep-read) Cross-distro CA-bundle discovery; a PR honouring SSL_CERT_FILE/SSL_CERT_DIR was closed in favour of an alternative that dropped the env vars, with TheAssassin: "it is a viable workaround to require from users to manually download the runtime if the TLS validation fails" P5.9
F-014 AppImageKit #913 open, 2019-01, 6 reactions "Check for GLIBC_/GLIBCXX_ and refuse to build AppImage if too new" — the build-side counterpart of declared minimum requirements. appimagelint (referenced in AIL#170) does exactly this check today P2.12
F-043 AppImageKit / appimagetool #810, #1042, appimagetool#36 closed / open Architecture detection broke when file changed its output; PNGs were misidentified as i386 binaries; an open issue asks to use only ELF files for the determination P8.5
F-044 linuxdeploy #18, #251 closed 2018-08-31 / PR unmerged (deep-read) Multilib was resolved by adopting FHS usr/lib32, over probonopd's objection that it was an "edge case". The unmerged 2023 PR shows cross-arch lib32 handling is still incomplete P8.6
F-047 AppImageLauncher #170 closed 2019-08-22, bug, high priority (deep-read) GNOME hides entries and shows "Untrusted desktop entry file" unless Exec= is quoted, TryExec= escapes spaces as \s, and the .desktop file is executable P9.1 → P9.7
F-048 AppImageKit #833 open, 2018-07-30 Redundant localised desktop keys cause integration bugs; opened by TheAssassin, never fixed P9.7
F-049 AppImageKit #458 open, 2017-08-30 (deep-read) URL-scheme registration requires editing mimeapps.list, a file shared with the user and the system: "we'd have to add a comment to every line managed by appimaged to indicate it shouldn't be edited/deleted, and to allow it to be found again in case an AppImage is deleted" P9.8
F-051 AppImageLauncher #517 open, 2022-07-29 A packager asks for a declared user-data directory so uninstall can clear it. Never specified P9.10
F-052 AppImageLauncher / AppImageKit AIL#456, #769 closed (deep-read) Leftover state and context-menu entries after uninstall; the AppImageKit thread was closed by pointing at an external tool P9.10
F-026 type2-runtime #71 open, 2024-08-19 (deep-read) Mounting FUSE inside a user namespace avoids the SUID helper, but "it's almost like a sandbox, so the behavior won't just be 1:1 with using fusermount" — the uid changes, nvtop cannot see processes, the cpu-x daemon breaks. TheAssassin cautions userns is restricted on many OSes. Contains measured launch times showing pre-extracted beats FUSE-mounted by ~1 s on Brave/OnlyOffice/Inkscape P1.4 → P1.15, P4.12, P10.5
F-040 pkg2appimage / linuxdeploy / linuxdeployqt pkg2appimage#510, linuxdeploy#152, #89, linuxdeployqt#631 mixed A cluster of host-boundary library decisions repeatedly re-litigated: bundling libwayland-client breaks Vulkan apps; libOpenGL/libGLdispatch/libGLX (GLVND) must be excluded P6.6
F-041 AppImageSpec #32 closed (deep-read) TheAssassin on why the payload format matters for updates: "in squashfs images, files are block-aligned, meaning that the same file contents create the same blocks. Thus, if a file does not change, it doesn't have to be retransmitted" — the property zsync exploits. Measurement (§11.2) refines the implication in both directions: the property is a function of block granularity and file ordering jointly — both DwarFS build flags, not consequences of the filesystem — so a new payload format loses the tuning rather than the property; but recovering it costs image size, so TheAssassin's conclusion that the payload format matters for updates survives the measurement even though his stated mechanism is only half of it P7.5, budget correction

Class A — raised upstream, solved by AnyLinux (cite as parity evidence, do not re-litigate)

ID Repo # State Finding Cite at
F-060 go-appimage #49 open, 2020-08 Calling ld-linux directly breaks /proc/self/exe; the reason sharun exists. Already cited by the AnyLinux FAQ P1.3
F-061 AppImageKit #472 closed, 53 comments "AppImage(s) on NixOS (doesn't work)" — the non-FHS failure AnyLinux removed P1.2 (E3)
F-062 AppImageKit #1015, #1112 closed; 23 and 17 reactions "Make AppImages run on Alpine" / "Support musl-based AppImages" — the two most-reacted host-libc portability requests in the corpus. (Scoped deliberately: the corpus-wide portability maximum is F-064's statically-linked-runtime demand at 81 reactions, which is the same problem approached from the runtime side) P1.2 (E2)
F-063 linuxdeployqt #638, #374 closed 2026-07-02; 5 reactions (deep-read) "ERROR: The host system is too new" — the excludelist doctrine still blocking developers in 2026 (a user could not use C++23 because they had to build on Ubuntu 22.04). probonopd endorses static musl as the alternative §1 thesis; P1.2
F-064 AppImageKit #877 closed; 140 comments, 81 reactions — the most-reacted item in the corpus "Statically linked runtime" — the demand AnyLinux's zero-requirement runtime answers P1.4
F-065 AppImageKit #852 closed, 17 comments ARGV0 breaks packaged software — the tripwire T2 already encodes this T2, P1.11
F-066 AppImageKit #391, #641 open / closed AppRun appending to PATH and other environment variables, leaking into children — the leak the anylinux preload cleans P1.8, T1
F-067 type2-runtime #116 closed 2025-03-06 (deep-read) The musl allocator launch-time regression — and its resolution via mimalloc (PR#120) §3 non-goals, correction C3

Class E — deliberately rejected, reasoning still holds

ID Repo # State Finding Where it belongs
F-070 AppImageKit #152 closed 2024-06-29, idea (deep-read) Eight years of bubblewrap investigation, closed by pointing at third-party aisap. probonopd: "Personally I am not very interested in sandboxing, so I leave this exercise to others." Contains Alpine's rationale for removing firejail and the mechanical constraint that bwrap cannot consume a FUSE mount directly (mount first, then sandbox) §3 non-goals; P4.12
F-071 AppImageSpec #35 open, wontfix (deep-read) Launch-time integrity verification rejected on measured performance grounds, with a first-launch-only counter-proposal §3 non-goals; correction C1; P1.16
F-072 AppImageUpdate #16 closed 2018-07-26 (deep-read) A trusted keyring was explicitly rejected in favour of transitive trust on the embedded key. Also records probonopd's "do not warn on unsigned" UX position vs TheAssassin's browser-TLS model §3 non-goals with counter-argument; correction C6
F-073 AppImageKit #949 closed 2022-02-13 (open 34 months), bug, high-priority (deep-read) TheAssassin on the reference verifier: "you first need to trust the key, then you can trust valid signatures made with it. But that 'trust the key' bit isn't implemented at all by this validate tool. So the tool is pretty pointless." §3 non-goals; P3.14
F-074 appimagetool #55 closed unmerged 2025-12-02 (deep-read) Two metadata paths rejected: "all tools that verify AppImages or integrate them into the system would have to check 2 different file paths as well" §3 non-goals; P2.13
F-075 AppImageLauncher #96, #26 closed (deep-read) The binfmt_misc interception design and its loop-execution hazards, with azubieta's objection that it risks "making AppImage depending on it. Which is one of the strongest points of the solution: not requiring anything to run an AppImage" §3 non-goals
F-076 AppImageKit #558, #559 closed 2017-12-04, PR unmerged (deep-read) A non-finding, recorded deliberately. "Return the app's return value" looks like a lost fix; the thread shows execvp replaces the process so the exit status is already the payload's, and the PR author agreed and withdrew it Do not add a gate item; see C10

Verified primary-source facts (not issue-derived)

ID Source Fact Used by
F-080 AppImageSpec/draft.md (280 lines, newest commit 51c2a146 2026-04-03) Type 2 MUST contain 0x414902 at offset 8; MUST have "a filesystem that the ELF part can mount" (no filesystem named); .sha256_sig is defined as a signature over the SHA-256 of the AppImage with only .sha256_sig zeroed.sig_key and .digest_md5 do not appear anywhere in the document; ./usr/ prefix and FHS are RECOMMENDED; .DirIcon MUST exist and SHOULD be 256×256 PNG; a static runtime MUST support TARGET_APPIMAGE; update transports are zsync, gh-releases-zsync, pling-v1-zsync and deprecated bintray-zsync, one per image. The revision history's last entry is "Draft | 2016-09-18" P1.12, P3.8, P11.6, P11.7, C5
F-081 /usr/share/mime/magic (shared-mime-info, as installed) application/vnd.appimage is matched by ELF at offset 1 plus A, I, \x02 at offsets 8, 9, 10 — the registered media type is welded to the magic offset P1.12, P2.10
F-082 uruntime xtask/src/main.rs add_magic() seeks to byte 8 and writes AI\x02 (RI\x02 for RunImage); add_sections() uses objcopy --add-section to embed .digest_md5, .envs, .sha256_sig, .sig_key, .upd_info with noload,readonly P1.12, C-§1
F-083 uruntime README CLI provides --appimage-signature (print) and --appimage-addsign (embed) but no verification; --appimage-unshare, URUNTIME_EXTRACT/_CLEANUP/_UNSHARE/_MOUNT control the ladder; NO_MEMFDEXEC=1 disables memfd execution §2.3 matrix; P0.9, E19
F-084 pkgforge/AppImageUpdate README "SHA1 verification ensures downloaded files are valid"; limitations include "No GPG signature verification (not implemented)" C2, T17
F-085 DwarFS LICENSE Reader code MIT, writer (mkdwarfs) GPL-3.0: "you can use mkdwarfs to create images and then use these images with only the MIT-licensed code" C9
F-086 AnyLinux FAQ.md Acknowledges the thumbnail regression ("you need an AppImage thumbnailer that supports DwarFS", naming two third-party tools) and the NixOS appimage-run failure ("all you have to do is disable appimage-run") §2.8 matrix; P2.8
F-087 AnyLinux fix-namespaces.md The hook uses pkexec to write kernel.apparmor_restrict_unprivileged_userns = 0 to /etc/sysctl.d/ and apply it — a permanent, system-wide weakening, not a per-launch workaround P4.6; §3 non-goals

11. Open questions and unresolved threads

Recorded as unresolved rather than guessed, per the evidence standard.

  1. Does the offset-8 magic still break a statically linked runtime? — answered by measurement (2026-09-05); what remains open is narrower. It does not, on the native execution path: a static binary carrying a released runtime's exact e_ident (EI_OSABI=SYSV, 41 49 02 at bytes 8–10) runs normally under kernel execve on glibc 2.39/x86-64, as does the dynamic equivalent, because the kernel does not re-verify e_ident; glibc only rejects it for objects ld.so opens itself. Static linking is therefore not an escape from the two mechanisms that do bite, because both are libc-independent: binfmt_misc mask matching (the qemu path) and any host-registered binfmt_misc entry keyed on offset 8 (the AppImageLauncher/container path). See §1 for the mechanisms and §12 for both reproductions. What is still open: breadth, not mechanism — other libcs, older glibcs, non-x86 values of LIBC_ABI_MAX, and third-party ELF parsers that inspect e_ident for their own reasons. E18 remains the gate, and P1.12's cost in MIME identification is unchanged by this result.

  2. Whether DwarFS payloads are zsync-friendly. — answered, and the answer is a build-flag problem, not a format problem. No measurement exists in the upstream corpus, which is why this was recorded as open; there is one outside it — pkgforge-dev/zsync-efficiency-test (2026-04) — reporting DwarFS at 18 % against SquashFS's 95 %, improved to 68 % by mkdwarfs --order=path, and concluding that "block size and compression level doesn't really change the zsync efficiency". Re-measured here under controlled conditions, that last conclusion does not hold, and the corrected picture is more useful. Method: one 296 MB / 1,457-file AppDir built twice — base, then a patch-release-sized change of six libraries (≈ 2 % of bytes) — through each payload format, with zsync reporting how much of the new image it can rebuild from the old (zsyncmake + "Target N% complete"; commands in §12). zstd:level=22 throughout; dwarfs 0.15.7, zsync 0.6.2, x86-64. Every figure is the single "Target N% complete" line of its own run, from one serial pass:

    Payload Block size File ordering Image zsync reuse ≈ update bytes
    SquashFS-xz 1 MiB (-b 1M) n/a 83.7 MB 98.3 % 1.4 MB
    SquashFS-zstd 1 MiB (-b 1M) n/a 91.5 MB 98.3 % 1.6 MB
    DwarFS 64 MiB (-S26) default (nilsimsa) 77.8 MB 28.7 % 55.5 MB
    DwarFS 64 MiB (-S26) --order=path 81.8 MB 24.9 % 61.4 MB
    DwarFS 1 MiB (-S20) default (nilsimsa) 90.9 MB 31.0 % 62.7 MB
    DwarFS 1 MiB (-S20) --order=path 90.8 MB 96.2 % 3.5 MB
    DwarFS 64 KiB (-S16) default (nilsimsa) 100.0 MB 98.4 % 1.6 MB
    DwarFS 64 KiB (-S16) --order=path 100.0 MB 98.3 % 1.7 MB

    DwarFS's delta-hostility is a tuning artefact, but neither lever fixes it alone. At -S26 — the tuning AppImage builds actually use — reuse is 25–29 %. Moving to SquashFS's own 1 MiB block while keeping the default ordering buys almost nothing (28.7 % → 31.0 %); applying --order=path at 64 MiB is worse than doing nothing (28.7 % → 24.9 %). The two are complementary, not ranked: together at 1 MiB they reach 96.2 %, a 65-point swing that neither produces on its own. The alternative route is brute force — at 64 KiB blocks, reuse is 98.4 % and file ordering stops mattering at all (98.4 % against 98.3 %), because the blocks are now smaller than the damage.

    Recovering it is not free, and that is what the budget should encode. SquashFS-xz remains the best point on both axes at once: 83.7 MB and 1.4 MB of update traffic. The best DwarFS configurations pay for their delta efficiency in size — -S20 --order=path costs +8.5 % image for 2.5× the update bytes, -S16 costs +19 % image for roughly parity on update bytes. Against the -S26 default, that is 16× fewer update bytes for 17 % more image, or 35× fewer for 29 % more. So the checklist's instruction to baseline against SquashFS-xz survives — but for a different reason than the one this document originally gave: not because DwarFS "is not block-aligned the way zsync exploits", but because its defaults are delta-hostile and every fix costs image size. That is a published build parameter, not a filesystem property.

    This is also where the one prior measurement is worth correcting rather than dismissing: its finding that ordering is the lever is right, and its conclusion that "block size … doesn't really change the zsync efficiency" is right only within the range it tested — 64 MiB to 1 MiB buys two points. It does not survive the step to 64 KiB, which buys 67.

    Scope, stated because it bounds the claim: one AppDir, one synthetic patch shape, one compression level, one architecture, and a patch that rewrites bytes throughout six files rather than replacing them as a real release would. A second, larger AppDir (672 MB) with an ≈ 11 % patch reproduces the -S26 ordering — 88.5 % SquashFS-xz against 12.5 % (nilsimsa) and 5.2 % (--order=path) — but settles nothing about the small-block rows. P7.5 should still run this across the real reference workloads; it now has a concrete recipe to test rather than an open question.

  3. The status of TheAssassin/type3-runtime and the AppImageX organisation. Referenced in AppImageSpec#32 as a WIP type-3 design with an "AppImage header" structure. Not harvested here. If it contains a drafted header layout, it is the most directly reusable prior art for P1.1 and should be read before designing the header.

  4. Whether libappimage's tracker holds a more complete integration-algorithm specification. Several threads defer to libappimage issues — notably libappimage#23 ("Avoid icon/mime clobbering by using <icon name=… />", open since 2017-07-14) for MIME/desktop integration. A title sweep of that tracker finds no licences thread: the licence discussions live in AppImageKit#978 and linuxdeploy#13 themselves. That repository was not in scope for this harvest and is the obvious next target.

  5. What appimaged/AM/Gear Lever actually do on integration. P9.1's "byte-identical XDG trees" criterion needs a reference implementation of what "correct" looks like; upstream's answer has been "read libappimage", which is precisely what AppImageSpec#36 says should be specified instead.

  6. Whether a successor can obtain a new shared-mime-info entry at all, given the project's documented preference for small magic offsets and its requirement of a test file. Measured from F-008's timeline, with the anchors stated because they change the answer: the type-1 entry took about six weeks from the request being raised (AppImageKit#144 opened 2016-05-18) to landing (01fa61fc, 2016-06-27) — but only one day from the patch actually reaching freedesktop (bug 96685, submitted 2016-06-26). The type-2 entry took exactly five months from its freedesktop bug (100608, filed 2017-04-07) to landing (c643cab2, 2017-09-07). The lesson is that the freedesktop-side latency is not the constant — the work of producing a magic the maintainers will accept, plus a test file, is — and propagation to distributions took longer still.


12. Appendix — reproducing this research

All network access went through the sanctioned proxies. The harvest is mechanical and re-runnable:

API="https://api.gh.pkgforge.dev"

# One repo, both kinds, all states, paginated to exhaustion.
for kind in issues pulls; do
  page=1
  while :; do
    curl -sS "$API/repos/AppImage/AppImageKit/$kind?state=all&per_page=100&page=$page" \
      -o "AppImage_AppImageKit.$kind.p$page.json"
    n=$(jq length "AppImage_AppImageKit.$kind.p$page.json")
    [ "$n" -lt 100 ] && break
    page=$((page+1))
  done
done

# Separate real issues from PRs (the /issues endpoint returns both).
jq -c '.[] | select(.pull_request|not)' AppImage_AppImageKit.issues.p*.json > issues.jsonl

# The highest-yield set: closed-but-never-merged PRs.
jq -r 'select(.state=="closed" and .merged==null) | "\(.number) \(.title)"' prs.jsonl

# Raw files and non-GitHub pages.
curl -sS "https://api.rv.pkgforge.dev/https://raw.githubusercontent.com/AppImage/AppImageSpec/master/draft.md"

The MIME finding (F-081) is reproducible on any system with shared-mime-info installed:

grep -a -A4 'application/vnd.appimage' /usr/share/mime/magic

Pitfalls worth repeating for whoever extends this: /issues returns pull requests too (filter on the pull_request key); /issues also omits a few pull requests entirely, so PR counts must come from /pulls (11 PRs across this corpus are missing from /issues — see §3); state=all is required or you see only the open tail; pagination fails silently, so loop until a page returns fewer than per_page items; and the updater's repository is AppImageCommunity/AppImageUpdate, not AppImage/AppImageUpdate, which returns 422 from the search API. One more, because it produced a wrong number during review: open issues and open pull requests must be counted separately. At the snapshot above there are 1,126 open issues and 126 open PRs; 1,252 is their sum, not the issue count.

Reproducing the e_ident results (F-001, §1). The glibc half needs nothing but a compiler:

cc hello.c -o hello                                     # or -static; both behave the same
printf '\x00'         | dd of=hello bs=1 seek=7 conv=notrunc  # EI_OSABI  = SYSV, as real runtimes
printf '\x41\x49\x02' | dd of=hello bs=1 seek=8 conv=notrunc  # AI\x02, the type-2 magic
./hello                                                 # runs: kernel execve does not re-verify e_ident
ld-linux-x86-64.so.2 ./hello                            # "ELF file ABI version invalid"

The container half needs binfmt_misc and any container runtime. It reproduces AppImageKit#828 exactly, and shows that the cause is a host-registered interpreter that the container cannot see — not the magic bytes as such:

mount -t binfmt_misc none /proc/sys/fs/binfmt_misc            # if not already mounted
# AppImageLauncher's pre-2022 registration shape: offset 8, magic AI\x02, path interpreter, no F flag
echo ':appimage-test:M:8:AI\x02::/opt/host-only/interpreter:' > /proc/sys/fs/binfmt_misc/register

docker run --rm -v "$PWD":/w ubuntu:14.04 /bin/bash -c '/w/hello'
#   -> /bin/bash: /w/hello: No such file or directory      (ENOENT; the interpreter is not in the image)
# the same binary with bytes 8-10 zeroed runs; so does the magic-carrying binary once the
# registration is removed, or once it is re-registered with the F (fix binary) flag:
echo -1 > /proc/sys/fs/binfmt_misc/appimage-test
echo ':appimage-test:M:8:AI\x02::/opt/host-only/interpreter:F' > /proc/sys/fs/binfmt_misc/register
docker run --rm -v "$PWD":/w ubuntu:14.04 /bin/bash -c '/w/hello'   # -> the host interpreter runs

Reproducing the DwarFS/zsync delta measurement (§8, §11.2). Build the same AppDir twice — a base version and a patch-release-sized change — through each candidate payload format, then let zsync report how much of the new image it can reconstruct from the old one. zsync prints that as "Target N% complete" after reading the seed file, before any download, so no server is needed:

mksquashfs AppDir-v1 sq.v1.img -comp xz -b 1M ; mksquashfs AppDir-v2 sq.v2.img -comp xz -b 1M
mkdwarfs -i AppDir-v1 -o dw.v1.img -S20 -C zstd:level=22 --set-owner 0 --set-group 0 \
         --no-history --no-create-timestamp --force
mkdwarfs -i AppDir-v2 -o dw.v2.img -S20 -C zstd:level=22 --set-owner 0 --set-group 0 \
         --no-history --no-create-timestamp --force
for f in sq dw; do
  zsyncmake -u "http://example/$f.v2.img" -o "$f.v2.img.zsync" "$f.v2.img"
  mkdir -p w && cp "$f.v1.img" "w/$f.v2.img"        # zsync seeds from the same-named local file
  ( cd w && zsync -i "$f.v2.img" "../$f.v2.img.zsync" ) 2>&1 | grep -o 'Target [0-9.]*% complete'
  rm -rf w
done

A note on the sources. Issue bodies and comments are text written by arbitrary people. They are treated here as evidence to be weighed, never as instructions. Where a claim in a thread was checkable against a primary source it was checked, and in one case (C9) the check contradicted the commenter. Nothing was posted to any upstream repository in the course of this research.


13. Verification pass (2026-09-06)

The document was re-verified end to end against primary sources, and eight review issues filed against it were checked one by one rather than applied. Both directions produced corrections: some of the document was wrong, and some of the review was wrong. Both are recorded here, because a review pass that only records the first kind is not a check.

What was re-verified and held. All 96 issue and pull-request references in the document as reviewed were re-fetched and cross-checked for state, open/close date, merge status, reaction count and comment count against a fresh full harvest of the ten-repository corpus; every one matched except #130 (below). Every cell of the §3 table reproduces exactly — 3,257 issues, 1,330 PRs, 229 closed-unmerged, all ten rows — as do the newest issue (2026-08-26, AppImageLauncher#791), the newest PR (2026-09-03, go-appimage#403) and the most-reacted item (#877, 81 reactions). The load-bearing quotes were located verbatim in the fetched threads, including piegamesde's openssl speed output (sha256 at 464,044.03k, i.e. ~464 MB/s), probonopd's "5 GB AppImage … think spinning disks", TheAssassin's "sections in the middle of a file have to be skipped", "way under 1 MiB" / "half a GiB of runtimes", the DwarFS-in-the-wild and "wild west" comments, probonopd's awesome-appimage adoption bar, and the (null) compression error text. The wontfix label on AppImageSpec#35, the shared-mime-info commits 01fa61fc (2016-06-27) and c643cab2 (2017-09-07), and the squashfuse SQFS_BADCOMP provenance of the (null) string all check out.

Corrected in the document.

  1. The e_ident mechanism (§1, F-001, §11.1) was wrong in a way that mattered. The old text said glibc's ld.so refuses the byte "when EI_OSABI == ELFOSABI_GNU" and that "qemu-user validates it too". Real runtimes are ELFOSABI_SYSV, where the rule is simply must be zero; ld.so only applies it to objects it opens itself, and the kernel execve path does not check at all; qemu-user's loader does not read these bytes; and the Docker failures the document cited are caused by AppImageLauncher's own binfmt_misc registration, not by either. All four were re-derived from source and reproduced locally.
  2. The zstd default is from 2023-07-07, not 2024-11 (§1, F-002). 2024-11 is when zstd images reached users — a sixteen-month lag that strengthens the point being made.
  3. AppImageKit#130 closed unmerged 2016-11-16 (F-057); the old text gave its opening date.
  4. libappimage#132 was mis-cited (§11.4): it is an svgz-icon bug, not a licences thread, and no licences thread exists in that tracker.
  5. F-062's "most-reacted portability requests" is now scoped to host-libc requests, since #877 (81 reactions) outranks both and is also portability.
  6. The shared-mime-info durations (§11.6) now state their anchors — six weeks / one day for the 2016 entry depending on where you start, five months for the 2017 one — because the anchor changes the answer by more than an order of magnitude.
  7. The DwarFS/zsync claim in §8 is now measured rather than asserted; see below.
  8. A pitfall was added to §12: /issues omits 11 PRs in this corpus, so PR counts must come from /pulls.

Where the review was wrong, and the document was right. A filed issue reported that §3's open-issue count of 1,126 was wrong and should be 1,252. It should not. An independent re-harvest gives 1,126 open non-PR issues at this document's snapshot; 1,252 is that number plus the 126 open pull requests. The original figure stood, and the text now states both numbers so the sum cannot be mistaken for either. A second issue reported that no reading of the F-008 timeline yields the "5 months in 2017" figure; measured from the freedesktop bug that actually carried the request (100608, filed 2017-04-07) to the commit that landed it (2017-09-07), it is five months to the day.

What the pass added rather than corrected.

  • The container half of AppImageKit#828 now has a mechanism, which upstream never established — TheAssassin's own opening post says "We should analyze whether this is a bug we caused …". It is AppImageLauncher's binfmt_misc registration on AI\x02 at offset 8, whose path-based interpreter is resolved inside the container's mount namespace and is not there. Reproduced end to end (§12): the symptom, the magic-zeroing workaround, and the F-flag fix all behave exactly as the thread describes.
  • Open question 1 is answered by measurement, and open question 2 is closed by a controlled experiment: DwarFS's delta-hostility is a tuning artefact rather than a property of the format, but neither block size nor file ordering recovers it alone, and every configuration that does costs image size — so the checklist's SquashFS-xz baseline survives, for a different reason than the one originally given (§11.2).
  • T11's scope is now justified from source: open_verify() memcmps EI_PAD as well, so the tripwire is right to cover bytes 9–15 and not only byte 8.
  • The (null) in F-002's error message turns out to name its own root cause. libappimage pinned squashfuse at 1f98030, a commit from 2016-10-10 — five months before squashfuse learned the name "zstd" (5986f1d2, 2017-03-27) — and held that pin until 2024-11-25. The compression-name table is sized [SQFS_COMP_MAX] (16) but initialised only for known ids, so the zstd id read back an implicitly-zeroed slot. A build merely missing the zstd decompressor would have printed "uses zstd compression"; (null) means the pinned table predated zstd by seven years.

One check the vendoring made possible for the first time. With the checklist versioned alongside this document, its citations can be verified mechanically rather than trusted. Every checklist item this report quotes or corrects — P3.1, P10.3, T7, P2.5, P3.3, P2.4, P3.4, P1.1, P2.3, the musl rationale, all four budget rows and the §2.10 exit-codes cell — resolves to the text quoted here, and the report's claim that "numbering continues within each gate; nothing existing is renumbered or deleted" holds exactly: the checklist defines P0.1–P0.5, P1.1–P1.11, P2.1–P2.6, P3.1–P3.7, P4.1–P4.7, P5.1–P5.6, P6.1–P6.5, P7.1–P7.4, P8.1–P8.4, P9.1–P9.5, P10.1–P10.4, P11.1–P11.5, P12.1–P12.5, T1–T10 and E1–E15; this document defines P0.6–P0.9, P1.12–P1.16, P2.7–P2.14, P3.8–P3.15, P4.8–P4.12, P5.7–P5.9, P6.6, P7.5, P8.5–P8.6, P9.6–P9.10, P10.5, P11.6–P11.9, P12.6, T11–T18 and E16–E21, with no identifier defined twice.

Standing caveats. The e_ident measurements are glibc 2.39 on x86-64; other libcs, older glibcs and non-x86 LIBC_ABI_MAX values are untested. The delta measurement is one AppDir, one synthetic patch shape, one compression level and one architecture — enough to settle direction and mechanism, not enough to be a published benchmark. The freedesktop Bugzilla is offline, so bug 96685's own creation date could not be read directly and the 2016 interval is anchored on the in-thread record instead. Nothing was posted to any upstream repository during this pass either.

About

ai slop, don't bother

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors