|
16 | 16 |
|
17 | 17 | --- |
18 | 18 |
|
19 | | -## Hallazgos resueltos — Segunda auditoría 2026-07-29 |
20 | | - |
21 | | -### N-1 · `HelmMusicService` exportado sin permiso de bind ✅ resuelto en 9f947a3 |
22 | | -**Archivos:** `app/src/main/AndroidManifest.xml:84-91` · `audio/src/main/kotlin/dev/helm/audio/HelmMusicService.kt:41-45` |
23 | | -**Severidad:** Alta |
24 | | - |
25 | | -`HelmMusicService` tiene `android:exported="true"` sin `android:permission` en el `<service>`. El guard en `onGetSession` compara `controllerInfo.packageName == packageName`, pero ese valor lo suministra el caller vía `connectionHints` — no está verificado por el sistema. Cualquier app puede hacer bind y controlar la reproducción o inyectar URIs al queue de ExoPlayer. |
26 | | - |
27 | | -**Fix:** |
28 | | -```xml |
29 | | -<service |
30 | | - android:name=".HelmMusicService" |
31 | | - android:permission="android.permission.MEDIA_CONTENT_CONTROL" |
32 | | - android:exported="true"> |
33 | | -``` |
34 | | -En `onGetSession`: confiar solo en `controllerInfo.isTrusted`, eliminar la comparación por `packageName`. |
35 | | - |
36 | | ---- |
37 | | - |
38 | | -### N-2 · OTA — URL de descarga sin validación de host/scheme ✅ resuelto en 36e5b05 |
39 | | -**Archivo:** `ota/src/main/kotlin/dev/helm/ota/OtaRepository.kt:45-56` |
40 | | -**Severidad:** Alta |
41 | | - |
42 | | -`browser_download_url` de la respuesta JSON de GitHub se pasa verbatim a `URL(apkUrl).openConnection()` sin verificar scheme ni host. `HttpURLConnection` sigue redirects por defecto. Con cuenta de GitHub comprometida o DNS poisoning se puede redirigir la descarga a un servidor arbitrario. |
43 | | - |
44 | | -**Fix:** |
45 | | -```kotlin |
46 | | -val parsed = URL(apkUrl) |
47 | | -require(parsed.protocol == "https") { "Scheme inválido" } |
48 | | -require( |
49 | | - parsed.host.endsWith(".github.com") || |
50 | | - parsed.host.endsWith(".githubusercontent.com") |
51 | | -) { "Host no permitido" } |
52 | | -conn.instanceFollowRedirects = false |
53 | | -``` |
54 | | - |
55 | | ---- |
56 | | - |
57 | | -### N-3 · OTA — Comparación de firma APK con lógica `any/any` ✅ resuelto en 752900b |
58 | | -**Archivo:** `ota/src/main/kotlin/dev/helm/ota/OtaRepository.kt:102-104` |
59 | | -**Severidad:** Alta |
60 | | - |
61 | | -La verificación usa `apkCerts.any { apk -> installedCerts.any { ... } }` — pasa si el APK descargado comparte **al menos un** certificado con el instalado. Si se rota la clave de firma y la anterior queda comprometida, un APK firmado con esa clave vieja seguiría pasando el check. Agravado por N-2. |
62 | | - |
63 | | -**Fix:** |
64 | | -```kotlin |
65 | | -// Igualdad estricta de conjuntos |
66 | | -require( |
67 | | - apkCerts.map { it.toCharsString() }.toSet() == |
68 | | - installedCerts.map { it.toCharsString() }.toSet() |
69 | | -) |
70 | | -``` |
71 | | -Opción más robusta: comparar contra un fingerprint SHA-256 hardcodeado del cert de release, eliminando dependencia del runtime. |
72 | | - |
73 | | ---- |
74 | | - |
75 | 19 | ## Riesgos aceptados (pendiente futura iteración) |
76 | 20 |
|
77 | 21 | ### A-10 · Sin Certificate Pinning para `lrclib.net` |
|
0 commit comments