@cap-kit/integrity 8.0.0-next.6

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.
Files changed (59) hide show
  1. package/CapKitIntegrity.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +26 -0
  4. package/README.md +1104 -0
  5. package/android/build.gradle +104 -0
  6. package/android/src/main/AndroidManifest.xml +21 -0
  7. package/android/src/main/java/io/capkit/integrity/IntegrityCheckOptions.kt +37 -0
  8. package/android/src/main/java/io/capkit/integrity/IntegrityConfig.kt +59 -0
  9. package/android/src/main/java/io/capkit/integrity/IntegrityError.kt +40 -0
  10. package/android/src/main/java/io/capkit/integrity/IntegrityImpl.kt +319 -0
  11. package/android/src/main/java/io/capkit/integrity/IntegrityPlugin.kt +475 -0
  12. package/android/src/main/java/io/capkit/integrity/IntegrityReportBuilder.kt +130 -0
  13. package/android/src/main/java/io/capkit/integrity/IntegritySignalBuilder.kt +72 -0
  14. package/android/src/main/java/io/capkit/integrity/emulator/IntegrityEmulatorChecks.kt +38 -0
  15. package/android/src/main/java/io/capkit/integrity/filesystem/IntegrityFilesystemChecks.kt +51 -0
  16. package/android/src/main/java/io/capkit/integrity/hook/IntegrityHookChecks.kt +61 -0
  17. package/android/src/main/java/io/capkit/integrity/remote/IntegrityRemoteAttestor.kt +49 -0
  18. package/android/src/main/java/io/capkit/integrity/root/IntegrityRootDetector.kt +136 -0
  19. package/android/src/main/java/io/capkit/integrity/runtime/IntegrityRuntimeChecks.kt +87 -0
  20. package/android/src/main/java/io/capkit/integrity/ui/IntegrityBlockActivity.kt +173 -0
  21. package/android/src/main/java/io/capkit/integrity/ui/IntegrityUISignals.kt +57 -0
  22. package/android/src/main/java/io/capkit/integrity/utils/IntegrityLogger.kt +85 -0
  23. package/android/src/main/java/io/capkit/integrity/utils/IntegrityUtils.kt +105 -0
  24. package/android/src/main/res/.gitkeep +0 -0
  25. package/android/src/main/res/values/styles.xml +5 -0
  26. package/dist/docs.json +598 -0
  27. package/dist/esm/definitions.d.ts +554 -0
  28. package/dist/esm/definitions.js +56 -0
  29. package/dist/esm/definitions.js.map +1 -0
  30. package/dist/esm/index.d.ts +15 -0
  31. package/dist/esm/index.js +16 -0
  32. package/dist/esm/index.js.map +1 -0
  33. package/dist/esm/web.d.ts +32 -0
  34. package/dist/esm/web.js +51 -0
  35. package/dist/esm/web.js.map +1 -0
  36. package/dist/plugin.cjs.js +130 -0
  37. package/dist/plugin.cjs.js.map +1 -0
  38. package/dist/plugin.js +133 -0
  39. package/dist/plugin.js.map +1 -0
  40. package/ios/Sources/IntegrityPlugin/IntegrityCheckOptions.swift +41 -0
  41. package/ios/Sources/IntegrityPlugin/IntegrityConfig.swift +135 -0
  42. package/ios/Sources/IntegrityPlugin/IntegrityEntitlementChecks.swift +58 -0
  43. package/ios/Sources/IntegrityPlugin/IntegrityError.swift +49 -0
  44. package/ios/Sources/IntegrityPlugin/IntegrityImpl.swift +397 -0
  45. package/ios/Sources/IntegrityPlugin/IntegrityPlugin.swift +345 -0
  46. package/ios/Sources/IntegrityPlugin/IntegrityReportBuilder.swift +184 -0
  47. package/ios/Sources/IntegrityPlugin/Utils/IntegrityLogger.swift +69 -0
  48. package/ios/Sources/IntegrityPlugin/Utils/IntegrityUtils.swift +144 -0
  49. package/ios/Sources/IntegrityPlugin/Version.swift +16 -0
  50. package/ios/Sources/IntegrityPlugin/filesystem/IntegrityFilesystemChecks.swift +86 -0
  51. package/ios/Sources/IntegrityPlugin/hook/IntegrityHookChecks.swift +85 -0
  52. package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakDetector.swift +74 -0
  53. package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakUrlSchemeDetector.swift +42 -0
  54. package/ios/Sources/IntegrityPlugin/remote/IntegrityRemoteAttestor.swift +40 -0
  55. package/ios/Sources/IntegrityPlugin/runtime/IntegrityRuntimeChecks.swift +63 -0
  56. package/ios/Sources/IntegrityPlugin/simulator/IntegritySimulatorChecks.swift +20 -0
  57. package/ios/Sources/IntegrityPlugin/ui/IntegrityBlockViewController.swift +143 -0
  58. package/ios/Tests/IntegrityPluginTests/IntegrityPluginTests.swift +10 -0
  59. package/package.json +106 -0
