granola

maintainer forvkusa · 0 votes · scanned 2026-08-18 01:38:17.845211
MEDIUM
View on AUR ↗
Why flagged The package downloads a prebuilt macOS .dmg from a non-whitelisted CDN (cloudfront.net) which is used to extract and repackage binaries; while it's the official release host, the unverifiable nature of the binary payload from a swappable source constitutes a supply-chain risk.

Triggered rules

MEDIUM source=() URL on a non-standard host source_untrusted_domain

One or more source=() URLs point to a host outside the trusted allowlist (github.com, gitlab.com, codeberg.org, pypi.org, …).

  • PKGBUILD:41 "granola-$pkgver.dmg::https://dr2v7l5emb758.cloudfront.net/$pkgver/Granola-$pkgver-mac-universal.dmg"
  • PKGBUILD:43 "https://electronjs.org/headers/v$_elver/node-v$_elver-headers.tar.gz"
LOW Few votes, recently uploaded zero_votes_recent

Uploaded within the last 14 days with 2 or fewer community votes — little peer review so far.

MEDIUM AI review llm_review

An AI model (qwen/qwen3-235b-a22b-2507) reviewed this and agrees it is MEDIUM (confidence 85%): The package downloads a prebuilt macOS .dmg from a non-whitelisted CDN (cloudfront.net) which is used to extract and repackage binaries; while it's the official release host, the unverifiable nature of the binary payload from a swappable source constitutes a supply-chain risk.

PKGBUILD

