Skip to content

Fix/orphaned crypted file cleanup#15098

Open
akshajrawat wants to merge 11 commits intolaurent22:devfrom
akshajrawat:fix/orphaned-crypted-file-cleanup
Open

Fix/orphaned crypted file cleanup#15098
akshajrawat wants to merge 11 commits intolaurent22:devfrom
akshajrawat:fix/orphaned-crypted-file-cleanup

Conversation

@akshajrawat
Copy link
Copy Markdown
Contributor

@akshajrawat akshajrawat commented Apr 14, 2026

Problem

Fixes #9093

When End-to-End Encryption (E2EE) is enabled, Joplin creates temporary .crypted files during the resource upload process. If an upload is interrupted or the application crashes, these files remain orphaned.

Previously, these files were not cleaned up automatically, leading to unnecessary disk usage over time.

Solution

This PR introduces a cleanup mechanism by isolating temporary encryption files into a dedicated directory.

Here is the benchmark testing:
https://discourse.joplinapp.org/t/gsoc-2026-local-note-encryption-draft-proposal-and-poc/49012/33?u=akshajrawat

Key Changes:

  • Isolation: Temporary .crypted files are now stored in a dedicated subdirectory: [tempDir]/encryptionCache.

  • Efficient Cleanup: Added startup hooks across Desktop, CLI, and Mobile platforms to clear the encryption cache directory on boot.

  • Redirection: Updated Resource.tempCryptedPath so that all temporary encrypted upload artifacts are created inside the encryption cache directory instead of the main resources directory.

  • Upload Cleanup: After a successful upload, temporary encrypted files are deleted if they originate from the encryption cache directory.

  • Backward Compatibility: Existing handling of .crypted files in the main resources directory is preserved for pre-upgrade resources.

  • Additional Fix: Ensured proper await usage for file move operations to prevent unhandled promises.

This change is low-risk as it only affects temporary files used during the sync-upload lifecycle and does not modify primary resource storage logic.

Test Plan

  • Added a unit test in packages/lib/models/Resource.test.ts to verify that emptyTempEncryptionCache() correctly removes files within the encryption cache directory.

AI Assistance Disclosure

  • Was AI used to generate this code? Yes, AI was used to assist in identifying appropriate startup hook locations and refining the TypeScript implementation for cross-platform compatibility.

  • Was AI used to generate this PR description? Yes, the PR description was initially AI-assisted and then manually reviewed and refined before submission.

Note on GSoC Project Alignment:

The implementation of the encryptionCache directory aligns with the resource-handling approach discussed in the GSoC local encryption proposal. This isolated directory provides a clean foundation for handling temporary encrypted resources in future encryption-related improvements.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Startup tasks in CLI, desktop and mobile now clear orphaned .crypted files from a temporary encryption cache. Resource adds tempCryptedPath() and emptyTempEncryptionCache(); decrypt() and sync upload logic were adjusted to use and clean the temp cache.

Changes

Cohort / File(s) Summary
Core resource encryption and cleanup logic
packages/lib/models/Resource.ts, packages/lib/models/Resource.test.ts
Added tempCryptedPath(resourceId) and emptyTempEncryptionCache(). decrypt() awaits the fallback move and performs a best-effort deletion of leftover .crypted files. fullPathForSyncUpload writes encryption output to the temp encryption cache for plaintext resources. Added unit test for cleaning orphaned .crypted files.
CLI startup cleanup
packages/app-cli/app/app.ts
Calls Resource.emptyTempEncryptionCache() during startup (after command service init). Call is wrapped in try/catch and logs warnings on failure without aborting startup.
Desktop startup cleanup
packages/app-desktop/app.ts
Inserts awaited startup task (after app/updateTray) that invokes Resource.emptyTempEncryptionCache() with try/catch and warning-level logging to avoid failing startup.
Mobile startup cleanup
packages/app-mobile/utils/buildStartupTasks.ts
Adds a startup task that invokes Resource.emptyTempEncryptionCache() wrapped in try/catch, logging warnings on failure so the startup chain continues.
Synchronizer cleanup on upload
packages/lib/Synchronizer.ts
When uploading a blob (put), deletes the local encrypted blob file if it resides in the encryption cache to prevent stale .crypted artifacts after successful upload.

