From 4dcb37f5a2acb2c8b64c1330f1c3f28e54589f82 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 18 Aug 2025 06:26:50 +0100 Subject: [PATCH] ## 0.25.8 ### New feature - Insecure chunk detection has been implemented. ### Fixed - Unexpected `Failed to obtain PBKDF2 salt` or similar errors during bucket-synchronisation no longer occur. - Unexpected long delays for chunk-missing documents when using bucket-synchronisation have been resolved. - Fetched remote chunks are now properly stored in the local database if `Fetch chunks on demand` is enabled. - The 'fetch' dialogue's message has been refined. - No longer overwriting any corrupted documents to the storage on boot-sequence. ### Refactored - Type errors have been corrected. --- src/lib | 2 +- src/modules/core/ModuleFileHandler.ts | 21 +++---- src/modules/essential/ModuleMigration.ts | 76 ++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/lib b/src/lib index 1f51336..a1c644e 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 1f51336162157983e63759808906366dad533fdb +Subproject commit a1c644e959820b5effcb6812c0a3d7998ae53be2 diff --git a/src/modules/core/ModuleFileHandler.ts b/src/modules/core/ModuleFileHandler.ts index c72521f..c293d2d 100644 --- a/src/modules/core/ModuleFileHandler.ts +++ b/src/modules/core/ModuleFileHandler.ts @@ -256,19 +256,20 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule { this._log(`File ${path} is not exist on the database`, LOG_LEVEL_VERBOSE); return false; } + + // If we want to process size mismatched files -- in case of having files created by some integrations, enable the toggle. + if (!this.settings.processSizeMismatchedFiles) { + // Check the file is not corrupted + // (Zero is a special case, may be created by some APIs and it might be acceptable). + if (docRead.size != 0 && docRead.size !== readAsBlob(docRead).size) { + this._log(`File ${path} seems to be corrupted! Writing prevented.`, LOG_LEVEL_NOTICE); + return false; + } + } + const docData = readContent(docRead); if (existOnStorage && !force) { - // If we want to process size mismatched files -- in case of having files created by some integrations, enable the toggle. - if (!this.settings.processSizeMismatchedFiles) { - // Check the file is not corrupted - // (Zero is a special case, may be created by some APIs and it might be acceptable). - if (docRead.size != 0 && docRead.size !== readAsBlob(docRead).size) { - this._log(`File ${path} seems to be corrupted! Writing prevented.`, LOG_LEVEL_NOTICE); - return false; - } - } - // The file is exist on the storage. Let's check the difference between the file and the entry. // But, if force is true, then it should be updated. // Ok, we have to compare. diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index 8327d52..2623f32 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -15,6 +15,7 @@ import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/ import { getPath, isValidPath } from "../../common/utils.ts"; import { isMetaEntry } from "../../lib/src/common/types.ts"; import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts"; +import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts"; export class ModuleMigration extends AbstractModule implements ICoreModule { async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) { @@ -36,11 +37,14 @@ export class ModuleMigration extends AbstractModule implements ICoreModule { if (shouldRebuild) { await this.core.rebuilder.scheduleRebuild(); await this.core.$$performRestart(); + return false; } else if (shouldRebuildLocal) { await this.core.rebuilder.scheduleFetch(); await this.core.$$performRestart(); + return false; } } + return true; } async migrateDisableBulkSend() { @@ -215,15 +219,77 @@ export class ModuleMigration extends AbstractModule implements ICoreModule { return Promise.resolve(true); } + async checkCompromisedChunks(): Promise { + Logger(`Checking for compromised chunks...`, LOG_LEVEL_VERBOSE); + if (!this.settings.encrypt) { + // If not encrypted, we do not need to check for compromised chunks. + return true; + } + // Check local database for compromised chunks + const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase); + const remote = this.core.$$getReplicator(); + const remoteCompromised = await remote.countCompromisedChunks(); + if (localCompromised === false) { + Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE); + return false; + } + if (remoteCompromised === false) { + Logger(`Failed to count compromised chunks in remote database`, LOG_LEVEL_NOTICE); + return false; + } + if (remoteCompromised === 0 && localCompromised === 0) { + return true; + } + Logger( + `Found compromised chunks : ${localCompromised} in local, ${remoteCompromised} in remote`, + LOG_LEVEL_NOTICE + ); + const title = $msg("moduleMigration.insecureChunkExist.title"); + const msg = $msg("moduleMigration.insecureChunkExist.message"); + const REBUILD = $msg("moduleMigration.insecureChunkExist.buttons.rebuild"); + const FETCH = $msg("moduleMigration.insecureChunkExist.buttons.fetch"); + const DISMISS = $msg("moduleMigration.insecureChunkExist.buttons.later"); + const buttons = [REBUILD, FETCH, DISMISS]; + if (remoteCompromised != 0) { + buttons.splice(buttons.indexOf(FETCH), 1); + } + const result = await this.core.confirm.askSelectStringDialogue(msg, buttons, { + title, + defaultAction: DISMISS, + timeout: 0, + }); + if (result === REBUILD) { + // Rebuild the database + await this.core.rebuilder.scheduleRebuild(); + await this.core.$$performRestart(); + return false; + } else if (result === FETCH) { + // Fetch the latest data from remote + await this.core.rebuilder.scheduleFetch(); + await this.core.$$performRestart(); + return false; + } else { + // User chose to dismiss the issue + this._log($msg("moduleMigration.insecureChunkExist.laterMessage"), LOG_LEVEL_NOTICE); + } + return true; + } + async $everyOnFirstInitialize(): Promise { if (!this.localDatabase.isReady) { this._log($msg("moduleMigration.logLocalDatabaseNotReady"), LOG_LEVEL_NOTICE); return false; } if (this.settings.isConfigured) { - // TODO: Probably we have to check for insecure chunks - await this.checkIncompleteDocs(); - await this.migrateUsingDoctor(false); + if (await this.checkCompromisedChunks()) { + return false; + } + if (await this.checkIncompleteDocs()) { + return false; + } + if (await this.migrateUsingDoctor(false)) { + return false; + } // await this.migrationCheck(); await this.migrateDisableBulkSend(); } @@ -233,7 +299,9 @@ export class ModuleMigration extends AbstractModule implements ICoreModule { this._log($msg("moduleMigration.logSetupCancelled"), LOG_LEVEL_NOTICE); return false; } - await this.migrateUsingDoctor(true); + if (await this.migrateUsingDoctor(true)) { + return false; + } } return true; }