2 offending line(s) highlighted
1# Maintainer: forvkusa <forvkusa+aur at csie dot ntu dot edu dot tw>
2# The build file is written by Claude Fable 5. Use with caution.
3#
4# Repackages the official Granola macOS .dmg as a native Linux pacman package.
5# Based on https://github.com/tirtha4/Granola-for-Linux.
6#
7# makepkg downloads the .dmg from Granola's release CDN (the same host the
8# in-app updater uses); no manual download step is needed.
9#
10# Format check: shfmt -ln bash -i 2 -d PKGBUILD
11# Linting: shellcheck --shell=bash --exclude=SC2034,SC2148,SC2154 PKGBUILD
12
13pkgname=granola
14pkgver=7.478.0
15pkgrel=1
16pkgdesc="AI notepad for back-to-back meetings, repackaged from the official macOS build"
17arch=('x86_64')
18url="https://www.granola.ai"
19license=('LicenseRef-proprietary')
20_electron=electron42
21# version of the installed $_electron package
22_elver=42.9.0
23# version of Granola's bundled better-sqlite3-multiple-ciphers fork
24_bs3ver=12.9.0
25depends=(
26 "$_electron"
27 'libstdc++'
28 'glibc'
29 'sh'
30 'desktop-file-utils'
31 'xdg-utils'
32)
33conflicts=('granola-wine')
34makedepends=(
35 '7zip'
36 'nodejs'
37 'node-gyp'
38 'python'
39)
40source=(
41 "granola-$pkgver.dmg::https://dr2v7l5emb758.cloudfront.net/$pkgver/Granola-$pkgver-mac-universal.dmg"
42 "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-$_bs3ver.tgz"
43 "https://electronjs.org/headers/v$_elver/node-v$_elver-headers.tar.gz"
44 "granola.desktop"
45)
46noextract=(
47 "granola-$pkgver.dmg"
48 "node-v$_elver-headers.tar.gz"
49)
50sha256sums=('a310b2541aecc38352f98badd4c0ddaaf9344e437e1b09a4f9989e81ab467dc0'
51 'ad8ceb2cfe687e0c106547fdd281f0d20b40688200d04f40bb163afd1f102609'
52 '7bef173f1350b2c9622b56fcd5ea24a578d2294ab164fd0c55aefa842a0243a6'
53 '22b5217b929a1d184f1f73d5f55fbef6756d5fbcef207da64f188231d38c9c20')
54
55_res="Granola/Granola.app/Contents/Resources"
56_bs3rel="app.asar.unpacked/node_modules/better-sqlite3-multiple-ciphers"
57
58prepare() {
59 local dmg="$srcdir/granola-$pkgver.dmg"
60
61 # The native module is built against the installed electron's headers.
62 local sysver
63 sysver="$(</usr/lib/$_electron/version)"
64 if [[ "$sysver" != "$_elver" ]]; then
65 echo "installed $_electron is $sysver, set _elver=$sysver and run updpkgsums" >&2
66 return 1
67 fi
68
69 # The .dmg must bundle the same Electron major as $_electron.
70 7z e "$dmg" \
71 "Granola/Granola.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist" \
72 -ofw -y >/dev/null || { echo "cannot read the .dmg" >&2; return 1; }
73 local dmgel
74 dmgel="$(grep -A1 CFBundleVersion fw/Info.plist | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
75 if [[ "${dmgel%%.*}" != "${_electron#electron}" ]]; then
76 echo ".dmg bundles Electron $dmgel, set _electron=electron${dmgel%%.*} and depends to match" >&2
77 return 1
78 fi
79
80 7z x "$dmg" "$_res/app.asar" "$_res/app.asar.unpacked" "$_res/icons" \
81 -opayload -y >/dev/null
82
83 # The npm tarball (extracted by makepkg into package/) must match the
84 # fork version inside the .dmg, or its binding.gyp may not fit the source.
85 local bs3="payload/$_res/$_bs3rel"
86 local realver
87 realver="$(node -p "require('$srcdir/$bs3/package.json').version")"
88 if [[ "$realver" != "$_bs3ver" ]]; then
89 echo "bundled better-sqlite3-multiple-ciphers is $realver, set _bs3ver=$realver and run updpkgsums" >&2
90 return 1
91 fi
92 cp package/binding.gyp "$bs3/"
93
94 # api.granola.ai answers 500 to any request carrying platform=linux,
95 # including the sign-in URL. The app maps darwin->macOS and win32->Windows
96 # and passes anything else through verbatim; rewrite that fallback so Linux
97 # reports Windows. The replacement is padded to the same byte length
98 # because the asar header records file offsets.
99 python - "payload/$_res/app.asar" <<'PYEOF'
100import sys, pathlib
101p = pathlib.Path(sys.argv[1]); data = p.read_bytes(); total = 0
102for pat in (b'?`Windows`:window.electron.platform', b'?`Windows`:process.platform'):
103 rep = b'?`Windows`:`Windows`'.ljust(len(pat))
104 total += data.count(pat)
105 data = data.replace(pat, rep)
106if total == 0:
107 sys.exit("no platform fallback found; Granola's bundler output changed")
108p.write_bytes(data)
109print(f"patched {total} platform fallback(s)")
110PYEOF
111}
112
113build() {
114 # Granola's fork adds an updateHook() method that upstream npm prebuilts
115 # lack, so the module is built from the C++ source shipped in the .dmg.
116 cd "payload/$_res/$_bs3rel" || exit 1
117 node-gyp rebuild --release --runtime=electron --target="$_elver" --arch=x64 \
118 --dist-url=https://electronjs.org/headers \
119 --tarball="$srcdir/node-v$_elver-headers.tar.gz" \
120 --devdir="$srcdir/gyp-devdir"
121}
122
123check() {
124 local bs3="$srcdir/payload/$_res/$_bs3rel"
125 local db="$srcdir/smoke.db"
126 rm -f "$db"
127 ELECTRON_RUN_AS_NODE=1 NODE_PATH="$srcdir/payload/$_res/app.asar/node_modules" \
128 "/usr/lib/$_electron/electron" -e "
129 const Database = require('$bs3/lib/index.js');
130 const db = new Database('$db');
131 db.pragma(\"cipher='sqlcipher'\");
132 db.pragma(\"key='smoketest'\");
133 db.exec('CREATE TABLE t(a)');
134 let fired = false;
135 db.updateHook(() => { fired = true; });
136 db.prepare('INSERT INTO t VALUES (1)').run();
137 if (db.prepare('SELECT count(*) c FROM t').get().c !== 1) throw new Error('insert failed');
138 if (!fired) throw new Error('updateHook did not fire');
139 db.close();
140 "
141 rm -f "$db"
142}
143
144package() {
145 install -Dm644 "payload/$_res/app.asar" "$pkgdir/usr/lib/granola/app.asar"
146 cp -r "payload/$_res/app.asar.unpacked" "$pkgdir/usr/lib/granola/app.asar.unpacked"
147
148 # Keep only the built binaries from the module tree; drop the C++ source,
149 # sqlite amalgamation, and node-gyp intermediates.
150 local bs3="$pkgdir/usr/lib/granola/$_bs3rel"
151 install -Dm755 "$bs3"/build/Release/*.node -t "$srcdir/keep"
152 rm -rf "$bs3/build" "$bs3/deps" "$bs3/src" "$bs3/binding.gyp"
153 install -Dm755 "$srcdir/keep"/*.node -t "$bs3/build/Release"
154
155 install -Dm644 "payload/$_res/icons/icon.png" "$pkgdir/usr/share/pixmaps/granola.png"
156 install -Dm644 granola.desktop "$pkgdir/usr/share/applications/granola.desktop"
157
158 install -Dm755 /dev/stdin "$pkgdir/usr/bin/granola" <<EOF
159#!/bin/sh
160exec /usr/lib/$_electron/electron --ozone-platform-hint=auto /usr/lib/granola/app.asar "\$@"
161EOF
162
163 # Upstream ships no EULA file in the .dmg; record the proprietary status
164 # and where the terms live, as the license field needs a matching entry.
165 install -Dm644 /dev/stdin "$pkgdir/usr/share/licenses/$pkgname/LICENSE" <<EOF
166Granola is proprietary software. Copyright (c) Granola.
167This package repackages the official binary distribution; no license text
168is shipped inside it. Terms: https://www.granola.ai/terms
169EOF
170}
171

Changes since previous scan

--- PKGBUILD @ 2026-08-18 00:03
+++ PKGBUILD @ 2026-08-18 01:38
@@ -11,8 +11,8 @@
# Linting: shellcheck --shell=bash --exclude=SC2034,SC2148,SC2154 PKGBUILD
pkgname=granola
-pkgver=7.469.2
-pkgrel=2
+pkgver=7.478.0
+pkgrel=1
pkgdesc="AI notepad for back-to-back meetings, repackaged from the official macOS build"
arch=('x86_64')
url="https://www.granola.ai"
@@ -47,7 +47,7 @@
"granola-$pkgver.dmg"
"node-v$_elver-headers.tar.gz"
)
-sha256sums=('2fcb9223b3450830d0ec6c00223365e5c7f839aa2d3951d7b2aa9eae1289b166'
+sha256sums=('a310b2541aecc38352f98badd4c0ddaaf9344e437e1b09a4f9989e81ab467dc0'
'ad8ceb2cfe687e0c106547fdd281f0d20b40688200d04f40bb163afd1f102609'
'7bef173f1350b2c9622b56fcd5ea24a578d2294ab164fd0c55aefa842a0243a6'
'22b5217b929a1d184f1f73d5f55fbef6756d5fbcef207da64f188231d38c9c20')

Scan history

Scanned at (UTC)SeverityRules
2026-08-18 01:38:17 MEDIUM 3
2026-08-18 00:03:42 MEDIUM 3
2026-08-17 00:18:29 MEDIUM 3
2026-08-16 00:03:42 MEDIUM 3
2026-08-15 00:26:13 MEDIUM 3
2026-08-14 00:03:41 MEDIUM 3
2026-08-13 01:25:20 MEDIUM 3
2026-08-13 00:17:07 MEDIUM 3
2026-08-12 13:27:17 MEDIUM 3
2026-08-12 13:23:33 MEDIUM 3

Report a package

Reports go to the AURWatch maintainer (one person) and are read by hand. No login required.

0 / 4000
Your suggestion