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
13
pkgname=granola
14
pkgver=7.478.0
15
pkgrel=1
16
pkgdesc="AI notepad for back-to-back meetings, repackaged from the official macOS build"
17
arch=('x86_64')
18
url="https://www.granola.ai"
19
license=('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
25
depends=(
26
"$_electron"
27
'libstdc++'
28
'glibc'
29
'sh'
30
'desktop-file-utils'
31
'xdg-utils'
32
)
33
conflicts=('granola-wine')
34
makedepends=(
35
'7zip'
36
'nodejs'
37
'node-gyp'
38
'python'
39
)
40
source=(
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
)
46
noextract=(
47
"granola-$pkgver.dmg"
48
"node-v$_elver-headers.tar.gz"
49
)
50
sha256sums=('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
58
prepare() {
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'
100
import sys, pathlib
101
p = pathlib.Path(sys.argv[1]); data = p.read_bytes(); total = 0
102
for 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)
106
if total == 0:
107
sys.exit("no platform fallback found; Granola's bundler output changed")
108
p.write_bytes(data)
109
print(f"patched {total} platform fallback(s)")
110
PYEOF
111
}
112
113
build() {
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
123
check() {
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
144
package() {
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
160
exec /usr/lib/$_electron/electron --ozone-platform-hint=auto /usr/lib/granola/app.asar "\$@"
161
EOF
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
166
Granola is proprietary software. Copyright (c) Granola.
167
This package repackages the official binary distribution; no license text
168
is shipped inside it. Terms: https://www.granola.ai/terms
169
EOF
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) | Severity | Rules |
|---|---|---|
| 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 |