Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ jobs:

runs-on: ${{ matrix.RUNS_ON }}

# Required for GitHub attestation
permissions:
id-token: write
contents: read
attestations: write

env:
ARCH: ${{ matrix.ARCH }}

Expand All @@ -36,6 +42,11 @@ jobs:
run: |
bash -ex ci/build-in-docker.sh

- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: '**/appimagetool*.AppImage'

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
Expand Down
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ execute_process(
# by default, static builds are off allow for working on this tool on any distribution
option(BUILD_STATIC OFF)

# Optional sanitizer support for testing and debugging
# Enable with -DENABLE_SANITIZERS=ON
# Note: Cannot be used with static builds
option(ENABLE_SANITIZERS "Enable AddressSanitizer and UndefinedBehaviorSanitizer" OFF)

if(ENABLE_SANITIZERS)
if(BUILD_STATIC)
message(FATAL_ERROR "Sanitizers cannot be used with static builds")
endif()

message(STATUS "Enabling AddressSanitizer and UndefinedBehaviorSanitizer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined")
Comment on lines +50 to +53
Copy link

@black-sliver black-sliver Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks correct, but to be effective, it needs to execute such a binary in CI - either the full build, or unit tests.

(Ideally, both happy and unhappy code paths)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It typically makes sense to run sanitizers separately, as logs can explode otherwise. Also, there are other sanitizers that are not considered yet. Plus, all of this should be matrix'd in CI.

endif()

if(BUILD_STATIC)
# since this project does not expose any libraries, we can safely set the linker flag globally
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -no-pie")
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,29 @@ If you are on an Intel machine and would like to cross-compile for ARM:
* For 64 bit ARM, run `ARCH=aarch64 bash ./ci/build-in-docker.sh`
* For 32 bit ARM, run `ARCH=armhf bash ./ci/build-in-docker.sh`

### Development Builds

For local development with sanitizers enabled:

```bash
mkdir build && cd build
cmake .. -DENABLE_SANITIZERS=ON
make
```

Note: Sanitizer builds cannot be combined with static builds and are for development/testing only.

## Security

This project includes several security and supply chain improvements:

- **Compiler Warnings**: Built with `-Wall -Wextra -Wconversion -Werror` to catch potential bugs
- **Hash Verification**: All downloaded dependencies are verified with SHA256 hashes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can't be true any more.

- **Build Attestation**: GitHub releases include cryptographically signed build provenance
- **Sanitizer Support**: Optional ASAN/UBSAN support for development testing

For more details, see [SECURITY.md](SECURITY.md).

## Changelog

* Unlike previous versions of this tool provided in the [AppImageKit](https://github.com/AppImage/AppImageKit/) repository, this version downloads the latest AppImage runtime (which will become part of the AppImage) from https://github.com/AppImage/type2-runtime/releases. If you do not like this (or if your build system does not have Internet access), you can supply a locally downloaded AppImage runtime using the `--runtime-file` parameter instead.
90 changes: 90 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Security and Supply Chain
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't be bothered to read this "AI" (LLM) generated wall of text which contains redundancies to other documents and would have to be kept in sync as well. I don't think it is useful to anybody, realistically. This has some advertising quality, but in the end, if you want to know for sure, you'd have a look at the build system and code yourself. This could be cut down drastically to contain an overview or so while referring to the primary sources.


This document describes the security measures and supply chain considerations for appimagetool.

## Compiler Security Flags

The project is built with comprehensive compiler warnings enabled to catch potential bugs and undefined behavior:

- `-Wall`: Enable all common warnings
- `-Wextra`: Enable extra warnings
- `-Wconversion`: Warn about implicit type conversions that may alter values
- `-Werror`: Treat warnings as errors to ensure they are addressed

These flags help ensure code quality and catch potential security issues at compile time.

## Download Verification

All external dependencies downloaded during the build process are verified using SHA256 hashes:

### Runtime Binaries

The AppImage runtime binaries are downloaded from https://github.com/AppImage/type2-runtime/releases and verified with SHA256 hashes for each architecture:

- `x86_64`: e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b
- `i686`: 3138b9f0c7a1872cfaf0e32db87229904524bb08922032887b298b22aed16ea8
- `aarch64`: c1b2278cf0f42f5c603ab9a0fe43314ac2cbedf80b79a63eb77d3a79b42600c5
- `armhf`: 6704e63466fa53394eb9326076f6b923177e9eb48840b85acf1c65a07e1fcf2b

The build process prints the hash and size of the downloaded runtime for transparency.

### Build Tools

External build tools are also verified:

- **mksquashfs 4.6.1**: SHA256 hash `9c4974e07c61547dae14af4ed1f358b7d04618ae194e54d6be72ee126f0d2f53`
- **zsyncmake 0.6.2**: SHA256 hash `0b9d53433387aa4f04634a6c63a5efa8203070f2298af72a705f9be3dda65af2` (already verified)
- **desktop-file-validate 0.28**: SHA256 hash `379ecbc1354d0c052188bdf5dbbc4a020088ad3f9cab54487a5852d1743a4f3b`

## Build Provenance Attestation

The GitHub Actions workflow generates cryptographically signed build provenance attestations using GitHub's attestation service. These attestations:

- Prove that the artifacts were built by the official GitHub Actions workflow
- Include the full build context (commit SHA, workflow, runner environment)
- Can be verified by downstream users using the GitHub CLI or API

To verify an AppImage artifact:

```bash
gh attestation verify appimagetool-x86_64.AppImage --owner AppImage
```

## Sanitizer Support

For development and testing, the build system supports AddressSanitizer (ASAN) and UndefinedBehaviorSanitizer (UBSAN):

```bash
cmake -DENABLE_SANITIZERS=ON /path/to/source
make
```

These sanitizers help detect:
- Memory errors (use-after-free, buffer overflows, memory leaks)
- Undefined behavior (integer overflow, null pointer dereferences, etc.)

Note: Sanitizers cannot be used with static builds and are intended for development/testing only.

## Updating Hashes

When updating dependencies, the hashes must be updated accordingly:

1. Download the new version of the dependency
2. Calculate its SHA256 hash: `sha256sum <file>`
3. Update the hash in the corresponding script in `ci/`
4. Document the change in the commit message

## Supply Chain Considerations

This project takes the following measures to ensure supply chain security:

1. **Pinned Dependencies**: All external dependencies are pinned to specific versions
2. **Hash Verification**: All downloads are verified against known-good SHA256 hashes
3. **Minimal Trust Surface**: Only downloads from official sources (GitHub releases, official package repositories)
4. **Transparency**: All hashes and versions are printed during the build process
5. **Reproducibility**: Static builds ensure consistent behavior across different systems
6. **Build Provenance**: GitHub attestations provide cryptographic proof of build authenticity

## Reporting Security Issues

If you discover a security vulnerability in appimagetool, please report it by opening an issue on GitHub. Please provide as much detail as possible to help us understand and address the issue.
39 changes: 39 additions & 0 deletions ci/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,45 @@ chmod +x AppDir/AppRun

wget https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-"$ARCH"

# Verify runtime hash for supply chain security
Copy link

@black-sliver black-sliver Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will break whenever someone pushes to continuous. You'd either need to do non-continuous releases, or download the .sig and check with GPG instead (or update the hash whenever pushing to the other repo, I guess).

# These hashes are from the continuous release and should be updated when the runtime is updated
case "$ARCH" in
x86_64)
expected_hash="e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b"
;;
i686)
expected_hash="3138b9f0c7a1872cfaf0e32db87229904524bb08922032887b298b22aed16ea8"
;;
aarch64)
expected_hash="c1b2278cf0f42f5c603ab9a0fe43314ac2cbedf80b79a63eb77d3a79b42600c5"
;;
armhf)
expected_hash="6704e63466fa53394eb9326076f6b923177e9eb48840b85acf1c65a07e1fcf2b"
;;
*)
echo "Warning: Unknown architecture $ARCH, skipping hash verification"
expected_hash=""
;;
esac

