Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 5 additions & 83 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@
"clsx": "^2.1.1",
"dayjs": "^1.10.7",
"electron-store": "^8.0.1",
"electron-updater": "^6.8.3",
"framer-motion": "^5.6.0",
"jwt-decode": "^3.1.2",
"openapi-fetch": "^0.13.4",
Expand Down
2 changes: 1 addition & 1 deletion release/sign.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ $base64 = [System.Convert]::ToBase64String($bytes)
Write-Host "Exe base64 hash: $base64"

(Get-Content $yamlPath) `
| ForEach-Object { $_ -replace '^(\s*sha512:\s*).+', "`$1$base64" } `
| ForEach-Object { $_ -replace '^(\s*sha512:\s*).+', ('${1}' + $base64) } `
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build release script was sometimes generating wrongly the latest.yml because of the hash.

| Set-Content $yamlPath
46 changes: 46 additions & 0 deletions src/apps/main/electron/autoupdater/check-for-updates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { logger } from '@internxt/drive-desktop-core/build/backend';
import { app } from 'electron';
import { writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { measurePerfomance } from '@/core/utils/measure-performance';
import { INTERNXT_VERSION } from '@/core/utils/utils';
import { showDialog } from './show-dialog';
import { verifyHash } from './verify-hash';

export function isNewer(current: string, latestVersion: string) {
const [la, lb, lc] = latestVersion.split('.').map(Number);
const [ca, cb, cc] = current.split('.').map(Number);
return la > ca || (la === ca && (lb > cb || (lb === cb && lc > cc)));
}

export async function checkForUpdates() {
if (!app.isPackaged) return;

try {
const res = await fetch('https://api.github.com/repos/internxt/drive-desktop/releases/latestVersion');
const data = await res.json();
const release = data as { tag_name: string };
const latestVersion = release.tag_name.replace(/^v/, '');

if (!isNewer(INTERNXT_VERSION, latestVersion)) {
logger.debug({ msg: 'App is up to date', latestVersion });
return;
}

const fileName = `Internxt-Setup-${latestVersion}.exe`;
const filePath = join(tmpdir(), fileName);

logger.debug({ msg: 'New release available', latestVersion, filePath });
const time = await measurePerfomance(async () => {
const res = await fetch(`https://github.com/internxt/drive-desktop/releases/tag/v2.6.8/${filePath}`);
await writeFile(filePath, Buffer.from(await res.arrayBuffer()));
});
logger.debug({ msg: 'New release downloaded', latestVersion, time });

await verifyHash({ filePath, latestVersion });
await showDialog({ filePath, latestVersion });
} catch (error) {
logger.error({ msg: 'Check for updates failed', error });
}
}
18 changes: 18 additions & 0 deletions src/apps/main/electron/autoupdater/show-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { dialog, nativeImage } from 'electron';
import { spawn } from 'node:child_process';
import { iconPath } from '@/apps/utils/icon';

