Skip to content

Commit f53d4f2

Browse files
committed
v6.3.0: Security hardening audit, preferences refactor, DB indices, error handling
Security: PBKDF2 PIN hashing (auto-migrates SHA-256), encrypted backups (AES-256-GCM), DoH fail-closed (removed unpinned fallback), DoT response boundary check, HTTPS-only sync URLs with 10MB limit + SHA-256 integrity hashing, RootUtil shell injection fixes (quoted paths, removed sed), WireGuard nonce randomization, GeoIP switched to ipapi.co (HTTPS). Refactoring: AppPreferences facade over 6 domain-specific managers, DnsVpnService packet parsing extracted to PacketClassifier, BlocklistHolder unified trie walk, SettingsScreen decomposed into section composables, SettingsViewModel consolidated flows. Database: v12->v14 migrations adding composite indices for dns_logs, host_sources, user_rules (faster per-app drill-down, category filtering). UI: Error/loading states on Logs/Firewall/Sources screens, search history chips on Home, accessibility content descriptions on AppsScreen. Bug fixes: BootReceiver SupervisorJob lifecycle, BlockNotificationService scope recreation, captive portal HTTP documentation.
1 parent d9631eb commit f53d4f2

34 files changed

Lines changed: 1442 additions & 2804 deletions

app/app/build.gradle.kts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// HostShield v6.2.0
1+
// HostShield v6.3.0
22
plugins {
33
id("com.android.application")
44
id("org.jetbrains.kotlin.android")
@@ -15,8 +15,8 @@ android {
1515
applicationId = "com.hostshield"
1616
minSdk = 26
1717
targetSdk = 35
18-
versionCode = 55
19-
versionName = "6.2.0"
18+
versionCode = 56
19+
versionName = "6.3.0"
2020

2121
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2222

@@ -145,6 +145,9 @@ dependencies {
145145
// DataStore for preferences
146146
implementation("androidx.datastore:datastore-preferences:1.1.1")
147147

148+
// AndroidX Security — EncryptedSharedPreferences (Roadmap #30)
149+
implementation("androidx.security:security-crypto:1.1.0-alpha06")
150+
148151
// Custom Tabs (captive portal login)
149152
implementation("androidx.browser:browser:1.8.0")
150153

app/app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@
2323

2424
<application
2525
android:name=".HostShieldApp"
26-
android:allowBackup="true"
26+
android:allowBackup="false"
27+
android:dataExtractionRules="@xml/data_extraction_rules"
2728
android:icon="@mipmap/ic_launcher"
2829
android:label="HostShield"
2930
android:networkSecurityConfig="@xml/network_security_config"
3031
android:supportsRtl="true"
3132
android:theme="@style/Theme.HostShield"
32-
android:usesCleartextTraffic="true"
33+
android:usesCleartextTraffic="false"
3334
tools:targetApi="35">
3435

3536
<activity
@@ -85,7 +86,8 @@
8586
<receiver
8687
android:name=".service.BootReceiver"
8788
android:exported="true"
88-
android:enabled="true">
89+
android:enabled="true"
90+
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
8991
<intent-filter>
9092
<action android:name="android.intent.action.BOOT_COMPLETED" />
9193
<action android:name="android.intent.action.QUICKBOOT_POWERON" />

app/app/src/main/java/com/hostshield/HostShieldApp.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.hostshield
33
import android.app.Application
44
import androidx.hilt.work.HiltWorkerFactory
55
import androidx.work.Configuration
6+
import com.hostshield.data.preferences.SecurityPreferences
7+
import com.hostshield.data.preferences.SyncPreferences
68
import com.hostshield.service.CnameCloakUpdater
79
import com.hostshield.service.ThreatIntelWorker
810
import com.hostshield.util.OfflineGeoIp
@@ -20,6 +22,8 @@ class HostShieldApp : Application(), Configuration.Provider {
2022
@Inject lateinit var workerFactory: HiltWorkerFactory
2123
@Inject lateinit var cnameCloakUpdater: CnameCloakUpdater
2224
@Inject lateinit var offlineGeoIp: OfflineGeoIp
25+
@Inject lateinit var securityPreferences: SecurityPreferences
26+
@Inject lateinit var syncPreferences: SyncPreferences
2327

2428
private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
2529

@@ -29,6 +33,12 @@ class HostShieldApp : Application(), Configuration.Provider {
2933
appScope.launch { cnameCloakUpdater.loadCached() }
3034
appScope.launch { offlineGeoIp.initialize() }
3135

36+
// v6.2: Migrate plaintext secrets to EncryptedSharedPreferences (Roadmap #30)
37+
appScope.launch {
38+
securityPreferences.migratePlaintextSecrets()
39+
syncPreferences.migratePlaintextSecrets()
40+
}
41+
3242
// v6.0: Schedule daily threat intelligence feed updates
3343
try { ThreatIntelWorker.schedule(this) }
3444
catch (e: Exception) { android.util.Log.w("HostShieldApp", "WorkManager scheduling failed: ${e.message}") }

app/app/src/main/java/com/hostshield/data/database/Daos.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ interface DnsLogDao {
172172
suspend fun deleteOlderThan(before: Long)
173173

174174
/** Batch delete for ANR-safe cleanup. Returns number of rows deleted. */
175+
@Transaction
175176
@Query("DELETE FROM dns_logs WHERE id IN (SELECT id FROM dns_logs WHERE timestamp < :before LIMIT :batchSize)")
176177
suspend fun deleteOldestBatch(before: Long, batchSize: Int = 1000): Int
177178

@@ -334,6 +335,12 @@ interface ProfileDao {
334335

335336
@Query("UPDATE profiles SET is_active = 1 WHERE id = :id")
336337
suspend fun activate(id: Long)
338+
339+
@Transaction
340+
suspend fun activateExclusive(id: Long) {
341+
deactivateAll()
342+
activate(id)
343+
}
337344
}
338345

339346
// Projection classes

app/app/src/main/java/com/hostshield/data/database/HostShieldDatabase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import com.hostshield.data.model.*
1919
AutomationAuditEntry::class,
2020
VpnStabilityEntry::class
2121
],
22-
version = 12,
22+
version = 14,
2323
exportSchema = true
2424
)
2525
@TypeConverters(Converters::class)

app/app/src/main/java/com/hostshield/data/database/Migrations.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
1717
* - v5: Added dns_logs.query_type + dns_logs indices + source health columns
1818
* - v6: Added dns_logs.response_time_ms, dns_logs.upstream_server,
1919
* dns_logs.cname_chain columns for per-query detail view
20+
* - v13: Composite indices for common query patterns
21+
* - v14: Added index on host_sources.category
2022
*/
2123
object Migrations {
2224

@@ -131,6 +133,24 @@ object Migrations {
131133
}
132134
}
133135

136+
// v6.2.1: Add composite indices for common query patterns
137+
val MIGRATION_12_13 = object : Migration(12, 13) {
138+
override fun migrate(db: SupportSQLiteDatabase) {
139+
// Composite index for per-app DNS log drill-down (AppLogsScreen)
140+
db.execSQL("CREATE INDEX IF NOT EXISTS index_dns_logs_app_blocked_ts ON dns_logs (app_package, blocked, timestamp)")
141+
// Index on enabled for source/rule filtering
142+
db.execSQL("CREATE INDEX IF NOT EXISTS index_host_sources_enabled ON host_sources (enabled)")
143+
db.execSQL("CREATE INDEX IF NOT EXISTS index_user_rules_enabled_type ON user_rules (enabled, type)")
144+
}
145+
}
146+
147+
// v6.2.2: Add index on host_sources.category for category-filtered queries
148+
val MIGRATION_13_14 = object : Migration(13, 14) {
149+
override fun migrate(db: SupportSQLiteDatabase) {
150+
db.execSQL("CREATE INDEX IF NOT EXISTS index_host_sources_category ON host_sources (category)")
151+
}
152+
}
153+
134154
/** All migrations in order. Pass to Room.databaseBuilder().addMigrations(). */
135-
val ALL = arrayOf(MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12)
155+
val ALL = arrayOf(MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12, MIGRATION_12_13, MIGRATION_13_14)
136156
}

app/app/src/main/java/com/hostshield/data/model/Entities.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ enum class SourceHealth {
2020
UNKNOWN, OK, STALE, ERROR, DEAD
2121
}
2222

23-
@Entity(tableName = "host_sources")
23+
@Entity(
24+
tableName = "host_sources",
25+
indices = [
26+
Index(value = ["enabled"]),
27+
Index(value = ["category"])
28+
]
29+
)
2430
data class HostSource(
2531
@PrimaryKey(autoGenerate = true) val id: Long = 0,
2632
@ColumnInfo(name = "url") val url: String,
@@ -44,7 +50,10 @@ data class HostSource(
4450

4551
@Entity(
4652
tableName = "user_rules",
47-
indices = [Index(value = ["hostname"], unique = true)]
53+
indices = [
54+
Index(value = ["hostname"], unique = true),
55+
Index(value = ["enabled", "type"])
56+
]
4857
)
4958
data class UserRule(
5059
@PrimaryKey(autoGenerate = true) val id: Long = 0,
@@ -64,7 +73,8 @@ data class UserRule(
6473
Index(value = ["timestamp"]),
6574
Index(value = ["blocked", "timestamp"]), // composite for filtered log queries
6675
Index(value = ["hostname"]),
67-
Index(value = ["app_package"])
76+
Index(value = ["app_package"]),
77+
Index(value = ["app_package", "blocked", "timestamp"]) // composite for per-app drill-down
6878
]
6979
)
7080
data class DnsLogEntry(

0 commit comments

Comments
 (0)