Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,21 @@ data class JellyfinCredentials(

// Warn about cleartext HTTP on public hosts
if (!parsed.isHttps) {
val host = parsed.host
val isPrivate = host == "localhost" ||
host == "127.0.0.1" ||
host.endsWith(".local") ||
CloudStreamSecurity.isPrivateIpv4Literal(host)
if (!isPrivate) {
val host = parsed.host.lowercase()
if (!isHttpAllowedHost(host)) {
return "Use https:// for remote Jellyfin servers. HTTP is only allowed for local network addresses."
}
}

return null
}

private fun isHttpAllowedHost(host: String): Boolean {
return host == "localhost" ||
host == "127.0.0.1" ||
host.endsWith(".local") ||
host.endsWith(".ts.net") ||
!host.contains('.') ||
CloudStreamSecurity.isPrivateIpv4Literal(host)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ object CloudStreamSecurity {
return first == 0 ||
first == 10 ||
first == 127 ||
(first == 100 && second in 64..127) ||
(first == 169 && second == 254) ||
(first == 172 && second in 16..31) ||
(first == 192 && second == 168)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ class JellyfinCredentialsTest {

@Test
fun `http is allowed on local network addresses`() {
// localhost, loopback, private RFC1918 ranges, and .local hosts may be HTTP-only.
// localhost, loopback, private RFC1918 ranges, Tailscale, and local DNS names may be HTTP-only.
listOf(
"http://localhost:8096",
"http://127.0.0.1:8096",
"http://192.168.1.50:8096",
"http://10.0.0.5",
"http://172.16.4.4",
"http://100.64.12.34:8096",
"http://musicbox:8096",
"http://media.local",
"http://jellyfin.tailnet.ts.net:8096",
).forEach { url ->
assertNull(creds(url).connectionValidationError(), "expected $url to be allowed over http")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,23 @@ class CloudStreamSecurityIdTest {

@Test
fun `private and loopback ipv4 literals are detected`() {
listOf("10.0.0.1", "192.168.1.1", "172.16.0.1", "172.31.255.255", "127.0.0.1", "169.254.1.1", "0.0.0.0")
listOf(
"10.0.0.1",
"192.168.1.1",
"172.16.0.1",
"172.31.255.255",
"127.0.0.1",
"169.254.1.1",
"0.0.0.0",
"100.64.0.1",
"100.127.255.254",
)
.forEach { assertTrue(CloudStreamSecurity.isPrivateIpv4Literal(it), "$it should be private") }
}

@Test
fun `public ipv4 literals and non-ip hosts are not flagged private`() {
listOf("8.8.8.8", "1.2.3.4", "172.15.0.1", "172.32.0.1", "203.0.113.7")
listOf("8.8.8.8", "1.2.3.4", "172.15.0.1", "172.32.0.1", "203.0.113.7", "100.63.255.255", "100.128.0.1")
.forEach { assertFalse(CloudStreamSecurity.isPrivateIpv4Literal(it), "$it should be public") }
// Not dotted-quad IPv4 at all.
assertFalse(CloudStreamSecurity.isPrivateIpv4Literal("example.com"))
Expand Down