Sequence Diagram(s)

sequenceDiagram
  participant Startup as Startup Manager
  participant App as Application (CLI / Desktop / Mobile)
  participant Resource as Resource
  participant FS as fsDriver
  participant Logger as Logger

  Startup->>App: begin startup sequence
  App->>Resource: emptyTempEncryptionCache()
  Resource->>FS: ensure tempDir/encryptionCache exists / list files
  FS-->>Resource: file list
  loop for each .crypted file
    Resource->>FS: unlink <file>
    FS-->>Resource: success / error
    alt error
      Resource->>Logger: warn(error.message)
    end
  end
  Resource-->>App: return (no throw)
  App-->>Startup: continue startup tasks
Loading

Suggested reviewers

  • mrjo118
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix/orphaned crypted file cleanup' accurately summarises the main change: implementing cleanup of orphaned .crypted files created during E2EE uploads.
Description check ✅ Passed The description clearly explains the problem (orphaned .crypted files), solution (dedicated encryptionCache directory with startup cleanup), and key implementation changes.
Linked Issues check ✅ Passed The PR addresses issue #9093 by implementing startup cleanup of orphaned .crypted files across Desktop, CLI, and Mobile platforms via encryptionCache directory isolation.
Out of Scope Changes check ✅ Passed Changes remain focused on orphaned .crypted file cleanup. Await fixes for file move operations represent necessary robustness improvements within scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added bug It's a bug mobile All mobile platforms desktop All desktop platforms cli CLI app specific issue performance Performance issues labels Apr 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/lib/models/Resource.ts (1)

269-271: Log the error details for debuggability.

The caught error is not included in the log message, making it harder to diagnose cleanup failures. Additionally, using info level for failures is inconsistent with the calling code in mobile/CLI which uses warn.