if [ -n "$expected_hash" ]; then
echo "Verifying runtime-$ARCH hash..."
echo "$expected_hash runtime-$ARCH" | sha256sum -c || {
echo "ERROR: Runtime hash verification failed for $ARCH"
echo "Expected: $expected_hash"
echo "Got: $(sha256sum runtime-$ARCH | awk '{print $1}')"
exit 1
}
echo "Runtime hash verified successfully"
else
echo "Warning: Runtime hash not verified for $ARCH"
fi

# Print runtime information for transparency
echo "Runtime file: runtime-$ARCH"
echo "Runtime SHA256: $(sha256sum runtime-$ARCH | awk '{print $1}')"
echo "Runtime size: $(stat -c%s runtime-$ARCH) bytes"

pushd AppDir
ln -s usr/share/applications/appimagetool.desktop .
ln -s usr/share/icons/hicolor/128x128/apps/appimagetool.png .
Expand Down
17 changes: 17 additions & 0 deletions ci/install-static-desktop-file-validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ pushd "$build_dir"
# apk add glib-static glib-dev autoconf automake # Moved to build-in-docker.sh

wget -c "https://gitlab.freedesktop.org/xdg/desktop-file-utils/-/archive/"$version"/desktop-file-utils-"$version".tar.gz"

