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.
- Synthesis — what the archive says that the AnyLinux docs do not
- Corrections — where the existing checklist is wrong
- Method and corpus
- Feature-matrix deltas
- Non-goals to add (§3)
- Tripwires to add (§4)
- Conformance environments to add (§5)
- Budget corrections (§6)
- New and sharpened checklist items, Gate 0 → Gate 12
- Findings table
- Open questions and unresolved threads
- Appendix — reproducing this research
- Verification pass
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
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.
These are the places where the historical record contradicts the checklist as written. Each states which side the evidence favours.
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.
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
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.
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
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.
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.
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.
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".
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.
Included because the discipline cuts both ways. §2.10 lists exit codes as "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).
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.
Cells to change in §2, with the evidence. Legend unchanged (✅ /
| § | Row | Cell | Current | Corrected | Evidence |
|---|---|---|---|---|---|
| 2.3 | Signed updates (via §2.9) | Vanilla | ❌ | 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 | .sig_key, transitive trust; no rotation, no revocation |
AppImageSpec#29; AppImageUpdate#260 |
| 2.8 | Thumbnails in file managers | Vanilla | ✅ | AppImageKit#203 | |
| 2.8 | Third-party tooling compatibility | Vanilla | ✅ baseline | 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 |
| § | 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 | 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 | 🎯 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 |
🎯 per-uid, 0700, $XDG_RUNTIME_DIR-preferred |
AppImageKit#1215; AppImageKit#452 | |
| 2.4 | Confinement without a SUID helper | ❌ | 🎯 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 |
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
bwrapcannot consume a FUSE mount directly — you must mount first, then sandbox, which is whataisapdoes. 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_miscregistration 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 thebinfmt-bypasstechnique 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.xmlvsappdata.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
validatetool. So the tool is pretty pointless." Any--verifysurface must state, in its output, which key it trusted and why. - ❌ An unversioned media type. TheAssassin warned against
application/vnd.appimagein 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.
Wire into CI as hard failures, continuing §4's numbering.
- T11 — Magic or format bytes written anywhere inside
e_ident—EI_OSABI(byte 7),EI_ABIVERSION(byte 8) orEI_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_keyis a fixed 8192-byte pre-allocated section and real keys overflow it).
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.
| 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 |
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.
- 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)
SIGKILLof the payload, (c) asystemdoneshotunit, and (d) 200 sequential launches,/proc/self/mountinfocontains 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
EACCESand 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, SUIDfusermount) trigger alerts on at least one commercial EDR, and a supported, tested non-memfdfallback for each. (F-024)
- P1.12 Format magic MUST NOT occupy any byte of
e_ident— in practiceEI_OSABI(byte 7),EI_ABIVERSION(byte 8) andEI_PAD(bytes 9–15) — and MUST sit at a fixed offset ≥0x40and ≤0x800soshared-mime-infocan sniff it. Pass:readelf -hshowse_identbytes 7–15 all zero;ld.so ./imagedoes not fail with "ELF file ABI version invalid"; the image runs unpatched (a) underqemu-userfor a foreign architecture withqemu-binfmt-conf.shregistrations installed and (b) insideubuntu:14.04andopensuse/leapcontainers on a host that has AppImageLauncher'sbinfmt_miscentry 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 whoseld.sovalidatesEI_ABIVERSION" is not a test at all, because the kernel never re-verifiese_identfor a program itexecves (§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
$APPDIRby the payload must keep working after the launcher exits. Pass: exit status equals the payload's on all three fallback rungs;SIGTERM/SIGINTreach the payload; a double-forked child started from$APPDIRstill resolves its libraries and runs to completion; no orphaned FUSE daemon (T16). (F-020, F-021, F-025) - P1.15
--whymust report not only which rung was used but which observable semantics changed on it. Pass: on the user-namespace rung,--why --jsonreports 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)
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
libappimageshipped 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
libappimageitself. Pass: a PR is open againstlibappimageimplementing 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 --jsonenumerates 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-infoXML defining the successor type at its new magic offset is submitted upstream with a test file, per theHACKINGrequirements that AppImageKit#144 documents; existingapplication/vnd.appimagehandlers 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
.DirIconis 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:
--doctorrefuses or warns before exec rather than the app dying withSIGILLorsymbol 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.xmlis canonical,appdata.xmlis 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)
- 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_CHANGEDwith 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
.zsynccontrol file naming.bashrccannot cause a write outside the target image path; updates land via a sibling temp file plusrename(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:
--sourcesemits it; a fetcher retrieves every listed source archive; the mapping from bundled file → source package is complete. (F-031)
- 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
--doctorreports (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)popenof 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-uid0700directory), 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
fusermountpresent at all, the image still runs via user namespaces or extraction, and--whyreports which; E5 and E15 green. (F-026, F-036)
- P5.7 usrmerge correctness: the rule set resolves
/lib↔/usr/libaliasing so the produced tree is self-consistent regardless of the build host's layout. Pass: images built on a merged-/usrhost and a split-/usrhost have byte-identical internal layouts for the same inputs. (F-037) - P5.8 gettext/
TEXTDOMAINDIRis 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_FILEandSSL_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)
- 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)
- 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-S26default'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)
- P8.5 Architecture is determined from the ELF headers of the payload binaries, never from
fileoutput heuristics or filenames. Pass: an AppDir containing a PNG thatfilemisreads as an i386 binary still produces a correctly-tagged image; changing thefileversion 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)
- P9.6
argv[0]dispatch: a symlink named after a declared payload binary runs that binary. Pass:ln -s app.image editcap && ./editcap -hruns the bundlededitcap; the set of dispatchable names is declared in the manifest;--installcreates exactly those symlinks and--uninstallremoves 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.desktopfile 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.listare tagged so they can be removed; a pre-existing hand-writtenmimeapps.listis preserved byte-for-byte across install → uninstall;update-desktop-databaseis 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
--uninstallenumerates 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_HOMEand$XDG_CACHE_HOMEsubdirectories as declared; nothing outside the declaration is offered for deletion; nothing is deleted without consent. (F-051, F-052)
- 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)
- 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) ashared-mime-infoentry with a test file; (c) the normative signature specification; (d)SOURCE_DATE_EPOCH/mkfs-timesupport inappimagetool; (e) a reproducible runtime build. Pass: issue/PR links present and their state recorded. (F-003, F-008, F-016, F-029, F-054)
- 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
memfdexecution, and page-cache duplication across concurrent instances. Pass: each item names the test that demonstrates it. (F-020, F-025, F-024, F-026)
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.
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
Recorded as unresolved rather than guessed, per the evidence standard.
-
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 exacte_ident(EI_OSABI=SYSV,41 49 02at bytes 8–10) runs normally under kernelexecveon glibc 2.39/x86-64, as does the dynamic equivalent, because the kernel does not re-verifye_ident; glibc only rejects it for objectsld.soopens itself. Static linking is therefore not an escape from the two mechanisms that do bite, because both are libc-independent:binfmt_miscmask matching (the qemu path) and any host-registeredbinfmt_miscentry 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 ofLIBC_ABI_MAX, and third-party ELF parsers that inspecte_identfor their own reasons. E18 remains the gate, and P1.12's cost in MIME identification is unchanged by this result. -
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 % bymkdwarfs --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, withzsyncreporting how much of the new image it can rebuild from the old (zsyncmake+ "Target N% complete"; commands in §12).zstd:level=22throughout; 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=path81.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=path90.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=path100.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=pathat 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=pathcosts +8.5 % image for 2.5× the update bytes,-S16costs +19 % image for roughly parity on update bytes. Against the-S26default, 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
-S26ordering — 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. -
The status of
TheAssassin/type3-runtimeand theAppImageXorganisation. 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. -
Whether
libappimage's tracker holds a more complete integration-algorithm specification. Several threads defer tolibappimageissues — 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. -
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. -
Whether a successor can obtain a new
shared-mime-infoentry 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.
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/magicPitfalls 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 runsReproducing 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
doneA 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.
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.
- The
e_identmechanism (§1, F-001, §11.1) was wrong in a way that mattered. The old text said glibc'sld.sorefuses the byte "whenEI_OSABI == ELFOSABI_GNU" and that "qemu-uservalidates it too". Real runtimes areELFOSABI_SYSV, where the rule is simply must be zero;ld.soonly applies it to objects it opens itself, and the kernelexecvepath 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 ownbinfmt_miscregistration, not by either. All four were re-derived from source and reproduced locally. - 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.
- AppImageKit#130 closed unmerged 2016-11-16 (F-057); the old text gave its opening date.
libappimage#132was mis-cited (§11.4): it is an svgz-icon bug, not a licences thread, and no licences thread exists in that tracker.- F-062's "most-reacted portability requests" is now scoped to host-libc requests, since #877 (81 reactions) outranks both and is also portability.
- The
shared-mime-infodurations (§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. - The DwarFS/zsync claim in §8 is now measured rather than asserted; see below.
- A pitfall was added to §12:
/issuesomits 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_miscregistration onAI\x02at 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 theF-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()memcmpsEI_PADas 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.libappimagepinned squashfuse at1f98030, 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.