♻️ Suggested improvement
 		} catch (error) {
-			this.logger().info('Could not clear temporary encryption cache.');
+			this.logger().warn('Could not clear temporary encryption cache:', error);
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/models/Resource.ts` around lines 269 - 271, The catch block in
Resource.ts that currently does this.logger().info('Could not clear temporary
encryption cache.') should be changed to log the caught error details and use a
warning level: replace the info call with this.logger().warn(...) and include
the error object (e.g., this.logger().warn('Could not clear temporary encryption
cache', { error })) so the error message and stack are captured for debugging;
ensure the caught variable (error) is preserved and passed to the logger rather
than discarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/lib/models/Resource.ts`:
- Around line 269-271: The catch block in Resource.ts that currently does
this.logger().info('Could not clear temporary encryption cache.') should be
changed to log the caught error details and use a warning level: replace the
info call with this.logger().warn(...) and include the error object (e.g.,
this.logger().warn('Could not clear temporary encryption cache', { error })) so
the error message and stack are captured for debugging; ensure the caught
variable (error) is preserved and passed to the logger rather than discarded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d8dc375-03d1-4886-b623-b663044e24db

📥 Commits

Reviewing files that changed from the base of the PR and between d406e27 and 5cfc88d.

📒 Files selected for processing (5)
  • packages/app-cli/app/app.ts
  • packages/app-desktop/app.ts
  • packages/app-mobile/utils/buildStartupTasks.ts
  • packages/lib/models/Resource.test.ts
  • packages/lib/models/Resource.ts

Comment thread packages/app-desktop/app.ts Outdated

addTask('app/updateTray', () => this.updateTray());

addTask('app/deleteOrphanedTempCache', async () => await Resource.emptyTempEncryptionCache());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any reason why there isn't a try catch for this call?

Copy link
Copy Markdown
Contributor Author

@akshajrawat akshajrawat Apr 15, 2026

Choose a reason for hiding this comment

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

Thanks for review!
emptyTempEncryptionCache itself handels the error so basically it never will throw an error, though I was particularly not sure about any edge cases arising in mobile or cli so I added the double try catch there.
Now that you've mentioned it, I think it would be better to add a try catch here too to maintain the consistency.
I will just make a quick commit by adding the nitpick and try catch

@mrjo118
Copy link
Copy Markdown
Collaborator

mrjo118 commented Apr 15, 2026

Is it possible when starting the app, that the sync could be in progress while the emptying of the temp folder occurs, and this could delete a crypted file while it is uploading?

@akshajrawat
Copy link
Copy Markdown
Contributor Author

akshajrawat commented Apr 16, 2026

Is it possible when starting the app, that the sync could be in progress while the emptying of the temp folder occurs, and this could delete a crypted file while it is uploading?

I wanted to be 100% sure, so I verified this by forcing an artificial delay on the sweep and seeing the startup logs.

There's no risk of a race condition here. The logs confirm that the startup sweep finishes during the initial boot sequence (within the first ~2 seconds), while the background Synchronizer doesn't even wake up to index resources until about 11 seconds later.

Because their lifecycles are completely isolated + Even if an unexpected overlap occurred, the tempCryptedPath function ensures the path is instantly rebuilt in time for the Sync process to use it, acting as an absolute safeguard against collisions.

@mrjo118
Copy link
Copy Markdown
Collaborator

mrjo118 commented Apr 16, 2026

Is it possible when starting the app, that the sync could be in progress while the emptying of the temp folder occurs, and this could delete a crypted file while it is uploading?

I wanted to be 100% sure, so I verified this by forcing an artificial delay on the sweep and seeing the startup logs.

There's no risk of a race condition here. The logs confirm that the startup sweep finishes during the initial boot sequence (within the first ~2 seconds), while the background Synchronizer doesn't even wake up to index resources until about 11 seconds later.

Because their lifecycles are completely isolated + Even if an unexpected overlap occurred, the tempCryptedPath function ensures the path is instantly rebuilt in time for the Sync process to use it, acting as an absolute safeguard against collisions.

If you put some random 20gb file in the tempCryptedPath, would it still delete within the first 2 seconds?

@akshajrawat
Copy link
Copy Markdown
Contributor Author

If you put some random 20gb file in the tempCryptedPath, would it still delete within the first 2 seconds?

For a single large file, deletion is usually very fast since the OS primarily removes metadata, so in practice it should still complete within the startup window. That said, I wouldn’t rely on strict timing guarantees.

Even with a larger number of files (e.g., up to ~10k in temp_cache), cleanup remains fast in practice since we’re operating on a small, isolated directory rather than scanning the entire resource folder (as reflected in the benchmarks).

The main safety comes from the blocking startup sequence (cleanup runs before the Synchronizer starts and the app waits for it to complete), so even in unlikely edge cases (e.g., temporary buildup of files), we avoid impacting active uploads.

@laurent22 laurent22 added the v3.7 label Apr 16, 2026
Copy link
Copy Markdown
Owner

@laurent22 laurent22 left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this. I think that's a sensible approach and indeed should be much faster than scanning the resource folder.

I looked at the benchmark and maybe I'm lacking context, but was that done on the CLI app? And if so would it be possible to try on the Android app?

We need to think about the worst case - some people import hundreds of thousands of notes and will have as many .crypted files. If you have, say, a million .crypted files in that folder in Android, how does it perform?

Als I'm not a fan of calling this "temp cache". Maybe just "encryptionCache"?

Comment thread packages/lib/models/Resource.ts Outdated

if (await this.fsDriver().exists(tempCacheDir)) {
try {
await this.fsDriver().remove(tempCacheDir);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Before proceeding please add a message "Clearing encryption cache..." too

@akshajrawat
Copy link
Copy Markdown
Contributor Author

I looked at the benchmark and maybe I'm lacking context

To give some context on the benchmark, I actually didn't run it in the CLI app. I wrote a quick standalone Node script just to test the raw disk I/O difference between scanning a huge folder vs. dropping an isolated one.

As for the 1 million files on Android scenario: the app shouldn't ever hold that many at once since the sync engine processes uploads in small batches. But even if a massive backlog did pile up over time, dropping the directory natively on Android is definitely the safest bet. It bypasses the React Native JS bridge entirely.

Here is the comparison benchmark for mobile too :

Image

Here is the deletion time for 1 million file :-
Each file are of around 4kb.
Image

@coderabbitai coderabbitai Bot removed bug It's a bug mobile All mobile platforms desktop All desktop platforms cli CLI app specific issue performance Performance issues labels Apr 17, 2026
@laurent22
Copy link
Copy Markdown
Owner

Hello, this is Claude review for this PR. Please take a look and see what might need to be addressed. A quick read through it and I see indeed there some small issues:

Code Review: PR #15098 — Fix orphaned .crypted file cleanup

Overview

The PR addresses #9093: orphaned .crypted files left in the resources directory after interrupted/crashed E2EE uploads. The approach:

  1. Redirect temp .crypted upload artifacts into a dedicated [tempDir]/encryptionCache/ subdirectory.
  2. Add a startup hook (Desktop / CLI / Mobile) that wipes that subdirectory.
  3. Also delete the leftover .crypted file synchronously after a successful blob decryption (in the main resource dir).

The diff is small (90/3) and well scoped. However, there are several correctness issues that should be addressed before merging.


Critical issues

1. PR description does not match the implementation

The description says files go to [profile]/tmp/temp_cache, but the code uses [tempDir]/encryptionCache (Resource.ts:264-274). Minor, but worth fixing the description so reviewers and future readers aren't misled.

2. Mismatch between upload path and sync-time encryption check (likely bug)

At Resource.ts:254 the original code does:

const encryptedPath = this.fullPath(resource, true);
if (resource.encryption_blob_encrypted) return { path: encryptedPath, resource };

i.e. when a resource is already encrypted on disk, it reads from Resource.fullPath(resource, true) (the main resources dir). The new code keeps that branch but moves the encryption output path to tempCryptedPath. So:

  • First sync attempt: encrypt → write to tempDir/encryptionCache/<id>.crypted → upload.
  • Crash mid-upload: file remains; DB still has encryption_blob_encrypted = 0.
  • Next sync attempt: re-enters fullPathForSyncUploadencryption_blob_encrypted is still 0 → re-encrypts to the temp path. ✓ OK.

But there's a separate concern: I cannot find anywhere that the temp .crypted file is removed after a successful upload. Previously the file lived next to the resource and persisted (which was the original bug). Now it lives in the temp cache and is only cleaned at next startup. That's fine for long-running mobile/desktop, but for the CLI it means files accumulate during a single session if many large encrypted resources are synced. Consider deleting after apiCall('put', ...) succeeds in Synchronizer.ts:776 — or document that startup cleanup is sufficient.

3. New post-decrypt cleanup is redundant and may mask bugs

The new block at Resource.ts:233-243 removes encryptedPath after decryption. But encryptionService().decryptFile(encryptedPath, plainTextPath) already deletes the source file on success (this is its existing contract — that's why the original code didn't need to delete it). If you've observed cases where it doesn't, the right fix is to identify why decryptFile left the file behind, not to add a second sweep here. Otherwise you risk silently swallowing a real bug.

Also note: in the invalidIdentifier recovery branch, you move(encryptedPath, plainTextPath). After that, encryptedPath no longer exists, and the new block is a no-op. So this block only fires when decryptFile returned successfully but didn't delete the input — which is the case I'd want to investigate rather than paper over.

4. encryption_blob_encrypted already-encrypted branch still points at main resource dir

At Resource.ts:294-297:

if (resource.encryption_blob_encrypted) {
    const encryptedPath = this.fullPath(resource, true);
    return { path: encryptedPath, resource: resource };
}

If the resource was already encrypted by a previous run that used the old code path (pre-upgrade), the .crypted file is in the resources dir and this works. But going forward, encrypted upload artifacts only ever live in the temp cache — so this branch is reachable only for legacy state or for resources where encryption_blob_encrypted = 1 is set in the DB. Worth adding a one-line comment clarifying that the two paths (main vs temp) are intentional, otherwise this looks like an inconsistency.

5. Race: tempCryptedPath does an exists-then-mkdir

Resource.ts:264-274:

if (!(await this.fsDriver().exists(encryptionCache))) {
    await this.fsDriver().mkdir(encryptionCache);
}

fsDriver().mkdir is idempotent (it wraps fs.mkdirp). The exists-check is unnecessary and creates a tiny TOCTOU window when two resources sync concurrently. Just call mkdir unconditionally.

6. Unhandled promise in fixed move (already fixed elsewhere in the diff, but inconsistent)

You correctly added await at Resource.ts:227 — good catch. But the pattern of unhandled promises here is incidental to the PR's stated goal. Worth calling out in the description so the maintainer knows you intentionally fixed it.


Smaller issues

  • Comment style — Project style requires // comments and "should not contain jsdoc syntax" (✓ followed). However the comment // remove temp cache file (CLI/mobile) doesn't explain why. Closer to: // Clear orphaned .crypted files left over from interrupted uploads.
  • Desktop task name — 'app/deleteOrphanedTempCache' uses a different verb ("delete") than CLI/mobile ("empty"); pick one. The method is emptyTempEncryptionCache, so app/empty temp encryption cache matches better.
  • Style nit — async () =>{ at app-desktop/app.ts:602 — missing space before {.
  • Logger consistency — logger().warn('...:', error) interpolates [object Object] in some logger backends. Prefer error.message or ${error}.
  • Test coverage is thin — The single new test (Resource.test.ts:224-238) only verifies that a file written via tempCryptedPath is deleted by emptyTempEncryptionCache. It doesn't verify:
    • The post-decrypt cleanup branch (issue Build from source code for Linux Desktop #3) actually fires.
    • That fullPathForSyncUpload writes to the new temp directory and not the resource dir.
    • That emptyTempEncryptionCache is a no-op when the dir doesn't exist (the if (await ... exists(...)) guard is untested).
  • Test isolation — The test writes into the real tempDir. If it runs in parallel with another test that also uses the encryption cache, they will interfere. Worth using a per-test temp dir or being explicit that tests in this file are serial.
  • Naming — tempCryptedPath is fine; emptyTempEncryptionCache is clear. But encryptionCache as a directory name is a misnomer — nothing here is cached in the typical sense (cache implies reuse). It's a temp staging area for in-flight uploads. pendingUploadEncryption/ or tempEncrypted/ would be more accurate. Not blocking.

Performance / O(1) claim

The PR description claims "$O(1)$ cleanup" replacing a previous full-resources-folder scan. I couldn't find an existing scan in the codebase that this replaces — the prior behaviour was that orphaned files just stayed forever. The new behaviour is O(orphans), not O(1), but it's strictly better than before. The framing in the description overstates what's there.


Security

  • No new attack surface; the temp dir is inside the user's profile.
  • The remove(encryptionCache) call is on a known fixed path, not user-controlled — safe.
  • Worth confirming fsDriver().remove on a directory recursively deletes (it does on RN-fs and node-fs in this project).

Summary

The direction is right and the patch is small. Before merging I'd want:

  1. Justify or remove the post-decrypt remove(encryptedPath) block (issue Build from source code for Linux Desktop #3) — there's likely a real bug behind needing it.
  2. Confirm the lifecycle: should the temp file be deleted right after a successful upload, or only at next startup?
  3. Drop the redundant exists check before mkdir.
  4. Tighten the PR description (path name, "$O(1)$" claim, mention the awaited-move fix).
  5. Expand tests to cover the upload-path redirection and the no-dir branch.

@akshajrawat
Copy link
Copy Markdown
Contributor Author

akshajrawat commented Apr 30, 2026

Consider deleting after apiCall('put', ...) succeeds in Synchronizer.ts:776 — or document that startup cleanup is sufficient.

I want to double check on this statement. I intentionally skipped this originally because since for this I would have to add extra peice of code in the Synchronizer while in one of the past pr you have mentioned that you don't want to get the Synchronizer code more messy. Is it fine to add the code there?

In #3 :- encryptionService().decryptFile(encryptedPath, plainTextPath) already deletes the source file on success (this is its existing contract — that's why the original code didn't need to delete it)

I checked the implementation of encryptionService().decryptFile() and it does not actually delete the source file upon success. The internal cleanUp() function only closes the I/O streams, and the unlink() calls only target the destination path.
It raises the query that should the cleanup be done in encryptionService().decryptFile() ? There might be peices of code that would be affected if the encryptionService().decryptFile() function itself started deleting the decrypted files?

I will commit the pr including the Synchronizer and all small change but excluding the encryptionService().decryptFile() one, will change it later if it is not what you want.

@coderabbitai coderabbitai Bot added bug It's a bug mobile All mobile platforms desktop All desktop platforms cli CLI app specific issue sync sync related issue labels Apr 30, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/lib/Synchronizer.ts`:
- Around line 777-780: Make cleanup non-fatal and use a deterministic prefix
check: instead of using localResourceContentPath.includes('/encryptionCache/'),
normalize the path and check that it starts with the actual temp
encryption-cache directory (e.g. build a tempDirPrefix from
shim.fsDriver().tempDirPath() + path.sep + 'encryptionCache' and use
path.normalize and string.startsWith(tempDirPrefix)). Wrap the await
shim.fsDriver().remove(localResourceContentPath) call in a try/catch and swallow
errors, emitting a warning (logger.warn or console.warn) rather than letting the
error bubble and fail sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0912e59d-bb28-4a4e-9e90-19f9e22deb04

📥 Commits

Reviewing files that changed from the base of the PR and between 2341bc8 and fc4e1ff.

📒 Files selected for processing (6)
  • packages/app-cli/app/app.ts
  • packages/app-desktop/app.ts
  • packages/app-mobile/utils/buildStartupTasks.ts
  • packages/lib/Synchronizer.ts
  • packages/lib/models/Resource.test.ts
  • packages/lib/models/Resource.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/lib/models/Resource.test.ts
  • packages/app-mobile/utils/buildStartupTasks.ts

Comment thread packages/lib/Synchronizer.ts
@coderabbitai coderabbitai Bot removed bug It's a bug mobile All mobile platforms desktop All desktop platforms cli CLI app specific issue sync sync related issue labels Apr 30, 2026
const tempDir = Setting.value('tempDir');
const encryptionCacheDir = `${tempDir}/encryptionCache/`;

if (localResourceContentPath && localResourceContentPath.startsWith(encryptionCacheDir)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this cleanup may need to wrap the temp path rather than only the put. fullPathForSyncUpload() can create the encryptionCache file before we know if the blob needs uploading, but this only removes it after a successful put. So if the upload is skipped, or put fails, the temp file stays until next startup. Maybe a finally around that temp path would be safer?

Copy link
Copy Markdown
Collaborator

@mrjo118 mrjo118 May 1, 2026

Choose a reason for hiding this comment

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

Are you suggesting to delete the whole encryption cache here, rather than just the individual item? I don't think that would work, because items can be preuploaded (including doing the encryption) before looping the items to be uploaded

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah got you, i meant keeping it per file, only for this same localResourceContentPath, not clearing the whole encryptionCache dir.

So other preuploaded entries would stay as they are, was only thinking about the case where this temp file gets created, then put is skipped or fails.

if (await this.fsDriver().exists(encryptedPath)) {
// The file was successfully decrypted into plaintext.
// We must delete the leftover .crypted file from the main resource directory immediately.
await this.fsDriver().remove(encryptedPath);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this cleanup should happen after the save below? If encryptedPath is removed here and the save fails/crashes before the DB update, the resource can still look encrypted but no longer have the .crypted file to retry from.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delete .crypted files when the app starts

4 participants