cornelsen-offline-lernen-bin

LOW
maintainer micleh 0 votes scanned 2026-09-17 00:27:14.276658
View on AUR
Why flagged

The package downloads a prebuilt Electron app from the official vendor domain and patches its JavaScript files locally to improve offline functionality; the use of npx is for asar manipulation tools on extracted app code, not remote code execution, and the non-standard host is part of the vendor's own infrastructure.

Triggered rules

Low AI review downgraded a static finding llm_review

The static rules flagged this MEDIUM, but an AI model (qwen/qwen3-235b-a22b-2507) reviewed the full PKGBUILD and judged it LOW (confidence 95%): The package downloads a prebuilt Electron app from the official vendor domain and patches its JavaScript files locally to improve offline functionality; the use of npx is for asar manipulation tools on extracted app code, not remote code execution, and the non-standard host is part of the vendor's own infrastructure.

2 higher static findings superseded - not the current verdict (shown for transparency)
Medium npx/bunx/deno executes a remote package remote_code_tool

`npx`/`bunx`/`pnpm dlx`/`deno run <url>` downloads AND runs a remote package at build time — the moral equivalent of piping a download into a shell. Severity downgraded: Node.js consumer context.

  • PKGBUILD:66 npx --yes @electron/asar extract "$_asar_path" "$_tmp_dir"
  • PKGBUILD:204 npx --yes @electron/asar pack "$_tmp_dir" "$_asar_path"
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:12 source=("${pkgname}-${pkgver}.zip::https://ebook.cornelsen.de/uma20/public/v2/uma/offline/win"

PKGBUILD

3 offending line(s) highlighted
1# Maintainer: MicLeh <micleh at proton dot me>
2pkgname=cornelsen-offline-lernen-bin
3pkgver=37.10.2
4# App version 2026.8.1 from 2026-05-07
5pkgrel=10
6pkgdesc="Cornelsen Offline Lernen Electron App"
7arch=('x86_64')
8url="https://www.cornelsen.de"
9license=('custom')
10depends=('electron37')
11makedepends=('npm')
12source=("${pkgname}-${pkgver}.zip::https://ebook.cornelsen.de/uma20/public/v2/uma/offline/win"
13 "${pkgname}.sh"
14 "${pkgname}.desktop"
15 "icon16.png"
16 "icon32.png"
17 "icon96.png")
18b2sums=('80a1762e20039c727ee433c559cc0a64f21cf2a33b7e2039e6cc3944b0bcd61724a5f89ae7d7935d41ad40a2d31018f0ea9c039442767e638b8bc669ecf49193'
19 '9be125c58666538ccd056253a32daa1d55ce38ae72a97e1f5deebaef6c729e8b0a548f8478fa0b1f5b11d7759bc98b4cdd21db07d3b93dfea7cf4e2d760fc8e6'
20 '8157b061d35da630252a8c402a98fb978fcad59fcfa813eb8b67dfeaae7e467051d659201e55b5d1811ae15eca49e47189a5a9840277f09876cf1d7021cfc1da'
21 '8dd68d9cd90347b954da9280d2133bc45706eec1e74261bdf19f53b61150ee5168db20a0f217643230c4dc66fb840f9ef61fce5f6aff5853b1bebae4bc9f4c65'
22 'b326c7f468c3cecb0e2618bae56092b7c6c5fa7e8f43f499645df01c2b468e74052bef51d48b36a8a7ade2fa12f9612591eaf38819998868271ec23abd366ad5'
23 '302bef8514b991cb6903d2def32abf348329a1cf104009b3c50c0e83e44b15103b89f669fb2012894df0c7d6ab926151f09e0cf61aff4102296be7071f66f794')
24
25_find_asar_path() {
26 local _path
27 local _candidates=(
28 "src/windows/resources/app.asar"
29 "src/src/windows/resources/app.asar"
30 "windows/resources/app.asar"
31 "resources/app.asar"
32 )
33
34 for _path in "${_candidates[@]}"; do
35 if [ -f "$_path" ]; then
36 printf '%s\n' "$_path"
37 return 0
38 fi
39 done
40
41 _path="$(find . -type f -path '*/resources/app.asar' | head -n 1)"
42 if [ -n "$_path" ]; then
43 printf '%s\n' "${_path#./}"
44 return 0
45 fi
46
47 return 1
48}
49
50prepare() {
51 local _asar_path
52 _asar_path="$(_find_asar_path || true)"
53
54 if [ -z "$_asar_path" ] || [ ! -f "$_asar_path" ]; then
55 echo "ERROR: source app.asar not found in extracted zip"
56 exit 1
57 fi
58
59 local _tmp_base _tmp_dir
60 _tmp_base="${TMPDIR:-/var/tmp}"
61 if [ ! -w "$_tmp_base" ]; then
62 _tmp_base="$PWD"
63 fi
64 _tmp_dir="$(mktemp -d "$_tmp_base/${pkgname}.asar.XXXXXX")"
65
66 npx --yes @electron/asar extract "$_asar_path" "$_tmp_dir"
67
68 node - "$_tmp_dir" <<'NODE'
69const fs = require('fs');
70const path = require('path');
71const root = process.argv[2];
72
73function collectJsFiles(dir, out = []) {
74 for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
75 const p = path.join(dir, ent.name);
76 if (ent.isDirectory()) {
77 collectJsFiles(p, out);
78 } else if (ent.isFile() && ent.name.endsWith('.js')) {
79 out.push(p);
80 }
81 }
82 return out;
83}
84
85const distDir = path.join(root, 'dist');
86if (!fs.existsSync(distDir)) {
87 console.error('ERROR: unpacked asar does not contain a dist directory');
88 process.exit(1);
89}
90
91const jsFiles = collectJsFiles(distDir);
92
93const verbose = process.env.CORNELSEN_PATCH_VERBOSE === '1';
94const strict = process.env.CORNELSEN_PATCH_STRICT === '1';
95
96const patches = [
97 {
98 name: 'annotations-404-fallback',
99 regex: /getProductAnnotations\(r,s=!1\)\{return this\.http\.get\(`\$\{s\?os\.getSyncApiBaseUrl\(\):os\.getApiBaseUrl\(\)\}\/pspdf\/annotations\/\$\{r\}`\)\.pipe\(/g,
100 replaceWith: 'getProductAnnotations(r,s=!1){return this.http.get(`${s?os.getSyncApiBaseUrl():os.getApiBaseUrl()}/pspdf/annotations/${r}`).pipe(ip(y=>s&&y.status===404?hs({}):r0(()=>y)),',
101 from: 'getProductAnnotations(r,s=!1){return this.http.get(`${s?os.getSyncApiBaseUrl():os.getApiBaseUrl()}/pspdf/annotations/${r}`).pipe(ip(y=>r0(()=>y)),Jl(y=>this.mapOfflinePdfIds(y)))}',
102 fromAlt: 'getProductAnnotations(r,s=!1){return this.http.get(`${s?ls.getSyncApiBaseUrl():ls.getApiBaseUrl()}/pspdf/annotations/${r}`).pipe(nc(y=>this.mapOfflinePdfIds(y)))}',
103 toAlt: 'getProductAnnotations(r,s=!1){return this.http.get(`${s?ls.getSyncApiBaseUrl():ls.getApiBaseUrl()}/pspdf/annotations/${r}`).pipe(op(y=>s&&y.status===404?fs({}):r0(()=>y)),nc(y=>this.mapOfflinePdfIds(y)))}',
104 fromAlt2: 'getProductAnnotations(r,s=!1){return this.http.get(`${s?cs.getSyncApiBaseUrl():cs.getApiBaseUrl()}/pspdf/annotations/${r}`).pipe(nc(y=>this.mapOfflinePdfIds(y)))}',
105 toAlt2: 'getProductAnnotations(r,s=!1){return this.http.get(`${s?cs.getSyncApiBaseUrl():cs.getApiBaseUrl()}/pspdf/annotations/${r}`).pipe(op(y=>s&&y.status===404?Es({}):r0(()=>y)),nc(y=>this.mapOfflinePdfIds(y)))}',
106 fromAlt3: 'getProductAnnotations(i,s=!1){return this.http.get(`${s?cs.getSyncApiBaseUrl():cs.getApiBaseUrl()}/pspdf/annotations/${i}`).pipe(nc(y=>this.mapOfflinePdfIds(y)))}',
107 toAlt3: 'getProductAnnotations(i,s=!1){return this.http.get(`${s?cs.getSyncApiBaseUrl():cs.getApiBaseUrl()}/pspdf/annotations/${i}`).pipe(dp(y=>s&&y.status===404?ws({}):Q1(()=>y)),nc(y=>this.mapOfflinePdfIds(y)))}',
108 fromAlt4: 'getProductAnnotations(t,r=!1){return this.http.get(`${r?ps.getSyncApiBaseUrl():ps.getApiBaseUrl()}/pspdf/annotations/${t}`).pipe(zl(d=>this.mapOfflinePdfIds(d)))}',
109 toAlt4: 'getProductAnnotations(t,r=!1){return this.http.get(`${r?ps.getSyncApiBaseUrl():ps.getApiBaseUrl()}/pspdf/annotations/${t}`).pipe(dp(y=>r&&y.status===404?ws({}):Q1(()=>y)),zl(d=>this.mapOfflinePdfIds(d)))}',
110 to: 'getProductAnnotations(r,s=!1){return this.http.get(`${s?os.getSyncApiBaseUrl():os.getApiBaseUrl()}/pspdf/annotations/${r}`).pipe(dp(y=>s&&y.status===404?ws({}):Q1(()=>y)),Jl(y=>this.mapOfflinePdfIds(y)))}',
111 alreadyMarker: 's&&y.status===404?ws({}):Q1(()=>y)',
112 alreadyMarker2: 's&&y.status===404?ws({}):Q1(()=>y)',
113 alreadyMarker3: 's&&y.status===404?ws({}):Q1(()=>y)',
114 alreadyMarker4: 'r&&y.status===404?ws({}):Q1(()=>y)',
115 required: false
116 },
117 {
118 name: 'compatibility-401-fallback',
119 from: 'isCompatibleWithOnline$(){return this.http.get(`${os.getSyncApiBaseUrl()}/compatibility/offlineClients/${GE}`).pipe(sa(r=>r.isCompatible))}',
120 fromAlt: 'isCompatibleWithOnline$(){return this.http.get(`${ls.getSyncApiBaseUrl()}/compatibility/offlineClients/${qE}`).pipe(sa(r=>r.isCompatible))}',
121 toAlt: 'isCompatibleWithOnline$(){return this.http.get(`${ls.getSyncApiBaseUrl()}/compatibility/offlineClients/${qE}`).pipe(sa(r=>r.isCompatible),op(r=>r.status===401?fs(!0):r0(()=>r)))}',
122 fromAlt2: 'isCompatibleWithOnline$(){return this.http.get(`${cs.getSyncApiBaseUrl()}/compatibility/offlineClients/${ZE}`).pipe(sa(r=>r.isCompatible))}',
123 toAlt2: 'isCompatibleWithOnline$(){return this.http.get(`${cs.getSyncApiBaseUrl()}/compatibility/offlineClients/${ZE}`).pipe(sa(r=>r.isCompatible),op(r=>r.status===401?Es(!0):r0(()=>r)))}',
124 fromAlt3: 'isCompatibleWithOnline$(){return this.http.get(`${cs.getSyncApiBaseUrl()}/compatibility/offlineClients/${QE}`).pipe(ta(i=>i.isCompatible))}',
125 toAlt3: 'isCompatibleWithOnline$(){return this.http.get(`${cs.getSyncApiBaseUrl()}/compatibility/offlineClients/${QE}`).pipe(ta(i=>i.isCompatible),dp(r=>r.status===401?ws(!0):Q1(()=>r)))}',
126 fromAlt4: 'isCompatibleWithOnline$(){return this.http.get(`${ps.getSyncApiBaseUrl()}/compatibility/offlineClients/${UE}`).pipe(Ea(t=>t.isCompatible))}',
127 toAlt4: 'isCompatibleWithOnline$(){return this.http.get(`${ps.getSyncApiBaseUrl()}/compatibility/offlineClients/${UE}`).pipe(Ea(t=>t.isCompatible),dp(s=>s.status===401?ws(!0):Q1(()=>s)))}',
128 to: 'isCompatibleWithOnline$(){return this.http.get(`${os.getSyncApiBaseUrl()}/compatibility/offlineClients/${GE}`).pipe(sa(r=>r.isCompatible),dp(r=>r.status===401?ws(!0):Q1(()=>r)))}',
129 alreadyMarker: 'r.status===401?ws(!0):Q1(()=>r)',
130 alreadyMarker2: 'r.status===401?ws(!0):Q1(()=>r)',
131 alreadyMarker3: 'r.status===401?ws(!0):Q1(()=>r)',
132 alreadyMarker4: 's.status===401?ws(!0):Q1(()=>s)',
133 required: false
134 }
135];
136
137for (const p of patches) {
138 let patched = false;
139
140 for (const filePath of jsFiles) {
141 let txt = fs.readFileSync(filePath, 'utf8');
142
143 if (txt.includes(p.to) || (p.toAlt && txt.includes(p.toAlt)) || (p.toAlt2 && txt.includes(p.toAlt2)) || (p.toAlt3 && txt.includes(p.toAlt3)) || (p.toAlt4 && txt.includes(p.toAlt4)) || txt.includes(p.alreadyMarker) || (p.alreadyMarker2 && txt.includes(p.alreadyMarker2)) || (p.alreadyMarker3 && txt.includes(p.alreadyMarker3)) || (p.alreadyMarker4 && txt.includes(p.alreadyMarker4))) {
144 patched = true;
145 break;
146 }
147
148 if (p.regex && p.regex.test(txt)) {
149 txt = txt.replace(p.regex, p.replaceWith);
150 fs.writeFileSync(filePath, txt);
151 patched = true;
152 break;
153 }
154
155 if (txt.includes(p.from)) {
156 txt = txt.replace(p.from, p.to);
157 fs.writeFileSync(filePath, txt);
158 patched = true;
159 break;
160 }
161
162 if (p.fromAlt && p.toAlt && txt.includes(p.fromAlt)) {
163 txt = txt.replace(p.fromAlt, p.toAlt);
164 fs.writeFileSync(filePath, txt);
165 patched = true;
166 break;
167 }
168
169 if (p.fromAlt2 && p.toAlt2 && txt.includes(p.fromAlt2)) {
170 txt = txt.replace(p.fromAlt2, p.toAlt2);
171 fs.writeFileSync(filePath, txt);
172 patched = true;
173 break;
174 }
175
176 if (p.fromAlt3 && p.toAlt3 && txt.includes(p.fromAlt3)) {
177 txt = txt.replace(p.fromAlt3, p.toAlt3);
178 fs.writeFileSync(filePath, txt);
179 patched = true;
180 break;
181 }
182
183 if (p.fromAlt4 && p.toAlt4 && txt.includes(p.fromAlt4)) {
184 txt = txt.replace(p.fromAlt4, p.toAlt4);
185 fs.writeFileSync(filePath, txt);
186 patched = true;
187 break;
188 }
189 }
190
191 if (!patched) {
192 if (p.required || strict) {
193 console.error(`ERROR: required patch target not found: ${p.name}`);
194 process.exit(1);
195 }
196
197 if (verbose) {
198 console.warn(`INFO: optional patch target not found: ${p.name} (continuing)`);
199 }
200 }
201}
202NODE
203
204 npx --yes @electron/asar pack "$_tmp_dir" "$_asar_path"
205 rm -rf "$_tmp_dir"
206}
207
208package() {
209 local _asar_path
210 _asar_path="$(_find_asar_path || true)"
211
212 if [ ! -f "$_asar_path" ]; then
213 echo "ERROR: app.asar not found"
214 exit 1
215 fi
216
217 install -Dm644 "$_asar_path" "$pkgdir/usr/share/$pkgname/app.asar"
218 install -Dm755 "${pkgname}.sh" "$pkgdir/usr/bin/$pkgname"
219 install -Dm644 "${pkgname}.desktop" "$pkgdir/usr/share/applications/${pkgname}.desktop"
220
221 # Icons
222 install -Dm644 "icon16.png" "$pkgdir/usr/share/icons/hicolor/16x16/apps/${pkgname}.png"
223 install -Dm644 "icon32.png" "$pkgdir/usr/share/icons/hicolor/32x32/apps/${pkgname}.png"
224 install -Dm644 "icon96.png" "$pkgdir/usr/share/icons/hicolor/96x96/apps/${pkgname}.png"
225
226 # Licenses
227 _license=$(find . -name "LICENSE" | head -n 1)
228 if [ -n "$_license" ]; then
229 install -Dm644 "$_license" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
230 fi
231}
232

Scan history

Scanned at (UTC)SeverityRules
2026-09-17 00:27:14 Low 3
2026-09-16 00:03:17 Low 3
2026-09-15 00:25:31 Low 3
2026-09-14 00:27:57 Low 3
2026-09-13 00:19:54 Low 3
2026-09-12 00:25:17 Low 3
2026-09-11 00:19:22 Low 3
2026-09-10 00:22:44 Low 3
2026-09-09 00:04:09 Low 3
2026-09-08 00:18:08 Low 3
2026-09-07 00:30:15 Low 3
2026-09-06 00:17:06 Low 3
2026-09-05 00:16:27 Low 3
2026-09-04 00:03:13 Low 3
2026-09-03 00:15:47 Low 3
2026-09-02 00:02:31 Low 3
2026-09-01 00:11:19 Low 3
2026-08-31 00:19:57 Low 3
2026-08-30 00:04:14 Low 3
2026-08-29 00:29:17 Low 3

Report a package

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

0 / 4000
Your suggestion