# Verify tarball hash for supply chain security
# Hash for version 0.28
expected_hash="379ecbc1354d0c052188bdf5dbbc4a020088ad3f9cab54487a5852d1743a4f3b"
if [[ "$version" == "0.28" ]]; then
echo "Verifying desktop-file-utils tarball hash..."
echo "$expected_hash desktop-file-utils-$version.tar.gz" | sha256sum -c || {
echo "ERROR: desktop-file-utils tarball hash verification failed"
echo "Expected: $expected_hash"
echo "Got: $(sha256sum desktop-file-utils-$version.tar.gz | awk '{print $1}')"
exit 1
}
echo "Tarball hash verified successfully"
else
echo "Warning: No hash verification available for desktop-file-utils version $version"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think $hash should be $2, like it is done for other dependencies.

fi

tar xf desktop-file-utils-*.tar.gz
cd desktop-file-utils-*/

Expand Down
15 changes: 14 additions & 1 deletion ci/install-static-mksquashfs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,20 @@ trap cleanup EXIT

pushd "$build_dir"

wget https://github.com/plougher/squashfs-tools/archive/refs/tags/"$version".tar.gz -qO - | tar xvz --strip-components=1
wget https://github.com/plougher/squashfs-tools/archive/refs/tags/"$version".tar.gz

# Verify tarball hash for supply chain security
expected_hash="9c4974e07c61547dae14af4ed1f358b7d04618ae194e54d6be72ee126f0d2f53"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Hardcoding in this script makes no sense whatsoever. You want to configure these things in your workflow in a central place.

echo "Verifying mksquashfs tarball hash..."
echo "$expected_hash $version.tar.gz" | sha256sum -c || {
echo "ERROR: mksquashfs tarball hash verification failed"
echo "Expected: $expected_hash"
echo "Got: $(sha256sum $version.tar.gz | awk '{print $1}')"
exit 1
}
echo "Tarball hash verified successfully"

tar xvz -f "$version".tar.gz --strip-components=1

cd squashfs-tools

Expand Down
9 changes: 9 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ add_executable(appimagetool
md5.c
)

# Enable comprehensive warnings for better code quality and security
target_compile_options(appimagetool PRIVATE
-Wall
-Wextra
-Werror
$<$<COMPILE_LANGUAGE:C>:-Wconversion>
$<$<COMPILE_LANGUAGE:CXX>:-Wconversion>
Comment on lines +16 to +17
Copy link

@black-sliver black-sliver Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting choice.

The other kind of static analysis I mentioned in the issue is scan-build:
You install latest clang and instead of compiling with CC=gcc you wrap your call with scan-build, which overrides CC=, which analyses the code as it's being build, including code flow analysis. This is similar to -fanalyzer from gcc, but I had better results with clang (both errors found and quality of output).

Code flow analysis find things like "if fopen fails here, the program will crash there", or "if ftell fails here, the index access will be invalid there".

I don't have an example for cmake ready, but this shows how you install a specific / recent version of scan-build in CI: https://github.com/ArchipelagoMW/Archipelago/blob/main/.github/workflows/scan-build.yml

Could also just use -fanalyzer if managing that extra dependency seems like too much of a hassle.

)

# trick: list libraries on which imported static ones depend on in the PUBLIC section
# CMake then adds them after the PRIVATE ones in the linker command
target_link_libraries(appimagetool
Expand Down
Loading