fix(security): harden provider ACL, tickets, and local secrets

Close high-severity findings from the security review: default-deny blob
gets for unmapped hashes, map collection members for ACL, fail closed on
unknown access modes, require vnd1 tickets only, redact tickets from events,
exclude Android app-data backups, and tighten secret-file creation.
This commit is contained in:
2026-07-13 18:41:43 +02:00
parent 98375f479a
commit e7fb0331b5
27 changed files with 461 additions and 283 deletions

View File

@@ -128,7 +128,7 @@
<string name="transfer_event_saving">Saving files</string>
<string name="transfer_event_connecting">Connecting to sender</string>
<string name="ticket_details_title">Ticket details</string>
<string name="ticket_no_metadata">This ticket does not include VniDrop metadata.</string>
<string name="settings_title">Settings</string>
<string name="settings_subtitle">Configure the local node and app appearance.</string>
<string name="node_title">Node</string>

View File

@@ -77,8 +77,7 @@ data class TransferMetadataModel(
data class TicketInspectionModel(
val kind: String,
val blobTicket: String,
val metadata: TransferMetadataModel?,
val metadata: TransferMetadataModel,
)
data class ReceiverRequestModel(

View File

@@ -335,8 +335,7 @@ private fun ShareResult.toModel(): Share = Share(
private fun TicketInspection.toModel(): TicketInspectionModel = TicketInspectionModel(
kind = kind,
blobTicket = blobTicket,
metadata = metadata?.toModel(),
metadata = metadata.toModel(),
)
private fun TransferMetadata.toModel(): TransferMetadataModel = TransferMetadataModel(

View File

@@ -255,8 +255,8 @@ private fun InvitationReviewPanel(
val metadata = inspection.metadata
Surface(shape = RoundedCornerShape(14.dp), color = LocalVniDropColors.current.backgroundSurface200) {
Column(Modifier.fillMaxWidth().padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(metadata?.transferName ?: stringResource(Res.string.receive_unknown_transfer), fontWeight = FontWeight.Bold, maxLines = 2, overflow = TextOverflow.Ellipsis)
if (metadata != null) Text("${metadata.fileCount} ${stringResource(Res.string.metadata_files).lowercase()} · ${formatBytes(metadata.totalSize)}", color = LocalVniDropColors.current.foregroundLighter)
Text(metadata.transferName, fontWeight = FontWeight.Bold, maxLines = 2, overflow = TextOverflow.Ellipsis)
Text("${metadata.fileCount} ${stringResource(Res.string.metadata_files).lowercase()} · ${formatBytes(metadata.totalSize)}", color = LocalVniDropColors.current.foregroundLighter)
}
}
Field(state.receiverName, onReceiverNameChanged, stringResource(Res.string.field_receiver_name))

View File

@@ -32,7 +32,6 @@ import vnidrop.shared.generated.resources.metadata_size
import vnidrop.shared.generated.resources.metadata_transfer
import vnidrop.shared.generated.resources.progress_title
import vnidrop.shared.generated.resources.ticket_details_title
import vnidrop.shared.generated.resources.ticket_no_metadata
import vnidrop.shared.generated.resources.unknown_sender
@Composable
@@ -60,15 +59,14 @@ fun ProgressSection(coreState: CoreState) {
@Composable
fun TicketInspectionCard(inspection: TicketInspectionModel) {
val metadata = inspection.metadata
AppCard(title = stringResource(Res.string.ticket_details_title)) {
MetadataRow(stringResource(Res.string.metadata_kind), inspection.kind)
inspection.metadata?.let { metadata ->
MetadataRow(stringResource(Res.string.metadata_transfer), metadata.transferName)
MetadataRow(stringResource(Res.string.metadata_sender), metadata.senderName ?: stringResource(Res.string.unknown_sender))
MetadataRow(stringResource(Res.string.metadata_files), metadata.fileCount.toString())
MetadataRow(stringResource(Res.string.metadata_size), formatBytes(metadata.totalSize))
MetadataRow(stringResource(Res.string.metadata_hash), metadata.contentHash)
} ?: EmptyText(stringResource(Res.string.ticket_no_metadata))
MetadataRow(stringResource(Res.string.metadata_transfer), metadata.transferName)
MetadataRow(stringResource(Res.string.metadata_sender), metadata.senderName ?: stringResource(Res.string.unknown_sender))
MetadataRow(stringResource(Res.string.metadata_files), metadata.fileCount.toString())
MetadataRow(stringResource(Res.string.metadata_size), formatBytes(metadata.totalSize))
MetadataRow(stringResource(Res.string.metadata_hash), metadata.contentHash)
}
}