Tracks the security posture of the SermonIndex node app and how to keep its dependencies patched (Rust/Cargo + JavaScript/npm).
Last verified: cargo build and cargo audit were run on a maintainer's
machine (rustc updated via rustup). Results below reflect that run.
Baseline was 7 vulnerabilities. After remediation: 4 remaining, all in
quick-xml, both upstream-blocked (see below). Run cd src-tauri && cargo audit
to re-check at any time.
TLS certificate-validation bugs (a reachable panic in CRL parsing + two name-constraint acceptance bugs) on our HTTPS connections. Cleared by a patch bump:
cd src-tauri
cargo update -p rustls-webpki # 0.103.11 -> 0.103.13Two high-severity DoS advisories (memory-exhaustion + quadratic-time XML parsing).
Fix is quick-xml >= 0.41.0, which is a breaking API change, so it must come
from the parent crates — we can't hand-pin it without breaking the build. There
are two independent copies in the tree:
quick-xml 0.37.5 <- librqbit-upnp 1.0.0 <- librqbit 8.1.1 (reachable)
quick-xml 0.38.4 <- plist 1.8.0 <- tauri 2.11.x (NOT reachable)
- librqbit-upnp path (reachable): parses UPnP/SSDP XML from devices on the
local network during port-mapping. Real but narrow: a denial-of-service
only (crash / memory spike, not code execution), and it requires a malicious
UPnP device on the user's own LAN.
cargo update -p librqbitwas tried and does not yet pull a patchedlibrqbit-upnp— blocked until librqbit bumps it. - plist path (not reachable):
plistis pulled in by Tauri for macOS bundle handling and only ever parses the app's own, trustedInfo.plist— not attacker-controlled input. It cannot be removed without dropping Tauri, and is a non-issue in practice.
Decision: accept + monitor. A clean cargo audit is not achievable today
regardless of action, because the plist -> quick-xml copy is baked into Tauri.
Re-run cargo update && cargo audit periodically; when librqbit-upnp and plist
ship quick-xml >= 0.41, both clear automatically.
Optional hardening (removes the reachable copy only): gate librqbit's UPnP so that code path isn't built — automatic port-forwarding still works via NAT-PMP/PCP (already implemented) plus manual forwarding. This clears the 2 librqbit copies but leaves the 2 (harmless) plist copies. Ask if you want this done.
cargo audit also lists "unmaintained / unsound / yanked" transitive crates
(the gtk-rs GTK3 bindings, bincode, anyhow, rand, instant, etc.). These
are mostly Tauri's own Linux GUI dependencies, are not counted as vulnerabilities,
and need no action.
src-tauri/Cargo.toml:
tauri = { version = "2.11.1", features = ["tray-icon"] } # resolves to 2.11.5cargo build completed successfully with this bump, so the backend compiles on
2.11.x. Besides routine fixes, this closes a Windows/Android IPC-origin
advisory (an IPC message could be accepted from an unexpected origin on those
platforms).
Task 93 added a session-level BitTorrent upload rate limit
(librqbit::limits::LimitsConfig + Session.ratelimits.set_upload_bps) in
src-tauri/src/torrent_node.rs, with a set_upload_limit Tauri command and a
Settings opt-in control. cargo build compiles it cleanly against librqbit 8.1.1.
Remaining check is behavioral (confirm the cap actually limits upload speed in a
running build).
master-list.json is the app's trust anchor: it maps every sermon to an
infohash/magnet, and the app only joins swarms that appear in it. Serving it over
HTTPS proves only that it came from the CDN — it does not prove SermonIndex
authored it. Anyone able to write to the CDN path (leaked Bunny storage password,
compromised admin DB, malicious insider) could previously inject arbitrary
infohashes and redirect every node to attacker-chosen content.
It is now signed offline with an ed25519 key:
- Detached signature over the RAW BYTES of
master-list.json, published beside it asmaster-list.json.sig(base64 of the 64-byte signature). Raw bytes — not canonicalized JSON — so there is no serialization mismatch to exploit. Reformatting the JSON after signing invalidates the signature. - Verification happens in Rust (
verify_master_listinsrc-tauri/src/lib.rs), not WebCrypto: WKWebView's Ed25519 support is unreliable. - The public key is compiled into the binary (
MASTER_LIST_PUBKEY_B64). Changing it requires shipping a new, code-signed build — the CDN cannot change who is trusted. - Fail closed. Missing, malformed, or invalid signature → the list is not applied and not cached; the app keeps the previously-cached/bundled catalog. HTTP downloads are unaffected; only canonical torrent data is withheld.
Scope note: the on-disk localStorage cache of an already-verified list is not
re-verified on load. An attacker with local write access to the user's profile
already controls the app, so this is not an additional exposure.
scripts/masterlist.key — PKCS#8 PEM, mode 0600, generated by
scripts/gen-masterlist-key.mjs. It is gitignored by the existing *.key rule
(confirm with git check-ignore -v scripts/masterlist.key).
It must never be committed, uploaded, or placed in CI. Signing is a manual, offline maintainer step. Keep an encrypted offline backup — losing it means every node stops accepting master-list updates until a new key is generated and a new build carrying the new public key is shipped to everyone.
node scripts/generate-canonical-torrents.mjs # auto-signs at the end if the key exists
node scripts/sign-master-list.mjs # or re-sign without regenerating torrents
node scripts/upload-canonical-torrents.mjs # uploads the .sig first, then the .json
# then purge BOTH master-list.json and master-list.json.sig on the Bunny pull zoneThe uploader pushes the signature before the JSON, so there is never a window where a new JSON is live against a stale signature.
Verification fails closed, so:
The signed
master-list.json+master-list.json.sigMUST be live on the CDN BEFORE (or at the same moment as) shipping any build with verification enabled.
Ship a verifying build first and upgraded nodes will reject the master list entirely — they fall back to the bundled catalog, so HTTP downloads keep working, but all canonical torrent data (magnets, infohashes, verified sizes) is ignored until the signature is published and those nodes re-pull.
Correct order, every time:
node scripts/gen-masterlist-key.mjs(once;--forceonly to rotate)- Paste the printed public key into
MASTER_LIST_PUBKEY_B64insrc-tauri/src/lib.rs node scripts/sign-master-list.mjsnode scripts/upload-canonical-torrents.mjs, then purge the CDN- Verify live:
curl -sI https://sermonindex1.b-cdn.net/torrents/master-list.json.sig - Only now build and release the app
Rotation invalidates every published signature, so it is the same ordering problem twice over:
node scripts/gen-masterlist-key.mjs --force(back up the old key first)- Paste the new public key into
MASTER_LIST_PUBKEY_B64 - Re-sign and upload
master-list.json.sig, purge the CDN - Ship the new build
Between steps 3 and 4, old builds break — they carry the old public key and will reject the newly signed list. There is no dual-key grace period today; if rotation ever becomes urgent (key compromise), accept that old nodes fall back to the bundled catalog until they auto-update, and consider pairing it with a forced update push. Adding a second accepted key to the Rust constant ahead of time is the clean way to make future rotations seamless.
npm run audit:js reports build-time dev-tooling advisories only
(esbuild/vite dev-server request-leak, postcss stringify XSS). None of these
ship in the packaged desktop app.
npm install
npm run audit:js
npm audit fix # safe, non-breaking (clears postcss, etc.)
# do NOT run: npm audit fix --force (drags vite 5 -> 8, a breaking upgrade,
# to fix a local dev-server-only issue)The residual esbuild/vite advisory is dev-server-only and safe to defer until
a deliberate Vite major upgrade.
package.json has non-breaking lint, test, audit:js, audit:rust scripts
plus a minimal, dependency-free eslint.config.mjs. Opt in when ready:
npm i -D eslint # then: npm run lint
npm i -D vitest # then add *.test.js and: npm test| Area | Command(s) | Status |
|---|---|---|
| Rust advisories | cd src-tauri && cargo update && cargo audit |
✅ run — rustls-webpki fixed; quick-xml tracked (upstream) |
| Tauri bump | cd src-tauri && cargo build |
✅ builds on 2.11.5 |
| Upload throttle | (in Rust; verify behavior in a running build) | ✅ compiles |
| Master-list key | git check-ignore -v scripts/masterlist.key |
must be ignored; pubkey pasted into MASTER_LIST_PUBKEY_B64 |
| JS advisories | npm install && npm run audit:js && npm audit fix |
dev-only; safe fixes pending |
| JS lint / tests | npm i -D eslint vitest && npm run lint && npm test |
opt-in |