export async function showDialog({ filePath, latestVersion }: { filePath: string; latestVersion: string }) {
const { response } = await dialog.showMessageBox({
type: 'info',
icon: nativeImage.createFromPath(iconPath),
title: 'Update Available',
message: `Version ${latestVersion} is available`,
detail: 'Download and install the update now?',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, it's already downloaded

buttons: ['Update Now', 'Later'],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may be interested in forcing updates always. We do not want clients to use problematic apps if something happens, it's preferable to release a new one and let the autoupdater autoupdate directly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to, but if the user has the Settings window open or doing a backup or something it will stop in the middle and maybe is not the best UX. For me it would be the simplest, so I can change it. But not sure if what we want. Anyway, we can change between both in anytime.

});

if (response !== 0) return;

spawn(filePath, ['--updated'], { detached: true, stdio: 'ignore' }).unref();

Check failure on line 17 in src/apps/main/electron/autoupdater/show-dialog.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not construct the OS command from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-desktop&issues=AZ269jTbX5YjU0kPVdz7&open=AZ269jTbX5YjU0kPVdz7&pullRequest=1361

Check failure

Code scanning / SonarCloud

OS commands should not be vulnerable to command injection attacks High

Change this code to not construct the OS command from user-controlled data. See more on SonarQube Cloud
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
21 changes: 21 additions & 0 deletions src/apps/main/electron/autoupdater/verify-hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { logger } from '@internxt/drive-desktop-core/build/backend';
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';

export async function verifyHash({ filePath, latestVersion }: { filePath: string; latestVersion: string }) {
const data = await readFile(filePath);
const actual = createHash('sha512').update(data).digest('base64');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an app of a couple hundred MBs, it's better to use a stream (pipeline(readable, hasher)) rather than loading the entire binary content in memory

Copy link
Copy Markdown
Contributor Author

@dajimenezriv-internxt dajimenezriv-internxt Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading it into memory takes 0.8s and with the pipeline it takes 3.15s, which is time at the startup and that blocks the main thread. Should I change it?


logger.debug({ msg: 'Verifying release update', actual });

const url = `https://github.com/internxt/drive-desktop/releases/download/v${latestVersion}/latest.yml`;
const res = await fetch(url);

Check failure on line 12 in src/apps/main/electron/autoupdater/verify-hash.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Ensure that tainted data is validated before being used to construct a client-side request URL.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-desktop&issues=AZ269jXRX5YjU0kPVdz9&open=AZ269jXRX5YjU0kPVdz9&pullRequest=1361

Check failure

Code scanning / SonarCloud

Client-side requests should not be vulnerable to forging attacks High

Ensure that tainted data is validated before being used to construct a client-side request URL. See more on SonarQube Cloud
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
const text = await res.text();

const match = /^sha512:\s*(.+)$/m.exec(text);
if (!match) throw new Error('sha512 not found in latest.yml');
const expected = match[1].trim();

if (actual !== expected) throw new Error(`sha512 mismatch: expected ${expected}, got ${actual}`);
logger.debug({ msg: 'Release hash verified', actual, expected });
}
19 changes: 4 additions & 15 deletions src/apps/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'core-js/stable';
// via webpack in prod
import 'dotenv/config';
import { app, crashReporter } from 'electron';
import { autoUpdater } from 'electron-updater';
import { arch, release, version } from 'node:os';
import { resolve } from 'node:path';
import 'reflect-metadata';
Expand All @@ -24,6 +23,7 @@ import { setUpBackups } from './background-processes/backups/setUpBackups';
import { setupIssueHandlers } from './background-processes/issues';
import { setupThemeListener } from './config/theme';
import { setupDeviceIpc } from './device/handlers';
import { checkForUpdates } from './electron/autoupdater/check-for-updates';
import { processDeeplink } from './electron/deeplink/process-deeplink';
import { setupAntivirusIpc } from './ipcs/ipcMainAntivirus';
import { setupPreloadIpc } from './preload/ipc-main';
Expand Down Expand Up @@ -87,16 +87,6 @@ logger.debug({
arch: arch(),
});

async function checkForUpdates() {
autoUpdater.logger = {
debug: (msg) => logger.debug({ msg: `AutoUpdater: ${msg}` }),
info: (msg) => logger.debug({ msg: `AutoUpdater: ${msg}` }),
error: (msg) => logger.error({ msg: `AutoUpdater: ${msg}` }),
warn: (msg) => logger.warn({ msg: `AutoUpdater: ${msg}` }),
};
await autoUpdater.checkForUpdatesAndNotify();
}

if (process.env.NODE_ENV === 'production') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const sourceMapSupport = require('source-map-support');
Expand All @@ -118,9 +108,11 @@ app
.then(async () => {
app.setAppUserModelId(INTERNXT_APP_ID);

setupTrayIcon();
await checkForUpdates();

measureHealth();
runMigrations();
setupTrayIcon();
setUpBackups();

const user = checkIfUserIsLoggedIn();
Expand All @@ -133,8 +125,5 @@ app
showFrontend();
setTrayStatus('IDLE');
}

await checkForUpdates();
setInterval(checkForUpdates, 60 * 60 * 1000);
})
.catch((exc) => logger.error({ msg: 'Error starting app', exc }));
Loading