@@ -0,0 +1,104 @@
1
+ buildscript {
2
+ ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.2.20'
3
+ repositories {
4
+ google()
5
+ mavenCentral()
6
+ }
7
+ dependencies {
8
+ classpath 'com.android.tools.build:gradle:8.13.0'
9
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
10
+ }
11
+ }
12
+
13
+ plugins {
14
+ id "org.jlleitschuh.gradle.ktlint" version "12.1.1" apply false
15
+ }
16
+
17
+ ext {
18
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
19
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
20
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
21
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
22
+ androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.17.0'
23
+ }
24
+
25
+ apply plugin: 'com.android.library'
26
+ apply plugin: 'kotlin-android'
27
+ apply plugin: 'kotlin-parcelize'
28
+ apply plugin: 'org.jlleitschuh.gradle.ktlint'
29
+
30
+ import groovy.json.JsonSlurper
31
+
32
+ def getPluginVersion() {
33
+ try {
34
+ def packageJsonFile = file('../package.json')
35
+ if (packageJsonFile.exists()) {
36
+ def packageJson = new JsonSlurper().parseText(packageJsonFile.text)
37
+ return packageJson.version
38
+ }
39
+ } catch (Exception e) {
40
+ // Ignore errors and fallback
41
+ logger.info("Exception", e)
42
+ }
43
+ return "8.0.0"
44
+ }
45
+
46
+ def pluginVersion = getPluginVersion()
47
+
48
+ android {
49
+ namespace = "io.capkit.integrity"
50
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion as Integer : 36
51
+
52
+ // AGP 8.0+ disables BuildConfig by default for libraries.
53
+ // We need to enable it to inject the plugin version.
54
+ buildFeatures {
55
+ buildConfig = true
56
+ }
57
+
58
+ defaultConfig {
59
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion as Integer : 24
60
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion as Integer : 36
61
+ versionCode 1
62
+
63
+ // Dynamic versioning (feature enabled)
64
+ versionName = pluginVersion
65
+
66
+ // Injects the version into the BuildConfig class ONLY if feature is enabled
67
+ buildConfigField "String", "PLUGIN_VERSION", "\"${pluginVersion}\""
68
+
69
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
70
+ }
71
+ buildTypes {
72
+ release {
73
+ minifyEnabled = false
74
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
75
+ }
76
+ }
77
+ lint {
78
+ abortOnError = false
79
+ }
80
+ compileOptions {
81
+ sourceCompatibility = JavaVersion.VERSION_21
82
+ targetCompatibility = JavaVersion.VERSION_21
83
+ }
84
+ kotlinOptions {
85
+ jvmTarget = JavaVersion.VERSION_21
86
+ }
87
+ }
88
+
89
+ repositories {
90
+ google()
91
+ mavenCentral()
92
+ }
93
+
94
+ dependencies {
95
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
96
+ implementation project(':capacitor-android')
97
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
98
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
99
+ implementation "androidx.core:core-ktx:$androidxCoreKTXVersion"
100
+
101
+ testImplementation "junit:junit:$junitVersion"
102
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
103
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
104
+ }
@@ -0,0 +1,21 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
3
+
4
+ <queries>
5
+ <package android:name="com.noshufou.android.su" />
6
+ <package android:name="com.thirdparty.superuser" />
7
+ <package android:name="eu.chainfire.supersu" />
8
+ <package android:name="com.koushikdutta.superuser" />
9
+ <package android:name="com.zachareew.systemuituner" />
10
+ <package android:name="com.topjohnwu.magisk" />
11
+ <package android:name="com.alephzain.framaroot" />
12
+ <package android:name="org.adaway" />
13
+ </queries>
14
+
15
+ <application>
16
+ <activity
17
+ android:name=".ui.IntegrityBlockActivity"
18
+ android:exported="false"
19
+ android:theme="@style/IntegrityBlockTheme" />
20
+ </application>
21
+ </manifest>
@@ -0,0 +1,37 @@
1
+ package io.capkit.integrity
2
+
3
+ /**
4
+ * Native representation of `Integrity.check()` options.
5
+ *
6
+ * This model mirrors the JavaScript options object and is used
7
+ * to control how integrity checks are executed.
8
+ *
9
+ * Design principles:
10
+ * - Independent from the Plugin (Bridge) layer
11
+ * - Safe to use inside the native Implementation layer
12
+ * - Derived exclusively from JS input
13
+ * - Does NOT alter the public JS API shape
14
+ *
15
+ * Notes:
16
+ * - Default values are applied in the Plugin layer
17
+ * - The Implementation layer assumes normalized values
18
+ */
19
+ data class IntegrityCheckOptions(
20
+ /**
21
+ * Desired strictness level for integrity checks.
22
+ *
23
+ * Supported values:
24
+ * - "basic": minimal checks (root, emulator)
25
+ * - "standard": adds debug and instrumentation checks
26
+ * - "strict": enables all available heuristics
27
+ */
28
+ val level: String,
29
+ /**
30
+ * Whether debug-only information should be included
31
+ * in returned integrity signals.
32
+ *
33
+ * When true, signals MAY contain a human-readable
34
+ * `description` field for diagnostic purposes.
35
+ */
36
+ val includeDebugInfo: Boolean,
37
+ )
@@ -0,0 +1,59 @@
1
+ package io.capkit.integrity
2
+
3
+ import android.content.Context
4
+ import com.getcapacitor.Plugin
5
+
6
+ /**
7
+ * Plugin configuration container.
8
+ *
9
+ * This class is responsible for reading and exposing
10
+ * static configuration values defined under the
11
+ * `Integrity` key in capacitor.config.ts.
12
+ *
13
+ * Configuration rules:
14
+ * - Read once during plugin initialization
15
+ * - Treated as immutable runtime input
16
+ * - Accessible only from native code
17
+ */
18
+ class IntegrityConfig(plugin: Plugin) {
19
+ /**
20
+ * Android application context.
21
+ * Exposed for native components that may require it.
22
+ */
23
+ val context: Context = plugin.context
24
+
25
+ /**
26
+ * Enables verbose native logging.
27
+ *
28
+ * When enabled, additional debug information
29
+ * is printed to Logcat.
30
+ *
31
+ * Default: false
32
+ */
33
+ val verboseLogging: Boolean
34
+
35
+ /**
36
+ *
37
+ * Default: false
38
+ */
39
+ val blockPageEnabled: Boolean
40
+
41
+ /**
42
+ *
43
+ */
44
+ val blockPageUrl: String?
45
+
46
+ init {
47
+ val config = plugin.getConfig()
48
+
49
+ // Verbose logging flag
50
+ verboseLogging =
51
+ config.getBoolean("verboseLogging", false)
52
+
53
+ val blockPage = config.getObject("blockPage")
54
+
55
+ blockPageEnabled = blockPage?.getBoolean("enabled") ?: false
56
+
57
+ blockPageUrl = blockPage?.getString("url")
58
+ }
59
+ }
@@ -0,0 +1,40 @@
1
+ package io.capkit.integrity
2
+
3
+ /**
4
+ * Native error model for the Integrity plugin (Android).
5
+ *
6
+ * Architectural rules:
7
+ * - Must NOT reference Capacitor APIs
8
+ * - Must NOT reference JavaScript
9
+ * - Must be throwable from the Impl layer
10
+ * - Mapping to JS-facing error codes happens ONLY in the Plugin layer
11
+ */
12
+ sealed class IntegrityError(
13
+ message: String,
14
+ ) : Throwable(message) {
15
+ /**
16
+ * Feature or capability is not available
17
+ * due to device or configuration limitations.
18
+ */
19
+ class Unavailable(message: String) :
20
+ IntegrityError(message)
21
+
22
+ /**
23
+ * Required permission was denied or not granted.
24
+ */
25
+ class PermissionDenied(message: String) :
26
+ IntegrityError(message)
27
+
28
+ /**
29
+ * Plugin failed to initialize or perform
30
+ * a required operation.
31
+ */
32
+ class InitFailed(message: String) :
33
+ IntegrityError(message)
34
+
35
+ /**
36
+ * Invalid or unsupported input was provided.
37
+ */
38
+ class UnknownType(message: String) :
39
+ IntegrityError(message)
40
+ }
@@ -0,0 +1,319 @@
1
+ package io.capkit.integrity
2
+
3
+ import android.content.Context
4
+ import io.capkit.integrity.emulator.IntegrityEmulatorChecks
5
+ import io.capkit.integrity.filesystem.IntegrityFilesystemChecks
6
+ import io.capkit.integrity.hook.IntegrityHookChecks
7
+ import io.capkit.integrity.remote.IntegrityRemoteAttestor
8
+ import io.capkit.integrity.root.IntegrityRootDetector
9
+ import io.capkit.integrity.runtime.IntegrityRuntimeChecks
10
+ import io.capkit.integrity.ui.IntegrityUISignals
11
+ import io.capkit.integrity.utils.IntegrityLogger
12
+ import java.util.Collections
13
+
14
+ /**
15
+ * Native Android implementation for the Integrity plugin.
16
+ *
17
+ * CONTRACT:
18
+ * - This class MUST NOT reference:
19
+ * - PluginCall
20
+ * - Capacitor APIs
21
+ * - Activities or UI components
22
+ *
23
+ * Responsibilities:
24
+ * - Perform platform-specific integrity checks
25
+ * - Interact with Android system APIs
26
+ * - Produce platform-agnostic integrity signals
27
+ *
28
+ * Error handling:
29
+ * - MUST throw typed IntegrityError on unrecoverable failures
30
+ * - MUST NOT swallow fatal initialization errors
31
+ */
32
+ class IntegrityImpl(
33
+ private val context: Context,
34
+ ) {
35
+ // ---------------------------------------------------------------------------
36
+ // Signal lifecycle & execution model
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /**
40
+ * Signal production in this implementation follows a two-phase model:
41
+ *
42
+ * 1. Early boot phase (optional, best-effort)
43
+ * - Signals may be captured before the Capacitor bridge is initialized.
44
+ * - Early signals are collected via the static `onApplicationCreate` entry point.
45
+ * - Captured signals are stored in a volatile in-memory buffer (`bootSignals`).
46
+ * - Early boot detection is opportunistic and NOT guaranteed.
47
+ *
48
+ * 2. Runtime phase (authoritative)
49
+ * - Signals are produced when `check(...)` is invoked from JavaScript.
50
+ * - Any previously captured boot signals are merged into the first report.
51
+ * - Subsequent checks operate exclusively on runtime detection.
52
+ *
53
+ * IMPORTANT SEMANTICS:
54
+ * - Boot signals are best-effort and may be absent depending on
55
+ * process lifecycle, OS behavior, or warm starts.
56
+ * - Boot signals do NOT provide stronger guarantees than runtime signals.
57
+ * - The absence of boot signals does NOT indicate a clean environment.
58
+ *
59
+ * This design improves detection timing, not detection coverage.
60
+ */
61
+ companion object {
62
+ /**
63
+ * Volatile buffer for signals captured during early boot.
64
+ */
65
+ private val bootSignals = Collections.synchronizedList(mutableListOf<Map<String, Any>>())
66
+
67
+ /**
68
+ * Native entry point for MainActivity.onCreate.
69
+ * Captures security signals before the Capacitor bridge is initialized.
70
+ */
71
+ @JvmStatic
72
+ fun onApplicationCreate(context: Context) {
73
+ // Capture basic root signals immediately at boot using the dedicated detector
74
+ val signals =
75
+ IntegrityRootDetector.checkRootSignals(
76
+ IntegrityCheckOptions(
77
+ level = "basic",
78
+ includeDebugInfo = false,
79
+ ),
80
+ allowCache = false,
81
+ )
82
+ bootSignals.addAll(signals)
83
+ }
84
+ }
85
+
86
+ // Negative cache for expensive integrity checks.
87
+ // Caches only "no-signal" results for a short time window.
88
+ private data class NegativeCacheEntry(
89
+ val timestampMs: Long,
90
+ )
91
+
92
+ private val negativeCache = mutableMapOf<String, NegativeCacheEntry>()
93
+
94
+ private val negativeCacheTtlMs = 30_000L
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Remote Attestation
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Stub for Google Play Integrity integration.
102
+ * To be implemented in a future evolution step.
103
+ */
104
+ fun getPlayIntegritySignal(options: IntegrityCheckOptions): Map<String, Any>? {
105
+ return IntegrityRemoteAttestor.getPlayIntegritySignal(context, options)
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Configuration
110
+ // ---------------------------------------------------------------------------
111
+
112
+ /**
113
+ * Cached immutable plugin configuration.
114
+ */
115
+ private lateinit var config: IntegrityConfig
116
+
117
+ /**
118
+ * Applies static plugin configuration.
119
+ */
120
+ fun updateConfig(newConfig: IntegrityConfig) {
121
+ this.config = newConfig
122
+ IntegrityLogger.verbose = newConfig.verboseLogging
123
+
124
+ IntegrityLogger.debug(
125
+ "Integrity configuration applied. Verbose logging:",
126
+ newConfig.verboseLogging.toString(),
127
+ )
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Options orchestrator
132
+ // ---------------------------------------------------------------------------
133
+
134
+ /**
135
+ * Executes the requested integrity checks and aggregates signals.
136
+ */
137
+ fun performCheck(options: IntegrityCheckOptions): Map<String, Any> {
138
+ // Apply negative cache only for standard / strict levels.
139
+ // Cached results represent a recent "no-signal" execution.
140
+ if (options.level != "basic" && isNegativeCacheValid(options.level)) {
141
+ IntegrityLogger.debug(
142
+ "Negative cache hit for integrity check:",
143
+ options.level,
144
+ )
145
+
146
+ return IntegrityReportBuilder.buildReport(
147
+ emptyList(),
148
+ isEmulator = false,
149
+ )
150
+ }
151
+
152
+ val signals = mutableListOf<Map<String, Any>>()
153
+
154
+ // --- BOOT SIGNALS ----------------------------------------------------
155
+ // Merge signals captured during early boot and clear the buffer.
156
+ synchronized(bootSignals) {
157
+ signals.addAll(bootSignals)
158
+ bootSignals.clear()
159
+ }
160
+
161
+ // --- BASIC -----------------------------------------------------------
162
+
163
+ // Root filesystem & build-tag detection (cached, runtime)
164
+ signals.addAll(
165
+ IntegrityRootDetector.checkRootSignals(
166
+ options = options,
167
+ allowCache = true,
168
+ ),
169
+ )
170
+
171
+ // Known root management packages (runtime-only)
172
+ signals.addAll(
173
+ IntegrityRootDetector.checkRootPackages(
174
+ context = context,
175
+ options = options,
176
+ ),
177
+ )
178
+
179
+ // Sandbox escape heuristics (non-deterministic, filesystem-based)
180
+ IntegrityFilesystemChecks
181
+ .checkSandboxEscape(options)
182
+ ?.let { signals.add(it) }
183
+
184
+ val isEmulator = IntegrityEmulatorChecks.isEmulator()
185
+ if (isEmulator) {
186
+ signals.add(
187
+ IntegritySignalBuilder.build(
188
+ id = "android_emulator",
189
+ category = "emulator",
190
+ confidence = "high",
191
+ description = "Execution environment matches known emulator characteristics",
192
+ // Added metadata to identify common emulator properties
193
+ metadata =
194
+ mapOf(
195
+ "model" to android.os.Build.MODEL,
196
+ "manufacturer" to android.os.Build.MANUFACTURER,
197
+ "hardware" to android.os.Build.HARDWARE,
198
+ ),
199
+ options = options,
200
+ ),
201
+ )
202
+ }
203
+
204
+ // --- STANDARD & STRICT -----------------------------------------------
205
+
206
+ if (options.level != "basic") {
207
+ // checkDebug returns a list of signals
208
+ signals.addAll(IntegrityRuntimeChecks.checkDebugSignals(context, options))
209
+
210
+ // UI and Overlay detection (RASP)
211
+ IntegrityUISignals
212
+ .checkOverlaySignals(context, options)
213
+ ?.let { signals.add(it) }
214
+
215
+ // --- HOOKING DETECTION --------------------------------------------
216
+
217
+ // Frida detection via memory maps
218
+ val memHook = IntegrityHookChecks.checkFridaMemory()
219
+
220
+ if (memHook) {
221
+ signals.add(
222
+ IntegritySignalBuilder.build(
223
+ id = "android_frida_memory",
224
+ category = "hook",
225
+ confidence = "high",
226
+ description = "Process memory contains known instrumentation artifacts",
227
+ options = options,
228
+ ),
229
+ )
230
+ }
231
+
232
+ // Frida detection via known ports
233
+ val portHook = IntegrityHookChecks.checkFridaPorts()
234
+ if (portHook) {
235
+ signals.add(
236
+ IntegritySignalBuilder.build(
237
+ id = "android_frida_port",
238
+ category = "hook",
239
+ confidence = "medium",
240
+ description = "Known instrumentation ports are reachable on localhost",
241
+ options = options,
242
+ ),
243
+ )
244
+ }
245
+
246
+ // Signal correlation logic
247
+ if (memHook && portHook) {
248
+ signals.add(
249
+ IntegritySignalBuilder.build(
250
+ id = "android_frida_correlation_confirmed",
251
+ category = "hook",
252
+ confidence = "high",
253
+ description = "Multiple instrumentation indicators detected simultaneously",
254
+ metadata = mapOf("source" to "memory+port"),
255
+ options = options,
256
+ ),
257
+ )
258
+ }
259
+ }
260
+
261
+ // --- STRICT --------------------------------------------------------------
262
+
263
+ if (options.level == "strict") {
264
+ // Google Play Integrity (future remote attestation)
265
+ IntegrityRemoteAttestor
266
+ .getPlayIntegritySignal(context, options)
267
+ ?.let { signals.add(it) }
268
+
269
+ // Verify application signature integrity
270
+ if (!IntegrityRuntimeChecks.checkAppSignature(context)) {
271
+ signals.add(
272
+ IntegritySignalBuilder.build(
273
+ id = "android_signature_invalid",
274
+ category = "tamper",
275
+ confidence = "high",
276
+ description = "Application signing information does not match expected format",
277
+ // Added metadata to specify the source of the signature check failure
278
+ metadata = mapOf("package" to context.packageName),
279
+ options = options,
280
+ ),
281
+ )
282
+ }
283
+ }
284
+
285
+ // Update negative cache only when no integrity signals are detected.
286
+ // Any detected signal invalidates the cached clean state.
287
+ if (options.level != "basic") {
288
+ if (signals.isEmpty()) {
289
+ updateNegativeCache(options.level)
290
+ } else {
291
+ clearNegativeCache(options.level)
292
+ }
293
+ }
294
+
295
+ // Final report assembly using the dedicated builder
296
+ return IntegrityReportBuilder.buildReport(
297
+ signals,
298
+ isEmulator,
299
+ )
300
+ }
301
+
302
+ private fun cacheKey(level: String): String {
303
+ return "android:$level"
304
+ }
305
+
306
+ private fun isNegativeCacheValid(level: String): Boolean {
307
+ val entry = negativeCache[cacheKey(level)] ?: return false
308
+ return System.currentTimeMillis() - entry.timestampMs <= negativeCacheTtlMs
309
+ }
310
+
311
+ private fun updateNegativeCache(level: String) {
312
+ negativeCache[cacheKey(level)] =
313
+ NegativeCacheEntry(System.currentTimeMillis())
314
+ }
315
+
316
+ private fun clearNegativeCache(level: String) {
317
+ negativeCache.remove(cacheKey(level))
318
+ }
319
+ }