A shepherd for your Appimages.
0

Configure Feed

Select the types of activity you want to include in your feed.

check sigs before extraction

Aly Raffauf (Jun 19, 2026, 3:26 AM EDT) 4dbb2536 72f309f5

+25 -13
+1 -1
internal/appherder/appimage.go
··· 38 38 return openSquashFS(file, offset) 39 39 } 40 40 41 - // Not at the expected offset — try scanning forward. 41 + // Not at the expected offset; try scanning forward. 42 42 if scanned, ok := scanForSquashFS(file, offset); ok { 43 43 return openSquashFS(file, scanned) 44 44 }
+22 -10
internal/appherder/install.go
··· 20 20 return "", fmt.Errorf("resolve AppImage path %q: %w", appimage, err) 21 21 } 22 22 23 + // Verify before openAppImage: the DwarFS fallback executes the AppImage to 24 + // extract it, so bad images must be refused first. Pinned-key check is 25 + // deferred until the app name is known from the desktop file inside. 26 + fingerprint, err := verifyAppImage(appimage, "", want) 27 + if err != nil { 28 + return "", err 29 + } 30 + 23 31 fsys, closeAppImage, err := openAppImage(appimage) 24 32 if err != nil { 25 33 return "", err ··· 41 49 icon := resolveIcon(fsys) 42 50 appName = deriveAppName(desktop, desktopName, appimage) 43 51 44 - // Verify integrity and signature before any filesystem writes, so a refused 45 - // AppImage installs nothing. 46 - pin, err := verifyAppImage(appimage, a.pinnedSigningKey(appName), want) 47 - if err != nil { 48 - return "", err 52 + // Pinned-key check deferred from above; app name now known. 53 + pinned := a.pinnedSigningKey(appName) 54 + if pinned != "" { 55 + if fingerprint == "" { 56 + return "", fmt.Errorf("refusing unsigned AppImage: a signing key is pinned (%s); uninstall and reinstall to trust a different build", pinned) 57 + } 58 + if !strings.EqualFold(pinned, fingerprint) { 59 + return "", fmt.Errorf("refusing AppImage: signing key changed (pinned %s, got %s); uninstall and reinstall to trust the new key", pinned, fingerprint) 60 + } 49 61 } 62 + pin := fingerprint 50 63 51 64 // No desktop file inside the AppImage: synthesize a terminal launcher so 52 65 // CLI apps still get a menu entry and are tracked by managedApps. ··· 114 127 return a.install(tmpName, expectedChecksum{}) 115 128 } 116 129 117 - // deriveAppName picks the canonical install name, matching GearLever so the 118 - // two tools land at the same path: the desktop entry's Name field (e.g. 119 - // "ES-DE" -> "esde"), then the desktop-file id, then the source filename. 130 + // deriveAppName picks the canonical install name: the desktop entry's Name 131 + // field (e.g. "ES-DE" -> "esde"), then the desktop-file id, then the source 132 + // filename. 120 133 func deriveAppName(desktop *desktopfile.File, desktopName string, appimagePath string) string { 121 134 if desktop != nil { 122 135 if name, ok := desktop.Get(desktopEntrySection, "Name"); ok && name != "" { ··· 130 143 } 131 144 132 145 // sanitizeAppName lowercases s, turns spaces into underscores, and drops any 133 - // character that isn't alphanumeric, underscore, or dot — GearLever's naming 134 - // rule. 146 + // character that isn't alphanumeric, underscore, or dot. 135 147 func sanitizeAppName(name string) string { 136 148 name = strings.ToLower(name) 137 149 name = strings.ReplaceAll(name, " ", "_")
+1 -1
internal/appherder/list_test.go
··· 135 135 t.Fatal(err) 136 136 } 137 137 138 - // Desktop file with no Name= field — list should fall back to the filename. 138 + // Desktop file with no Name= field; list should fall back to the filename. 139 139 if err := os.WriteFile(filepath.Join(appimages, "noname.appimage"), []byte("x"), 0o644); err != nil { 140 140 t.Fatal(err) 141 141 }
+1 -1
internal/appherder/sync.go
··· 151 151 } 152 152 153 153 // appImageBackedOrphans returns appids of unmanaged desktop entries whose 154 - // TryExec or Exec points at a missing file inside appimagesDir — launchers left 154 + // TryExec or Exec points at a missing file inside appimagesDir; launchers left 155 155 // by another tool after their AppImage was deleted. 156 156 func appImageBackedOrphans(applicationsDir, appimagesDir string) ([]string, error) { 157 157 matches, err := filepath.Glob(filepath.Join(applicationsDir, "*.desktop"))