Compare commits

..
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 ec47d542dd ci(gitleaks): allowlist the desktop updater minisign public key
CI / Build & Quality Checks (pull_request) Successful in 2m9s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 7s
CI / Playwright smoke (e2e) (pull_request) Successful in 12m38s
utils/updateErrors.ts embeds the public key that verifies Linux
downloads (cinny-desktop #36); gitleaks' generic-api-key rule matched
it. It is a published public key, not a secret.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 04:24:51 -04:00
Lotus CIandClaude Opus 5.5 674aef94fb fix(desktop): the Linux update command verifies the signed SHA256SUMS before installing (cinny-desktop #36)
CI / Build & Quality Checks (pull_request) Successful in 1m54s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Failing after 13s
CI / Playwright smoke (e2e) (pull_request) Successful in 12m47s
The pacman / apt update command installed the downloaded package with
no integrity check. Download it with the release's SHA256SUMS.txt and
.minisig into a fresh temp dir, verify the signature with the updater
key (minisign), check the hash, then install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 00:37:34 -04:00
6 changed files with 42 additions and 32 deletions
+3
View File
@@ -12,6 +12,9 @@ regexes = [
'''https?://matrix\.lotusguild\.org''',
'''https?://code\.lotusguild\.org/api/packages/LotusGuild/npm/''',
'''matrix\.lotusguild\.org''',
# Lotus Chat desktop updater's minisign PUBLIC key (published in the
# cinny-desktop README); utils/updateErrors.ts verifies Linux downloads with it.
'''RWRZZdyii05/NUcz337A5SAbiZK\+NPVDWuiL2mM5Jk1p/LfRmNfiZ/6l''',
]
paths = [
'''config\.json''',
@@ -108,7 +108,6 @@ import {
TRANSLATE_TARGET_LANGUAGES,
normalizeLang,
isSupportedTargetLang,
translationUnavailableText,
} from '../../../utils/translation/langUtils';
import { chromeTranslationEngine } from '../../../utils/translation/chromeEngine';
import { SequenceCardStyle } from '../styles.css';
@@ -2757,7 +2756,7 @@ function Messages() {
description={
translationSupported
? 'Translate messages written in other languages into this one. Runs entirely on your device — message text never leaves your computer or touches a cloud service.'
: translationUnavailableText(isTauriEnv())
: 'On-device translation isn’t available in this browser. Use a Chromium desktop browser (Chrome/Edge 138+) or the Lotus desktop app.'
}
after={
<SettingsSelect
@@ -1,7 +1,6 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
translationUnavailableText,
normalizeLang,
sameLanguage,
languageName,
@@ -54,9 +53,3 @@ test('curated list: unique codes, English present', () => {
assert.equal(new Set(codes).size, codes.length);
assert.ok(codes.includes('en'));
});
test('translationUnavailableText: no "use the desktop app" inside the desktop app (#349)', () => {
assert.doesNotMatch(translationUnavailableText(true), /desktop app\.$|Lotus desktop app/);
assert.match(translationUnavailableText(true), /this version of the desktop app/);
assert.match(translationUnavailableText(false), /this browser/);
});
-10
View File
@@ -86,16 +86,6 @@ export function isSupportedTargetLang(code: string): boolean {
return CURATED_NAME.has(normalizeLang(code));
}
/**
* [Gitea #349] Why translation is unavailable, by platform. The desktop app
* on Linux/macOS (WebKit) lacks the on-device APIs too, so it must not send
* people to "the Lotus desktop app" they're already using.
*/
export const translationUnavailableText = (desktopApp: boolean): string =>
desktopApp
? 'On-device translation isn’t available in this version of the desktop app. It works in Chrome or Edge 138+ on a computer.'
: 'On-device translation isn’t available in this browser. Use Chrome or Edge 138+ on a computer.';
/** Pure: why a manual Translate did nothing (#287). */
export const translateSkipMessage = (from: string | undefined, targetLang: string): string =>
from
+14 -4
View File
@@ -69,13 +69,23 @@ test('install kinds: anything unknown keeps the in-app updater', async () => {
test('package installs get their package manager’s command, not an Install button', async () => {
const { packageUpdateHelp, isPackageManagedError } = await import('./updateErrors');
const pacman = packageUpdateHelp('pacman')!;
// Not `pacman -U <url>`: that also wants `<url>.sig`, which we don't publish.
assert.equal(
pacman.command,
'curl -LO https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64.pkg.tar.zst && sudo pacman -U ./LotusChat-x86_64.pkg.tar.zst',
// [cinny-desktop #36] signature, then checksum, then install, in that order;
// a local `pacman -U ./file` (not `<url>`, which also wants `<url>.sig`).
const steps = pacman.command!.split(' && ');
assert.equal(steps[0], 'cd "$(mktemp -d)"');
assert.match(
steps[1],
/-fLO \S+\/LotusChat-x86_64\.pkg\.tar\.zst -fLO \S+\/SHA256SUMS\.txt -fLO \S+\/SHA256SUMS\.txt\.minisig$/,
);
assert.equal(
steps[2],
'minisign -Vm SHA256SUMS.txt -P RWRZZdyii05/NUcz337A5SAbiZK+NPVDWuiL2mM5Jk1p/LfRmNfiZ/6l',
);
assert.equal(steps[3], 'sha256sum -c --ignore-missing SHA256SUMS.txt');
assert.equal(steps[4], 'sudo pacman -U ./LotusChat-x86_64.pkg.tar.zst');
assert.match(pacman.url, /LotusChat-x86_64\.pkg\.tar\.zst$/);
const deb = packageUpdateHelp('deb')!;
assert.match(deb.command!, /minisign -Vm SHA256SUMS\.txt/);
assert.match(deb.command!, /sudo apt install \.\/LotusChat-x86_64\.deb$/);
assert.equal(packageUpdateHelp('manual')?.command, undefined);
assert.equal(packageUpdateHelp('in-app'), undefined);
+24 -9
View File
@@ -64,23 +64,38 @@ export const toInstallKind = (value: unknown): InstallKind =>
export type PackageUpdateHelp = { command?: string; url: string; download: string };
/** The desktop updater's minisign public key (cinny-desktop tauri.conf.json). */
export const RELEASE_MINISIGN_KEY = 'RWRZZdyii05/NUcz337A5SAbiZK+NPVDWuiL2mM5Jk1p/LfRmNfiZ/6l';
/**
* [cinny-desktop #36] Download a Linux package and install it only after
* checking the release's minisign-signed SHA256SUMS.txt. Runs in a fresh temp
* directory so a stale SHA256SUMS.txt can't be picked up.
*/
const verifiedInstall = (file: string, install: string): string =>
[
`cd "$(mktemp -d)"`,
`curl -fLO ${RELEASE_DOWNLOAD}/${file} -fLO ${RELEASE_DOWNLOAD}/SHA256SUMS.txt -fLO ${RELEASE_DOWNLOAD}/SHA256SUMS.txt.minisig`,
`minisign -Vm SHA256SUMS.txt -P ${RELEASE_MINISIGN_KEY}`,
`sha256sum -c --ignore-missing SHA256SUMS.txt`,
`${install} ./${file}`,
].join(' && ');
export const packageUpdateHelp = (kind: InstallKind): PackageUpdateHelp | undefined => {
if (kind === 'pacman') {
const url = `${RELEASE_DOWNLOAD}/LotusChat-x86_64.pkg.tar.zst`;
// Download first, then install the local file: `pacman -U <url>` also
// fetches `<url>.sig` and fails without it (we don't sign packages),
// while a local file falls under LocalFileSigLevel (signature optional).
// Install the local file: `pacman -U <url>` also fetches `<url>.sig` and
// fails without it (packages aren't signed for pacman), while a local file
// falls under LocalFileSigLevel. Integrity comes from SHA256SUMS.txt.
return {
command: `curl -LO ${url} && sudo pacman -U ./LotusChat-x86_64.pkg.tar.zst`,
url,
command: verifiedInstall('LotusChat-x86_64.pkg.tar.zst', 'sudo pacman -U'),
url: `${RELEASE_DOWNLOAD}/LotusChat-x86_64.pkg.tar.zst`,
download: 'Download package',
};
}
if (kind === 'deb') {
const url = `${RELEASE_DOWNLOAD}/LotusChat-x86_64.deb`;
return {
command: `curl -LO ${url} && sudo apt install ./LotusChat-x86_64.deb`,
url,
command: verifiedInstall('LotusChat-x86_64.deb', 'sudo apt install'),
url: `${RELEASE_DOWNLOAD}/LotusChat-x86_64.deb`,
download: 'Download package',
};
}