fix(security): address medium findings for ACL, limits, and UX

Tighten approve-endpoint to active shares with TTL sessions, reject non-file
FDs, lower default ticket/approval/size caps, show endpoint IDs and Public-mode
warnings, harden Android receive path checks, and run cargo-audit in CI.
This commit is contained in:
2026-07-13 18:54:28 +02:00
parent e7fb0331b5
commit d0c8d8774a
18 changed files with 184 additions and 14 deletions

View File

@@ -225,8 +225,7 @@ private class AndroidMediaStoreDownloadsSink(
"MediaStore Downloads requires Android 10 or newer"
}
check(relativePath !in pending) { "Output stream is already open for $relativePath" }
val parts = relativePath.split('/').filter { it.isNotBlank() }
require(parts.isNotEmpty()) { "relative path must not be empty" }
val parts = requireSafeRelativePathParts(relativePath)
val finalName = parts.last()
val relativeDir = mediaStoreRelativePath(parts.dropLast(1))
check(!mediaStoreItemExists(finalName, relativeDir)) {
@@ -346,6 +345,8 @@ private class AndroidTreeReceiveOutputSink(
override fun startFile(relativePath: String) {
check(relativePath !in pending) { "Output stream is already open for $relativePath" }
// Defense in depth: Rust also validates, but sinks must reject traversal alone.
requireSafeRelativePathParts(relativePath)
val (parent, finalName) = resolveParent(relativePath)
check(findChild(parent, finalName) == null) { "Destination already exists: $relativePath" }
val temporaryName = ".$finalName.vnidrop-${UUID.randomUUID()}.part"
@@ -391,8 +392,7 @@ private class AndroidTreeReceiveOutputSink(
}
private fun resolveParent(relativePath: String): Pair<Uri, String> {
val parts = relativePath.split('/').filter { it.isNotBlank() }
require(parts.isNotEmpty()) { "relative path must not be empty" }
val parts = requireSafeRelativePathParts(relativePath)
var parent = DocumentsContract.buildDocumentUriUsingTree(
treeUri,
DocumentsContract.getTreeDocumentId(treeUri),
@@ -428,3 +428,18 @@ private class AndroidTreeReceiveOutputSink(
return null
}
}
/**
* Split a receive relative path and reject traversal / absolute-style components.
* Rust already validates; this keeps Android sinks safe if called incorrectly.
*/
private fun requireSafeRelativePathParts(relativePath: String): List<String> {
val parts = relativePath.split('/').filter { it.isNotBlank() }
require(parts.isNotEmpty()) { "relative path must not be empty" }
require(parts.none { part ->
part == "." || part == ".." || part.contains('\\') || part.contains('\u0000')
}) {
"relative path contains invalid components: $relativePath"
}
return parts
}