better url risk scorer, icon, language, views reduced.

This commit is contained in:
Hadrian Burkhardt
2026-05-08 18:09:57 +02:00
parent a0646273bc
commit 4c443a0b86
55 changed files with 879 additions and 321 deletions
@@ -1,4 +1,4 @@
package com.clean.scanner.testutil
package de.softwareapp_hb.privateqrscanner.testutil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -1,7 +1,7 @@
package com.clean.scanner.ui
package de.softwareapp_hb.privateqrscanner.ui
import com.clean.scanner.domain.ScanResult
import com.clean.scanner.testutil.MainDispatcherRule
import de.softwareapp_hb.privateqrscanner.domain.ScanResult
import de.softwareapp_hb.privateqrscanner.testutil.MainDispatcherRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
@@ -1,6 +1,6 @@
package com.clean.scanner.util
package de.softwareapp_hb.privateqrscanner.util
import com.clean.scanner.domain.ScanRecord
import de.softwareapp_hb.privateqrscanner.domain.ScanRecord
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -1,4 +1,4 @@
package com.clean.scanner.util
package de.softwareapp_hb.privateqrscanner.util
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -1,5 +1,6 @@
package com.clean.scanner.util
package de.softwareapp_hb.privateqrscanner.util
import de.softwareapp_hb.privateqrscanner.domain.UrlRiskResult
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -20,14 +21,22 @@ class UrlRiskScorerTest {
@Test
fun `ip host adds two points`() {
val result = UrlRiskScorer.score("https://192.168.1.1/path")
val result = UrlRiskScorer.score("https://93.184.216.34/path")
assertEquals(2, result.score)
}
@Test
fun `private ip host adds extra risk`() {
val result = UrlRiskScorer.score("https://192.168.1.1/path")
assertAtLeast(4, result.score)
assertReasonContains(result, "private or reserved IP")
}
@Test
fun `punycode host adds two points`() {
val result = UrlRiskScorer.score("https://xn--pple-43d.com")
assertEquals(2, result.score)
assertAtLeast(2, result.score)
assertReasonContains(result, "punycode")
}
@Test
@@ -56,6 +65,60 @@ class UrlRiskScorerTest {
assertEquals(2, result.score)
}
@Test
fun `url shortener adds risk`() {
val result = UrlRiskScorer.score("https://bit.ly/privateqr")
assertAtLeast(2, result.score)
assertReasonContains(result, "shortener")
}
@Test
fun `non web scheme is high risk`() {
val result = UrlRiskScorer.score("javascript:alert(1)")
assertAtLeast(5, result.score)
assertReasonContains(result, "unsafe URL scheme")
}
@Test
fun `official brand login stays below warning threshold`() {
val result = UrlRiskScorer.score("https://accounts.google.com/login")
assertTrue(result.score < 3)
}
@Test
fun `regional brand domain does not trigger impersonation heuristic`() {
val result = UrlRiskScorer.score("https://google.de/login")
assertTrue(result.score < 3)
}
@Test
fun `brand embedded in unofficial host is risky`() {
val result = UrlRiskScorer.score("https://paypal-security.example.com/login")
assertAtLeast(3, result.score)
assertReasonContains(result, "known brand")
}
@Test
fun `lookalike brand host is risky`() {
val result = UrlRiskScorer.score("https://paypa1.com")
assertAtLeast(3, result.score)
assertReasonContains(result, "resembles a known brand")
}
@Test
fun `external redirect parameter is risky`() {
val result = UrlRiskScorer.score("https://example.com/login?redirect=https%3A%2F%2Fevil.test")
assertAtLeast(3, result.score)
assertReasonContains(result, "redirects to another domain")
}
@Test
fun `downloadable executable path is risky`() {
val result = UrlRiskScorer.score("https://example.com/security-update.apk")
assertAtLeast(3, result.score)
assertReasonContains(result, "executable file")
}
@Test
fun `combined risk can exceed threshold`() {
val result = UrlRiskScorer.score("http://user:pass@192.168.0.1")
@@ -73,4 +136,15 @@ class UrlRiskScorerTest {
val result = UrlRiskScorer.score("http://xn--pple-43d.com")
assertTrue(result.reasons.isNotEmpty())
}
private fun assertAtLeast(expectedMinimum: Int, actual: Int) {
assertTrue("Expected score >= $expectedMinimum but was $actual", actual >= expectedMinimum)
}
private fun assertReasonContains(result: UrlRiskResult, text: String) {
assertTrue(
"Expected reasons to contain '$text' but were ${result.reasons}",
result.reasons.any { it.contains(text, ignoreCase = true) }
)
}
}