ilya terskov created JAMES-4234:
-----------------------------------

             Summary: BlobMailRepository: Isolate removeAll across mail 
repositories to prevent catastrophic data loss and cross-repository deletion
                 Key: JAMES-4234
                 URL: https://issues.apache.org/jira/browse/JAMES-4234
             Project: James Server
          Issue Type: Bug
          Components: Blob
    Affects Versions: master
            Reporter: ilya terskov


https://github.com/apache/james-project/pull/3213

Description
Context & Problem Statement
In BlobMailRepository, multiple mail repositories may share the same underlying 
BlobStore and bucket. Previously, several critical issues existed in repository 
isolation, cleanup, and life-cycle management:

Unbounded removeAll() Deletions (Cross-Repository Data Loss):
removeAll() called listBlobs() across the entire bucket without scoping to the 
repository's path prefix. As a result, invoking removeAll() on a single 
repository wiped out metadata and MIME blobs belonging to all other mail 
repositories sharing the same bucket.
Missing Ownership & Cross-Repository MIME Reference Protection:
Operations blindly trusted metadata and MIME blob references. If a corrupted, 
forged, or cross-referenced metadata entry pointed to MIME blobs belonging to a 
different repository, operations like remove() or removeAll() could delete 
foreign MIME data, and retrieve() could leak foreign emails.
MIME Blob Leaks on Mail Overwrite:
Calling store(mail) with an existing MailKey overwrote the metadata entry with 
new MIME blob IDs but left the superseded header and body MIME blobs orphaned 
in the blobstore.
Stream Leaks & Corrupted Metadata Resiliency:
Metadata decoding did not reliably close underlying streams via Using.resource. 
Furthermore, non-JSON or schema-mismatched blobs encountered during enumeration 
or retrieval could crash operations rather than being gracefully skipped.
Proposed Changes
1. Path-Scoped Listing & Verification (ownedMails and readOwned)
Scoped metadata blob listing to the repository's prefix using listBlobs(bucket, 
url.getPath.asString() + "/").
Implemented readOwned(id: MailPartsId) as a centralized gatekeeper for all 
operations (retrieve, remove, removeAll, store, list, size):
Validates that the metadata payload's mail name matches the expected blob ID 
(mailMetadataBlobIdFactory.of(mail.getName) == id.metadataBlobId).
Enforces isOwnedMimePart: verifies that both headerBlobId and bodyBlobId reside 
strictly under the repository's expected MIME path prefix 
(<repoPath>/mimeMessagedata/). Any foreign MIME references are rejected.
Gracefully ignores missing (ObjectNotFoundException), malformed 
(JsonParseException), or invalid schema (JsResultException) metadata entries.
2. Cross-Repository Isolation & Progress Tracking
Implemented removeAll(progressCallback: Consumer[MailKey]) and updated 
removeAll() to iterate exclusively over ownedMails(), deleting MIME parts 
followed by metadata blobs, and reporting deleted keys via callback.
Fixed size and list to count/iterate only valid, owned mails.
Updated remove(key) to delete blobs only after ownership and MIME prefix 
validation pass.
3. Cleanup of Superseded MIME Blobs on Overwrite
In store(mc: Mail), checked for existing owned metadata before persisting the 
new mail.
If overwriting an existing key, the superseded MIME blobs are asynchronously 
deleted with a backoff retry (Retry.backoff(2, Duration.ofMillis(50))). If 
deletion exhausts retries, a warning is logged with the specific blob IDs, 
preventing silent data leakage while keeping the primary store operation 
reliable.
4. Safe Resource Handling
Wrapped Store.CloseableByteSource stream decoding in Using.resource within 
MailMetadataDecoder to guarantee stream closure.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to