From 0ebeb5aa92cfdeefc7dfd43e94ef1198fe425df9 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 8 Jun 2026 11:54:38 -0400 Subject: [PATCH] Restrict S3 downloads to ReleaseChannel. --- .../contactshare/ContactModelMapper.java | 9 +++++++-- .../securesms/jobs/AttachmentDownloadJob.kt | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java b/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java index 8ca512b920..0293a3a759 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java @@ -5,6 +5,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.Cdn; import org.thoughtcrime.securesms.attachments.PointerAttachment; import org.whispersystems.signalservice.api.InvalidMessageStructureException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; @@ -119,9 +120,13 @@ public class ContactModelMapper { try { SignalServiceAttachmentPointer attachmentPointer = AttachmentPointerUtil.createSignalAttachmentPointer(contact.avatar.avatar); Attachment attachment = PointerAttachment.forPointer(Optional.of(attachmentPointer.asPointer())).get(); - boolean isProfile = Boolean.TRUE.equals(contact.avatar.isProfile); - avatar = new Avatar(null, attachment, isProfile); + if (attachment.cdn == Cdn.S3) { + Log.w(TAG, "Ignoring contact avatar that resolves to the internal release-channel CDN."); + } else { + boolean isProfile = Boolean.TRUE.equals(contact.avatar.isProfile); + avatar = new Avatar(null, attachment, isProfile); + } } catch (InvalidMessageStructureException e) { Log.w(TAG, "Unable to create avatar for contact", e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt index 1401390f4a..abe179ae68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.kt @@ -235,7 +235,14 @@ class AttachmentDownloadJob private constructor( SignalDatabase.attachments.setTransferState(messageId, attachmentId, AttachmentTable.TRANSFER_PROGRESS_STARTED) when (attachment.cdn) { - Cdn.S3 -> retrieveAttachmentForReleaseChannel(messageId, attachmentId, attachment) + Cdn.S3 -> { + if (!isReleaseChannelMessage(messageId)) { + Log.w(TAG, "Refusing to download an S3 attachment for a message that is not from the release channel.") + markPermanentlyFailed(messageId, attachmentId) + return + } + retrieveAttachmentForReleaseChannel(messageId, attachmentId, attachment) + } else -> retrieveAttachment(messageId, attachmentId, attachment) } @@ -467,6 +474,12 @@ class AttachmentDownloadJob private constructor( } } + private fun isReleaseChannelMessage(messageId: Long): Boolean { + val releaseChannelRecipientId = SignalStore.releaseChannel.releaseChannelRecipientId ?: return false + val messageRecord = SignalDatabase.messages.getMessageRecordOrNull(messageId) ?: return false + return messageRecord.fromRecipient.id == releaseChannelRecipientId + } + private fun markFailed(messageId: Long, attachmentId: AttachmentId) { SignalDatabase.attachments.setTransferProgressFailed(attachmentId, messageId) }