Skip to content

encryption:disable blocked by leftover filecache.encrypted rows that decrypt-all never clears (versions/trashbin/shares) #41623

@DeepDiver1975

Description

@DeepDiver1975

Summary

occ encryption:decrypt-all reports success, but occ encryption:disable still fails with:

The system still has encrypted files. Please decrypt them all before disabling encryption.

Root cause is a scope mismatch between the two commands plus a misleading success message.

Reported by an admin on central: https://central.owncloud.org/t/unable-to-disable-encryption/94685 — and closely related to #39212.

Analysis

The disable gate (core/Command/Encryption/Disable.php) scans the entire filecache table:

SELECT 1 FROM filecache fc WHERE fc.encrypted >= 1 LIMIT 1

decrypt-all (lib/private/Encryption/DecryptAll.php) only ever clears the flag for files under one root:

  • decryptUsersFiles() seeds the walk from '/' . $uid . '/files' — and nothing else.
  • decryptFile() is the only place that resets the column: $storage->getCache()->put($internalPath, ['encrypted' => 0]).
  • Shared storages are skipped by design (instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')).

So any filecache row with encrypted >= 1 outside /$uid/files survives and permanently blocks disable:

  1. /$uid/files_versions/* (most common)
  2. /$uid/files_trashbin/*
  3. shared / external / object-storage entries (intentionally skipped)
  4. files that failed to decrypt — e.g. the dangling .decrypted.<timestamp>.part files in Enabled encryption, encrypted all files, corrupted uploads, can't disable encryption, dangling .part files everywhere that can't be deleted #39212

Bug 1 — scope mismatch

disable scans all of filecache; decrypt-all only walks /$uid/files. They disagree on what "all encrypted files" means.

Bug 2 — misleading success message

DecryptAll::decryptAll() prints all files could be decrypted successfully! whenever no exception bubbled up, even though versions/trashbin/shares were never visited and encrypted rows remain. The admin has no signal that anything was left behind.

Diagnostic for affected admins

SELECT fc.fileid, fc.path, fc.encrypted, s.id AS storage
FROM oc_filecache fc
JOIN oc_storages s ON fc.storage = s.numeric_id
WHERE fc.encrypted >= 1;

(Adjust the oc_ prefix.) The path column shows the blocking rows — typically files_versions/… / files_trashbin/….

Workaround

  1. occ trashbin:cleanup --all-users
  2. occ versions:cleanup
  3. re-run occ encryption:decrypt-all, then occ encryption:disable
  4. for shared/external leftovers, decrypt per-owner: occ encryption:decrypt-all <uid>

Proposed fix (for discussion)

  • Make encryption:disable report the blocking paths instead of a generic message, so the admin knows what is left.
  • Either have decrypt-all also clear the encrypted flag for versions/trashbin, or scope the disable check to the same paths decrypt-all actually clears.
  • Stop printing "all files could be decrypted successfully!" when storages/paths were skipped or $this->failed would have applied to untouched namespaces.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions