android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: root-detection
|
|
3
|
+
description: >
|
|
4
|
+
Root detection for Android security hardening.
|
|
5
|
+
Load this skill when detecting rooted devices, implementing multi-signal
|
|
6
|
+
root checks, deciding how to respond to rooted devices, or combining
|
|
7
|
+
root detection with other integrity checks.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Root Detection
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Root detection identifies devices where the Android security model has been compromised. A rooted device can bypass app-level protections, read encrypted storage, hook function calls, and tamper with app behavior. Root detection is one layer in a defense-in-depth strategy — no single check is foolproof.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- **Multiple signals** — no single check is reliable; combine several
|
|
20
|
+
- **Fail-secure** — if detection is uncertain, treat as rooted in high-security contexts
|
|
21
|
+
- **Graceful degradation** — decide per-feature whether to block, warn, or restrict
|
|
22
|
+
- **Obfuscate detection logic** — easy-to-read detection code is easy to bypass
|
|
23
|
+
- **Server-side validation** — combine with Play Integrity API for stronger guarantees
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Multi-Signal Root Detection
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
class RootDetector @Inject constructor(
|
|
31
|
+
@ApplicationContext private val context: Context
|
|
32
|
+
) {
|
|
33
|
+
data class RootCheckResult(
|
|
34
|
+
val isRooted: Boolean,
|
|
35
|
+
val signals: List<String> // which signals triggered
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
fun check(): RootCheckResult {
|
|
39
|
+
val signals = mutableListOf<String>()
|
|
40
|
+
|
|
41
|
+
if (checkSuBinary()) signals.add("su_binary")
|
|
42
|
+
if (checkRootApps()) signals.add("root_apps")
|
|
43
|
+
if (checkDangerousProps()) signals.add("dangerous_props")
|
|
44
|
+
if (checkRwPaths()) signals.add("rw_system_paths")
|
|
45
|
+
if (checkTestKeys()) signals.add("test_keys")
|
|
46
|
+
if (checkMagiskFiles()) signals.add("magisk_files")
|
|
47
|
+
|
|
48
|
+
return RootCheckResult(
|
|
49
|
+
isRooted = signals.isNotEmpty(),
|
|
50
|
+
signals = signals
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ✅ Check for su binary in common locations
|
|
55
|
+
private fun checkSuBinary(): Boolean {
|
|
56
|
+
val paths = listOf(
|
|
57
|
+
"/system/bin/su", "/system/xbin/su", "/sbin/su",
|
|
58
|
+
"/system/su", "/system/bin/.ext/.su", "/system/usr/we-need-root/su-backup",
|
|
59
|
+
"/data/local/su", "/data/local/bin/su", "/data/local/xbin/su"
|
|
60
|
+
)
|
|
61
|
+
return paths.any { File(it).exists() }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ✅ Check for known root management apps
|
|
65
|
+
private fun checkRootApps(): Boolean {
|
|
66
|
+
val rootPackages = listOf(
|
|
67
|
+
"com.topjohnwu.magisk",
|
|
68
|
+
"com.noshufou.android.su",
|
|
69
|
+
"com.koushikdutta.superuser",
|
|
70
|
+
"eu.chainfire.supersu",
|
|
71
|
+
"com.kingroot.kinguser",
|
|
72
|
+
"com.kingo.root",
|
|
73
|
+
"com.smedialink.oneclickroot"
|
|
74
|
+
)
|
|
75
|
+
return rootPackages.any { pkg ->
|
|
76
|
+
try {
|
|
77
|
+
context.packageManager.getPackageInfo(pkg, 0)
|
|
78
|
+
true
|
|
79
|
+
} catch (e: PackageManager.NameNotFoundException) {
|
|
80
|
+
false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ✅ Check dangerous build properties
|
|
86
|
+
private fun checkDangerousProps(): Boolean {
|
|
87
|
+
val dangerousProps = mapOf(
|
|
88
|
+
"ro.debuggable" to "1",
|
|
89
|
+
"ro.secure" to "0",
|
|
90
|
+
"ro.build.type" to "eng"
|
|
91
|
+
)
|
|
92
|
+
return dangerousProps.any { (prop, dangerousValue) ->
|
|
93
|
+
readSystemProperty(prop) == dangerousValue
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ✅ Check if /system is writable (should always be read-only)
|
|
98
|
+
private fun checkRwPaths(): Boolean {
|
|
99
|
+
val rwPaths = listOf("/system", "/system/bin", "/system/sbin", "/system/xbin", "/vendor/bin")
|
|
100
|
+
return rwPaths.any { path ->
|
|
101
|
+
try {
|
|
102
|
+
val file = File(path)
|
|
103
|
+
file.exists() && file.canWrite()
|
|
104
|
+
} catch (e: Exception) {
|
|
105
|
+
false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ✅ Check for test-keys build (non-official signing)
|
|
111
|
+
private fun checkTestKeys(): Boolean {
|
|
112
|
+
val buildTags = Build.TAGS
|
|
113
|
+
return buildTags != null && buildTags.contains("test-keys")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ✅ Check for Magisk-specific files
|
|
117
|
+
private fun checkMagiskFiles(): Boolean {
|
|
118
|
+
val magiskPaths = listOf(
|
|
119
|
+
"/sbin/.magisk", "/sbin/.core/mirror",
|
|
120
|
+
"/sbin/.core/img", "/data/adb/magisk"
|
|
121
|
+
)
|
|
122
|
+
return magiskPaths.any { File(it).exists() }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private fun readSystemProperty(name: String): String? = try {
|
|
126
|
+
val process = Runtime.getRuntime().exec(arrayOf("getprop", name))
|
|
127
|
+
process.inputStream.bufferedReader().readLine()?.trim()
|
|
128
|
+
} catch (e: Exception) {
|
|
129
|
+
null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Play Integrity API (Recommended for Production)
|
|
137
|
+
|
|
138
|
+
```kotlin
|
|
139
|
+
// ✅ Play Integrity API — server-side verification, harder to bypass
|
|
140
|
+
class IntegrityChecker @Inject constructor(
|
|
141
|
+
@ApplicationContext private val context: Context
|
|
142
|
+
) {
|
|
143
|
+
suspend fun checkIntegrity(nonce: String): Result<String> = runCatching {
|
|
144
|
+
val integrityManager = IntegrityManagerFactory.create(context)
|
|
145
|
+
val request = IntegrityTokenRequest.builder()
|
|
146
|
+
.setNonce(nonce)
|
|
147
|
+
.build()
|
|
148
|
+
|
|
149
|
+
val response = integrityManager.requestIntegrityToken(request).await()
|
|
150
|
+
response.token() // send this token to your server for verification
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Server verifies the token via Google Play Integrity API
|
|
155
|
+
// Token contains: deviceIntegrity, appIntegrity, accountDetails
|
|
156
|
+
// deviceIntegrity.deviceRecognitionVerdict includes MEETS_STRONG_INTEGRITY
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Response Strategy
|
|
162
|
+
|
|
163
|
+
```kotlin
|
|
164
|
+
// ✅ Decide response per security level
|
|
165
|
+
@HiltViewModel
|
|
166
|
+
class SecurityViewModel @Inject constructor(
|
|
167
|
+
private val rootDetector: RootDetector
|
|
168
|
+
) : ViewModel() {
|
|
169
|
+
|
|
170
|
+
sealed interface SecurityState {
|
|
171
|
+
data object Safe : SecurityState
|
|
172
|
+
data class Compromised(val signals: List<String>) : SecurityState
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
val securityState: StateFlow<SecurityState> = flow {
|
|
176
|
+
val result = rootDetector.check()
|
|
177
|
+
emit(
|
|
178
|
+
if (result.isRooted) SecurityState.Compromised(result.signals)
|
|
179
|
+
else SecurityState.Safe
|
|
180
|
+
)
|
|
181
|
+
}.stateIn(viewModelScope, SharingStarted.Eagerly, SecurityState.Safe)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ✅ UI response options (choose based on app sensitivity)
|
|
185
|
+
// Option 1: Block entirely (banking, health apps)
|
|
186
|
+
if (securityState is SecurityState.Compromised) {
|
|
187
|
+
RootedDeviceBlockScreen()
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Option 2: Warn and restrict features
|
|
192
|
+
if (securityState is SecurityState.Compromised) {
|
|
193
|
+
showWarning("Some features are unavailable on rooted devices")
|
|
194
|
+
disableSensitiveFeatures()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Option 3: Log and monitor (lower security apps)
|
|
198
|
+
if (securityState is SecurityState.Compromised) {
|
|
199
|
+
analytics.logEvent("rooted_device_detected", securityState.signals)
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Anti-Patterns
|
|
206
|
+
|
|
207
|
+
- Relying on a single check — root tools specifically bypass known single checks
|
|
208
|
+
- Blocking without user explanation — frustrates legitimate users (developers, testers)
|
|
209
|
+
- Performing root check on main thread — run on IO dispatcher
|
|
210
|
+
- Storing root check result in plain SharedPreferences — a rooted device can modify it
|
|
211
|
+
- Skipping server-side validation for high-security operations — client checks are bypassable
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Related Skills
|
|
216
|
+
- `hook-detection` — detecting Frida and Xposed hooks
|
|
217
|
+
- `frida-detection` — Frida-specific detection techniques
|
|
218
|
+
- `reverse-engineering-resistance` — broader anti-tampering measures
|
|
219
|
+
- `obfuscation` — making detection logic harder to analyze
|
|
220
|
+
- `certificate-pinning` — network-level security complement
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: secure-networking
|
|
3
|
+
description: >
|
|
4
|
+
Secure networking practices for Android apps.
|
|
5
|
+
Load this skill when configuring TLS, implementing certificate pinning,
|
|
6
|
+
setting up Network Security Config, handling authentication headers securely,
|
|
7
|
+
preventing cleartext traffic, or hardening OkHttp/Retrofit for production.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Secure Networking
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Secure networking ensures that data in transit is protected from interception and tampering. The key concerns are: enforcing TLS, pinning certificates against MITM attacks, protecting auth credentials in requests, and preventing accidental cleartext traffic.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- **TLS only** — no cleartext HTTP in production; enforce via Network Security Config
|
|
20
|
+
- **Certificate pinning** for high-security endpoints — prevents MITM even with compromised CAs
|
|
21
|
+
- **Auth tokens in headers** — never in URL query parameters (logged by servers/proxies)
|
|
22
|
+
- **Token refresh** handled transparently via OkHttp Authenticator
|
|
23
|
+
- **Sensitive headers stripped** on redirects — never forward auth to different domains
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Network Security Config
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- res/xml/network_security_config.xml -->
|
|
31
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
32
|
+
<network-security-config>
|
|
33
|
+
|
|
34
|
+
<!-- ✅ Block all cleartext traffic in production -->
|
|
35
|
+
<base-config cleartextTrafficPermitted="false">
|
|
36
|
+
<trust-anchors>
|
|
37
|
+
<certificates src="system" />
|
|
38
|
+
</trust-anchors>
|
|
39
|
+
</base-config>
|
|
40
|
+
|
|
41
|
+
<!-- ✅ Allow cleartext only for debug builds (localhost) -->
|
|
42
|
+
<debug-overrides>
|
|
43
|
+
<trust-anchors>
|
|
44
|
+
<certificates src="system" />
|
|
45
|
+
<certificates src="user" /> <!-- allows Charles/Burp on debug -->
|
|
46
|
+
</trust-anchors>
|
|
47
|
+
</debug-overrides>
|
|
48
|
+
|
|
49
|
+
</network-security-config>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```xml
|
|
53
|
+
<!-- AndroidManifest.xml -->
|
|
54
|
+
<application
|
|
55
|
+
android:networkSecurityConfig="@xml/network_security_config"
|
|
56
|
+
...>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## OkHttp Security Configuration
|
|
62
|
+
|
|
63
|
+
```kotlin
|
|
64
|
+
// ✅ Production OkHttpClient
|
|
65
|
+
fun buildSecureOkHttpClient(
|
|
66
|
+
authInterceptor: AuthInterceptor,
|
|
67
|
+
certificatePinner: CertificatePinner? = null
|
|
68
|
+
): OkHttpClient {
|
|
69
|
+
return OkHttpClient.Builder()
|
|
70
|
+
.connectTimeout(30, TimeUnit.SECONDS)
|
|
71
|
+
.readTimeout(30, TimeUnit.SECONDS)
|
|
72
|
+
.writeTimeout(30, TimeUnit.SECONDS)
|
|
73
|
+
.addInterceptor(authInterceptor)
|
|
74
|
+
.apply { certificatePinner?.let { certificatePinner(it) } }
|
|
75
|
+
.authenticator(TokenRefreshAuthenticator())
|
|
76
|
+
.build()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ✅ Auth interceptor — adds token to every request
|
|
80
|
+
class AuthInterceptor @Inject constructor(
|
|
81
|
+
private val securePreferences: SecurePreferences
|
|
82
|
+
) : Interceptor {
|
|
83
|
+
override fun intercept(chain: Interceptor.Chain): Response {
|
|
84
|
+
val token = securePreferences.authToken
|
|
85
|
+
val request = if (token != null) {
|
|
86
|
+
chain.request().newBuilder()
|
|
87
|
+
.header("Authorization", "Bearer $token")
|
|
88
|
+
.build()
|
|
89
|
+
} else {
|
|
90
|
+
chain.request()
|
|
91
|
+
}
|
|
92
|
+
return chain.proceed(request)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ✅ Token refresh on 401
|
|
97
|
+
class TokenRefreshAuthenticator @Inject constructor(
|
|
98
|
+
private val securePreferences: SecurePreferences,
|
|
99
|
+
private val authApi: AuthApiService
|
|
100
|
+
) : Authenticator {
|
|
101
|
+
override fun authenticate(route: Route?, response: Response): Request? {
|
|
102
|
+
// Prevent infinite loops
|
|
103
|
+
if (response.request.header("Authorization") == null) return null
|
|
104
|
+
if (responseCount(response) >= 2) return null
|
|
105
|
+
|
|
106
|
+
val refreshToken = securePreferences.refreshToken ?: return null
|
|
107
|
+
|
|
108
|
+
val newToken = runBlocking {
|
|
109
|
+
authApi.refreshToken(refreshToken).getOrNull()
|
|
110
|
+
} ?: return null
|
|
111
|
+
|
|
112
|
+
securePreferences.authToken = newToken.accessToken
|
|
113
|
+
securePreferences.refreshToken = newToken.refreshToken
|
|
114
|
+
|
|
115
|
+
return response.request.newBuilder()
|
|
116
|
+
.header("Authorization", "Bearer ${newToken.accessToken}")
|
|
117
|
+
.build()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private fun responseCount(response: Response): Int {
|
|
121
|
+
var count = 1
|
|
122
|
+
var prior = response.priorResponse
|
|
123
|
+
while (prior != null) { count++; prior = prior.priorResponse }
|
|
124
|
+
return count
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Certificate Pinning
|
|
132
|
+
|
|
133
|
+
```kotlin
|
|
134
|
+
// ✅ Certificate pinning via OkHttp
|
|
135
|
+
fun buildCertificatePinner(): CertificatePinner {
|
|
136
|
+
return CertificatePinner.Builder()
|
|
137
|
+
// Pin the leaf certificate + backup pin for rotation
|
|
138
|
+
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
|
|
139
|
+
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // backup
|
|
140
|
+
.build()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ✅ Getting the pin value for your certificate:
|
|
144
|
+
// $ openssl s_client -connect api.example.com:443 | openssl x509 -pubkey -noout |
|
|
145
|
+
// openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Logging Interceptor (Debug Only)
|
|
151
|
+
|
|
152
|
+
```kotlin
|
|
153
|
+
// ✅ Never log request bodies in release — may contain tokens or PII
|
|
154
|
+
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
|
155
|
+
level = if (BuildConfig.DEBUG) {
|
|
156
|
+
HttpLoggingInterceptor.Level.BODY
|
|
157
|
+
} else {
|
|
158
|
+
HttpLoggingInterceptor.Level.NONE // ✅ no logging in production
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ✅ Redact sensitive headers in logs
|
|
163
|
+
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
|
164
|
+
level = HttpLoggingInterceptor.Level.HEADERS
|
|
165
|
+
redactHeader("Authorization")
|
|
166
|
+
redactHeader("Cookie")
|
|
167
|
+
redactHeader("Set-Cookie")
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Hilt Setup
|
|
174
|
+
|
|
175
|
+
```kotlin
|
|
176
|
+
@Module
|
|
177
|
+
@InstallIn(SingletonComponent::class)
|
|
178
|
+
object NetworkModule {
|
|
179
|
+
|
|
180
|
+
@Provides
|
|
181
|
+
@Singleton
|
|
182
|
+
fun provideCertificatePinner(): CertificatePinner = buildCertificatePinner()
|
|
183
|
+
|
|
184
|
+
@Provides
|
|
185
|
+
@Singleton
|
|
186
|
+
fun provideOkHttpClient(
|
|
187
|
+
authInterceptor: AuthInterceptor,
|
|
188
|
+
certificatePinner: CertificatePinner
|
|
189
|
+
): OkHttpClient = buildSecureOkHttpClient(authInterceptor, certificatePinner)
|
|
190
|
+
|
|
191
|
+
@Provides
|
|
192
|
+
@Singleton
|
|
193
|
+
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit =
|
|
194
|
+
Retrofit.Builder()
|
|
195
|
+
.baseUrl(BuildConfig.API_BASE_URL)
|
|
196
|
+
.client(okHttpClient)
|
|
197
|
+
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
|
198
|
+
.build()
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Anti-Patterns
|
|
205
|
+
|
|
206
|
+
- Passing auth tokens as URL query params — visible in server logs, browser history, proxies
|
|
207
|
+
- `cleartextTrafficPermitted="true"` in production manifest — allows HTTP interception
|
|
208
|
+
- Logging request/response bodies in release — leaks tokens and PII to logcat
|
|
209
|
+
- No token refresh logic — forces users to re-login on every token expiry
|
|
210
|
+
- Trusting user-installed CAs in production — enables corporate proxy / MITM attacks
|
|
211
|
+
- Hard-coding API keys or secrets in source code — use BuildConfig from CI environment variables
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Related Skills
|
|
216
|
+
- `certificate-pinning` — in-depth pinning setup and rotation strategy
|
|
217
|
+
- `retrofit` — Retrofit configuration
|
|
218
|
+
- `okhttp` — OkHttp interceptors and configuration
|
|
219
|
+
- `authentication` — auth flow and token management
|
|
220
|
+
- `network-security-config` — XML-based network security policy
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: alarmmanager
|
|
3
|
+
description: >
|
|
4
|
+
AlarmManager for time-based task scheduling in Android.
|
|
5
|
+
Load this skill when scheduling exact-time alarms, implementing
|
|
6
|
+
reminder notifications, triggering actions at a specific time,
|
|
7
|
+
or handling cases where WorkManager's timing precision is insufficient.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# AlarmManager
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
AlarmManager schedules tasks to run at a specific time, even if the app is not running. It is the correct tool when **exact timing** matters — reminder alarms, calendar events, scheduled notifications. For flexible/deferrable work, WorkManager is preferred. Since Android 12, exact alarms require explicit permission.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Use AlarmManager only when **exact timing** is required — use WorkManager for flexible scheduling
|
|
20
|
+
- Request `SCHEDULE_EXACT_ALARM` or `USE_EXACT_ALARM` permission for exact alarms on Android 12+
|
|
21
|
+
- Always use `PendingIntent` with `FLAG_IMMUTABLE` — required on Android 12+
|
|
22
|
+
- Re-schedule alarms after device reboot via `BOOT_COMPLETED` receiver
|
|
23
|
+
- Cancel alarms when they are no longer needed — they persist across app restarts
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Permissions
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- AndroidManifest.xml -->
|
|
31
|
+
|
|
32
|
+
<!-- For exact alarms — user must grant in settings (Android 12+) -->
|
|
33
|
+
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
|
34
|
+
|
|
35
|
+
<!-- OR for specific use cases (clock, calendar apps) — always granted -->
|
|
36
|
+
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
|
37
|
+
|
|
38
|
+
<!-- For BroadcastReceiver to survive reboot -->
|
|
39
|
+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Scheduling an Exact Alarm
|
|
45
|
+
|
|
46
|
+
```kotlin
|
|
47
|
+
// ✅ Schedule exact alarm
|
|
48
|
+
class ReminderScheduler @Inject constructor(
|
|
49
|
+
@ApplicationContext private val context: Context
|
|
50
|
+
) {
|
|
51
|
+
private val alarmManager = context.getSystemService(AlarmManager::class.java)
|
|
52
|
+
|
|
53
|
+
fun scheduleReminder(reminderId: Long, triggerAtMillis: Long) {
|
|
54
|
+
val intent = Intent(context, ReminderReceiver::class.java).apply {
|
|
55
|
+
putExtra(ReminderReceiver.EXTRA_REMINDER_ID, reminderId)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
val pendingIntent = PendingIntent.getBroadcast(
|
|
59
|
+
context,
|
|
60
|
+
reminderId.toInt(),
|
|
61
|
+
intent,
|
|
62
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
66
|
+
if (alarmManager.canScheduleExactAlarms()) {
|
|
67
|
+
alarmManager.setExactAndAllowWhileIdle(
|
|
68
|
+
AlarmManager.RTC_WAKEUP,
|
|
69
|
+
triggerAtMillis,
|
|
70
|
+
pendingIntent
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
// else: redirect user to settings
|
|
74
|
+
} else {
|
|
75
|
+
alarmManager.setExactAndAllowWhileIdle(
|
|
76
|
+
AlarmManager.RTC_WAKEUP,
|
|
77
|
+
triggerAtMillis,
|
|
78
|
+
pendingIntent
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun cancelReminder(reminderId: Long) {
|
|
84
|
+
val intent = Intent(context, ReminderReceiver::class.java)
|
|
85
|
+
val pendingIntent = PendingIntent.getBroadcast(
|
|
86
|
+
context,
|
|
87
|
+
reminderId.toInt(),
|
|
88
|
+
intent,
|
|
89
|
+
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
|
|
90
|
+
)
|
|
91
|
+
pendingIntent?.let { alarmManager.cancel(it) }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## BroadcastReceiver
|
|
99
|
+
|
|
100
|
+
```kotlin
|
|
101
|
+
// ✅ Receiver triggered by alarm
|
|
102
|
+
class ReminderReceiver : BroadcastReceiver() {
|
|
103
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
104
|
+
val reminderId = intent.getLongExtra(EXTRA_REMINDER_ID, -1L)
|
|
105
|
+
if (reminderId == -1L) return
|
|
106
|
+
|
|
107
|
+
// Show notification — short work only in onReceive
|
|
108
|
+
NotificationHelper.showReminder(context, reminderId)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
companion object {
|
|
112
|
+
const val EXTRA_REMINDER_ID = "reminder_id"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Register in manifest
|
|
117
|
+
// <receiver android:name=".ReminderReceiver" android:exported="false" />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Reschedule After Reboot
|
|
123
|
+
|
|
124
|
+
```kotlin
|
|
125
|
+
// ✅ Reboot receiver — reschedules all pending alarms
|
|
126
|
+
class BootReceiver : BroadcastReceiver() {
|
|
127
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
128
|
+
if (intent.action != Intent.ACTION_BOOT_COMPLETED) return
|
|
129
|
+
|
|
130
|
+
// Reschedule all pending reminders from local DB
|
|
131
|
+
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
132
|
+
scope.launch {
|
|
133
|
+
val scheduler = ReminderScheduler(context)
|
|
134
|
+
reminderRepository.getPendingReminders().forEach { reminder ->
|
|
135
|
+
scheduler.scheduleReminder(reminder.id, reminder.triggerAt)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Register in manifest
|
|
142
|
+
// <receiver android:name=".BootReceiver" android:exported="true">
|
|
143
|
+
// <intent-filter>
|
|
144
|
+
// <action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
145
|
+
// </intent-filter>
|
|
146
|
+
// </receiver>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Directing User to Exact Alarm Settings
|
|
152
|
+
|
|
153
|
+
```kotlin
|
|
154
|
+
// ✅ On Android 12+ — direct user to grant exact alarm permission
|
|
155
|
+
fun requestExactAlarmPermission(activity: Activity) {
|
|
156
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
157
|
+
val alarmManager = activity.getSystemService(AlarmManager::class.java)
|
|
158
|
+
if (!alarmManager.canScheduleExactAlarms()) {
|
|
159
|
+
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
|
|
160
|
+
activity.startActivity(intent)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Anti-Patterns
|
|
169
|
+
|
|
170
|
+
- Using AlarmManager for network sync or deferrable tasks — use WorkManager
|
|
171
|
+
- Not rescheduling alarms after reboot — alarms are lost on device restart
|
|
172
|
+
- Using `FLAG_MUTABLE` for PendingIntent on Android 12+ — SecurityException
|
|
173
|
+
- Using `set()` instead of `setExactAndAllowWhileIdle()` — alarm delayed in Doze mode
|
|
174
|
+
- Doing heavy work in `onReceive` — it runs on the main thread; delegate to a service or WorkManager
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Related Skills
|
|
179
|
+
- `workmanager` — preferred for deferrable background work
|
|
180
|
+
- `notification` — showing notifications from the alarm receiver
|
|
181
|
+
- `broadcast-receiver` — BroadcastReceiver fundamentals
|
|
182
|
+
- `background-processing` — choosing the right scheduling tool
|