@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.
- package/CapKitIntegrity.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +26 -0
- package/README.md +1104 -0
- package/android/build.gradle +104 -0
- package/android/src/main/AndroidManifest.xml +21 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityCheckOptions.kt +37 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityConfig.kt +59 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityError.kt +40 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityImpl.kt +319 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityPlugin.kt +475 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityReportBuilder.kt +130 -0
- package/android/src/main/java/io/capkit/integrity/IntegritySignalBuilder.kt +72 -0
- package/android/src/main/java/io/capkit/integrity/emulator/IntegrityEmulatorChecks.kt +38 -0
- package/android/src/main/java/io/capkit/integrity/filesystem/IntegrityFilesystemChecks.kt +51 -0
- package/android/src/main/java/io/capkit/integrity/hook/IntegrityHookChecks.kt +61 -0
- package/android/src/main/java/io/capkit/integrity/remote/IntegrityRemoteAttestor.kt +49 -0
- package/android/src/main/java/io/capkit/integrity/root/IntegrityRootDetector.kt +136 -0
- package/android/src/main/java/io/capkit/integrity/runtime/IntegrityRuntimeChecks.kt +87 -0
- package/android/src/main/java/io/capkit/integrity/ui/IntegrityBlockActivity.kt +173 -0
- package/android/src/main/java/io/capkit/integrity/ui/IntegrityUISignals.kt +57 -0
- package/android/src/main/java/io/capkit/integrity/utils/IntegrityLogger.kt +85 -0
- package/android/src/main/java/io/capkit/integrity/utils/IntegrityUtils.kt +105 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/values/styles.xml +5 -0
- package/dist/docs.json +598 -0
- package/dist/esm/definitions.d.ts +554 -0
- package/dist/esm/definitions.js +56 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +32 -0
- package/dist/esm/web.js +51 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +130 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +133 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/IntegrityPlugin/IntegrityCheckOptions.swift +41 -0
- package/ios/Sources/IntegrityPlugin/IntegrityConfig.swift +135 -0
- package/ios/Sources/IntegrityPlugin/IntegrityEntitlementChecks.swift +58 -0
- package/ios/Sources/IntegrityPlugin/IntegrityError.swift +49 -0
- package/ios/Sources/IntegrityPlugin/IntegrityImpl.swift +397 -0
- package/ios/Sources/IntegrityPlugin/IntegrityPlugin.swift +345 -0
- package/ios/Sources/IntegrityPlugin/IntegrityReportBuilder.swift +184 -0
- package/ios/Sources/IntegrityPlugin/Utils/IntegrityLogger.swift +69 -0
- package/ios/Sources/IntegrityPlugin/Utils/IntegrityUtils.swift +144 -0
- package/ios/Sources/IntegrityPlugin/Version.swift +16 -0
- package/ios/Sources/IntegrityPlugin/filesystem/IntegrityFilesystemChecks.swift +86 -0
- package/ios/Sources/IntegrityPlugin/hook/IntegrityHookChecks.swift +85 -0
- package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakDetector.swift +74 -0
- package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakUrlSchemeDetector.swift +42 -0
- package/ios/Sources/IntegrityPlugin/remote/IntegrityRemoteAttestor.swift +40 -0
- package/ios/Sources/IntegrityPlugin/runtime/IntegrityRuntimeChecks.swift +63 -0
- package/ios/Sources/IntegrityPlugin/simulator/IntegritySimulatorChecks.swift +20 -0
- package/ios/Sources/IntegrityPlugin/ui/IntegrityBlockViewController.swift +143 -0
- package/ios/Tests/IntegrityPluginTests/IntegrityPluginTests.swift +10 -0
- package/package.json +106 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
Capacitor bridge for the Integrity plugin (iOS).
|
|
7
|
+
|
|
8
|
+
Responsibilities:
|
|
9
|
+
- Parse JavaScript input
|
|
10
|
+
- Invoke the native implementation
|
|
11
|
+
- Resolve or reject CAPPluginCall exactly once
|
|
12
|
+
- Map native IntegrityError to JS-facing error codes
|
|
13
|
+
|
|
14
|
+
Forbidden:
|
|
15
|
+
- Platform-specific business logic
|
|
16
|
+
- System API usage
|
|
17
|
+
- Throwing uncaught exceptions
|
|
18
|
+
*/
|
|
19
|
+
@objc(IntegrityPlugin)
|
|
20
|
+
public final class IntegrityPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
21
|
+
|
|
22
|
+
// MARK: - Plugin metadata
|
|
23
|
+
|
|
24
|
+
/// The unique identifier for the plugin.
|
|
25
|
+
///
|
|
26
|
+
/// CONTRACT:
|
|
27
|
+
/// - MUST match the plugin registration name used by Capacitor
|
|
28
|
+
/// - MUST remain stable across releases (breaking change otherwise)
|
|
29
|
+
public let identifier = "IntegrityPlugin"
|
|
30
|
+
|
|
31
|
+
/// The name used to reference this plugin in JavaScript.
|
|
32
|
+
///
|
|
33
|
+
/// CONTRACT:
|
|
34
|
+
/// - MUST match `registerPlugin({ name })` on the JS side
|
|
35
|
+
/// - Changing this value breaks JS ↔ native binding
|
|
36
|
+
public let jsName = "Integrity"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
A list of methods exposed by this plugin.
|
|
40
|
+
- `check`:
|
|
41
|
+
- `presentBlockPage`:
|
|
42
|
+
- `getPluginVersion`: A method that returns the version of the plugin.
|
|
43
|
+
|
|
44
|
+
CONTRACT:
|
|
45
|
+
- Every method listed here MUST:
|
|
46
|
+
- be implemented exactly once
|
|
47
|
+
- resolve or reject its CAPPluginCall exactly once
|
|
48
|
+
- return a Promise on the JS side
|
|
49
|
+
|
|
50
|
+
NOTE:
|
|
51
|
+
- No method in this list may perform platform logic directly.
|
|
52
|
+
- All business logic MUST be delegated to the Impl layer.
|
|
53
|
+
*/
|
|
54
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
55
|
+
CAPPluginMethod(name: "check", returnType: CAPPluginReturnPromise),
|
|
56
|
+
CAPPluginMethod(name: "presentBlockPage", returnType: CAPPluginReturnPromise),
|
|
57
|
+
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
// MARK: - Properties
|
|
61
|
+
|
|
62
|
+
/// Native implementation instance.
|
|
63
|
+
private let implementation = IntegrityImpl()
|
|
64
|
+
|
|
65
|
+
// Configuration instance
|
|
66
|
+
private var config: IntegrityConfig?
|
|
67
|
+
|
|
68
|
+
// MARK: - Event-related properties
|
|
69
|
+
|
|
70
|
+
/// Buffer for integrity signals captured before a JS listener is registered.
|
|
71
|
+
private var bufferedSignals: [[String: Any]] = []
|
|
72
|
+
|
|
73
|
+
/// Canonical event name emitted to the JavaScript layer.
|
|
74
|
+
private static let integritySignalEvent = "integritySignal"
|
|
75
|
+
|
|
76
|
+
// MARK: - Lifecycle
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
Plugin lifecycle entry point.
|
|
80
|
+
|
|
81
|
+
CONTRACT:
|
|
82
|
+
- Called exactly once by Capacitor
|
|
83
|
+
- This is the ONLY valid place to:
|
|
84
|
+
- read plugin configuration
|
|
85
|
+
- create IntegrityConfig
|
|
86
|
+
- inject configuration into the Impl layer
|
|
87
|
+
- register system event observers (NotificationCenter)
|
|
88
|
+
|
|
89
|
+
WARNING:
|
|
90
|
+
- Calling applyConfig(_:) outside this method is FORBIDDEN
|
|
91
|
+
- Observers registered here MUST be detached in `deinit`
|
|
92
|
+
*/
|
|
93
|
+
override public func load() {
|
|
94
|
+
// Initialize IntegrityConfig
|
|
95
|
+
let cfg = IntegrityConfig(plugin: self)
|
|
96
|
+
self.config = cfg
|
|
97
|
+
implementation.applyConfig(cfg)
|
|
98
|
+
IntegrityLogger.debug("Integrity plugin loaded")
|
|
99
|
+
|
|
100
|
+
// Register passive system observers
|
|
101
|
+
addEventObservers()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
Overridden to catch the moment JavaScript starts listening.
|
|
106
|
+
Ensures boot-time signals are delivered immediately.
|
|
107
|
+
*/
|
|
108
|
+
override public func addEventListener(_ eventName: String, listener: CAPPluginCall) {
|
|
109
|
+
// Fix: Added explicit 'listener:' label required by the CAPPlugin base class
|
|
110
|
+
super.addEventListener(eventName, listener: listener)
|
|
111
|
+
|
|
112
|
+
// Ensure we match the canonical event name used in the plugin
|
|
113
|
+
if eventName == "onIntegritySignal" {
|
|
114
|
+
// Trigger a flush of early boot signals
|
|
115
|
+
let options = IntegrityCheckOptions(level: "standard", includeDebugInfo: false)
|
|
116
|
+
do {
|
|
117
|
+
// This will internally call mergeBootSignals to clear the volatile queue
|
|
118
|
+
let report = try implementation.performCheck(options: options)
|
|
119
|
+
if let signals = report["signals"] as? [[String: Any]], !signals.isEmpty {
|
|
120
|
+
emitOrBufferSignal(report)
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
IntegrityLogger.error("Failed to flush boot signals on listener registration")
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Plugin teardown.
|
|
129
|
+
///
|
|
130
|
+
/// NOTE:
|
|
131
|
+
/// - Invoked when the plugin instance is deallocated
|
|
132
|
+
/// - Responsible for detaching all NotificationCenter observers
|
|
133
|
+
deinit {
|
|
134
|
+
removeEventObservers()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// MARK: - Event emission and buffering
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
Emits an integrity signal to JavaScript or buffers it if no listeners exist.
|
|
141
|
+
*/
|
|
142
|
+
private func emitOrBufferSignal(_ signal: [String: Any]) {
|
|
143
|
+
if hasListeners(IntegrityPlugin.integritySignalEvent) {
|
|
144
|
+
notifyListeners(IntegrityPlugin.integritySignalEvent, data: signal, retainUntilConsumed: true)
|
|
145
|
+
} else {
|
|
146
|
+
bufferedSignals.append(signal)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
Flushes all buffered integrity signals to JavaScript listeners.
|
|
152
|
+
*/
|
|
153
|
+
@objc private func flushBufferedSignals() {
|
|
154
|
+
if hasListeners(IntegrityPlugin.integritySignalEvent) && !bufferedSignals.isEmpty {
|
|
155
|
+
for signal in bufferedSignals {
|
|
156
|
+
notifyListeners(IntegrityPlugin.integritySignalEvent, data: signal, retainUntilConsumed: true)
|
|
157
|
+
}
|
|
158
|
+
bufferedSignals.removeAll()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// MARK: - NotificationCenter registration
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
Registers passive system observers required for real-time integrity signals.
|
|
166
|
+
*/
|
|
167
|
+
private func addEventObservers() {
|
|
168
|
+
NotificationCenter.default.addObserver(
|
|
169
|
+
self,
|
|
170
|
+
selector: #selector(handleDidBecomeActiveNotification(_:)),
|
|
171
|
+
name: UIApplication.didBecomeActiveNotification,
|
|
172
|
+
object: nil
|
|
173
|
+
)
|
|
174
|
+
NotificationCenter.default.addObserver(
|
|
175
|
+
self,
|
|
176
|
+
selector: #selector(flushBufferedSignals),
|
|
177
|
+
name: UIApplication.willEnterForegroundNotification,
|
|
178
|
+
object: nil
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
Detaches all NotificationCenter observers.
|
|
184
|
+
|
|
185
|
+
NOTE:
|
|
186
|
+
- This method is invoked exclusively from `deinit`.
|
|
187
|
+
- The SwiftLint rule is suppressed intentionally as the call is lifecycle-safe.
|
|
188
|
+
*/
|
|
189
|
+
private func removeEventObservers() {
|
|
190
|
+
// swiftlint:disable notification_center_detachment
|
|
191
|
+
NotificationCenter.default.removeObserver(self)
|
|
192
|
+
// swiftlint:enable notification_center_detachment
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// MARK: - Error mapping
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
Maps native `IntegrityError` values to JS-facing error codes.
|
|
199
|
+
|
|
200
|
+
CONTRACT:
|
|
201
|
+
- Error codes MUST be stable and documented
|
|
202
|
+
- Error codes MUST match across platforms
|
|
203
|
+
- Platform-specific error codes are FORBIDDEN
|
|
204
|
+
*/
|
|
205
|
+
private func reject(
|
|
206
|
+
_ call: CAPPluginCall,
|
|
207
|
+
error: IntegrityError
|
|
208
|
+
) {
|
|
209
|
+
let code: String
|
|
210
|
+
|
|
211
|
+
switch error {
|
|
212
|
+
case .unavailable:
|
|
213
|
+
code = "UNAVAILABLE"
|
|
214
|
+
case .permissionDenied:
|
|
215
|
+
code = "PERMISSION_DENIED"
|
|
216
|
+
case .initFailed:
|
|
217
|
+
code = "INIT_FAILED"
|
|
218
|
+
case .unknownType:
|
|
219
|
+
code = "UNKNOWN_TYPE"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
call.reject(error.message, code)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// MARK: - Integrity check
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
Executes a baseline integrity check.
|
|
229
|
+
*/
|
|
230
|
+
@objc func check(_ call: CAPPluginCall) {
|
|
231
|
+
do {
|
|
232
|
+
let options =
|
|
233
|
+
try call.decode(IntegrityCheckOptions.self)
|
|
234
|
+
|
|
235
|
+
let normalizedOptions = IntegrityCheckOptions(
|
|
236
|
+
level: options.level ?? "basic",
|
|
237
|
+
includeDebugInfo: options.includeDebugInfo ?? false
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
let result =
|
|
241
|
+
try implementation.performCheck(
|
|
242
|
+
options: normalizedOptions
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
call.resolve(result)
|
|
246
|
+
|
|
247
|
+
} catch let error as IntegrityError {
|
|
248
|
+
reject(call, error: error)
|
|
249
|
+
} catch {
|
|
250
|
+
call.reject(
|
|
251
|
+
"Unexpected native error during integrity check.",
|
|
252
|
+
"INIT_FAILED"
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// MARK: - Present block page
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
Presents the configured integrity block page, if enabled.
|
|
261
|
+
*/
|
|
262
|
+
@objc func presentBlockPage(_ call: CAPPluginCall) {
|
|
263
|
+
guard
|
|
264
|
+
let blockPage = config?.blockPage,
|
|
265
|
+
blockPage.enabled,
|
|
266
|
+
let baseURL = blockPage.url
|
|
267
|
+
else {
|
|
268
|
+
call.resolve(["presented": false])
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let reason = call.getString("reason")
|
|
273
|
+
let dismissible = call.getBool("dismissible") ?? false
|
|
274
|
+
|
|
275
|
+
let finalURL =
|
|
276
|
+
reason != nil
|
|
277
|
+
? "\(baseURL)?reason=\(reason!)"
|
|
278
|
+
: baseURL
|
|
279
|
+
|
|
280
|
+
DispatchQueue.main.async {
|
|
281
|
+
// CONTRACT:
|
|
282
|
+
// All UIKit interactions MUST occur on the main thread.
|
|
283
|
+
guard let rootVC = self.bridge?.viewController else {
|
|
284
|
+
call.reject(
|
|
285
|
+
"View controller not available",
|
|
286
|
+
"UNAVAILABLE"
|
|
287
|
+
)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let blockVC = IntegrityBlockViewController(
|
|
292
|
+
url: finalURL,
|
|
293
|
+
dismissible: dismissible
|
|
294
|
+
)
|
|
295
|
+
let nav = UINavigationController(rootViewController: blockVC)
|
|
296
|
+
rootVC.present(nav, animated: true) {
|
|
297
|
+
call.resolve(["presented": true])
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// MARK: - Version
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
Returns the native plugin version.
|
|
306
|
+
|
|
307
|
+
- Used exclusively for diagnostics and compatibility checks.
|
|
308
|
+
- Must not be used for feature detection.
|
|
309
|
+
*/
|
|
310
|
+
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
311
|
+
// Standardized enum name across all CapKit plugins
|
|
312
|
+
call.resolve([
|
|
313
|
+
"version": PluginVersion.number
|
|
314
|
+
])
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// MARK: - Notification handlers
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
Handles application foreground transitions.
|
|
321
|
+
*/
|
|
322
|
+
@objc private func handleDidBecomeActiveNotification(_ notification: Notification) {
|
|
323
|
+
// Targeted options for real-time monitoring.
|
|
324
|
+
// Using explicit labels to satisfy Swift compiler and struct definition.
|
|
325
|
+
let options = IntegrityCheckOptions(
|
|
326
|
+
level: "standard",
|
|
327
|
+
includeDebugInfo: false
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
// Perform check and notify JS immediately if signals are found
|
|
331
|
+
do {
|
|
332
|
+
let report = try implementation.performCheck(options: options)
|
|
333
|
+
|
|
334
|
+
// Optimization: Only emit if the report contains signals (compromised or score > 0)
|
|
335
|
+
if let signals = report["signals"] as? [[String: Any]], !signals.isEmpty {
|
|
336
|
+
emitOrBufferSignal(report)
|
|
337
|
+
|
|
338
|
+
// Logging for internal audit
|
|
339
|
+
IntegrityLogger.debug("Real-time signal detected and emitted to JS")
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
IntegrityLogger.error("Real-time monitor failed during app activation:", error.localizedDescription)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Helper responsible for assembling the final integrity report payload.
|
|
5
|
+
|
|
6
|
+
Responsibilities:
|
|
7
|
+
- Aggregate signals
|
|
8
|
+
- Compute integrity score
|
|
9
|
+
- Build environment metadata
|
|
10
|
+
- Produce a JS-bridge-safe dictionary
|
|
11
|
+
|
|
12
|
+
This type contains NO platform logic.
|
|
13
|
+
*/
|
|
14
|
+
struct IntegrityReportBuilder {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
Builds the final integrity report returned to the JavaScript layer.
|
|
18
|
+
|
|
19
|
+
This method is responsible for:
|
|
20
|
+
- aggregating all collected integrity signals
|
|
21
|
+
- computing a numeric integrity score
|
|
22
|
+
- deriving a boolean compromise state
|
|
23
|
+
- attaching immutable environment metadata
|
|
24
|
+
- producing a JSON-bridge-safe payload
|
|
25
|
+
|
|
26
|
+
IMPORTANT:
|
|
27
|
+
- This builder MUST remain platform-agnostic.
|
|
28
|
+
- No platform-specific heuristics or weighting logic
|
|
29
|
+
may be introduced here.
|
|
30
|
+
*/
|
|
31
|
+
static func buildReport(
|
|
32
|
+
signals: [[String: Any]],
|
|
33
|
+
isEmulator: Bool,
|
|
34
|
+
platform: String
|
|
35
|
+
) -> [String: Any] {
|
|
36
|
+
|
|
37
|
+
// Compute the aggregate integrity score based on signal confidence.
|
|
38
|
+
let score = computeScore(from: signals)
|
|
39
|
+
|
|
40
|
+
// Build informational explanation metadata for the score.
|
|
41
|
+
let scoreExplanation = buildScoreExplanation(from: signals)
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
// Ordered list of all detected integrity signals.
|
|
45
|
+
"signals": signals,
|
|
46
|
+
|
|
47
|
+
// Numeric integrity score derived from signal confidence.
|
|
48
|
+
"score": score,
|
|
49
|
+
|
|
50
|
+
// Convenience flag indicating whether the device
|
|
51
|
+
// should be considered compromised.
|
|
52
|
+
"compromised": score >= 30,
|
|
53
|
+
|
|
54
|
+
// Static environment metadata describing the runtime context.
|
|
55
|
+
"environment": buildEnvironment(
|
|
56
|
+
platform: platform,
|
|
57
|
+
isEmulator: isEmulator
|
|
58
|
+
),
|
|
59
|
+
|
|
60
|
+
// Informational explanation describing how the score was derived.
|
|
61
|
+
// This metadata MUST NOT be treated as a security decision.
|
|
62
|
+
"scoreExplanation": scoreExplanation,
|
|
63
|
+
|
|
64
|
+
// Millisecond-precision UNIX timestamp of report generation.
|
|
65
|
+
"timestamp": currentTimestamp()
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// MARK: - Scoring
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
Computes a numeric integrity score from a list of signals.
|
|
73
|
+
|
|
74
|
+
Scoring policy:
|
|
75
|
+
- high → +30 points
|
|
76
|
+
- medium → +15 points
|
|
77
|
+
- low → +5 points
|
|
78
|
+
|
|
79
|
+
NOTES:
|
|
80
|
+
- Signals without a valid `confidence` field are ignored.
|
|
81
|
+
- This scoring model is intentionally simple and heuristic-based.
|
|
82
|
+
- Platform-specific adjustments MUST NOT be implemented here.
|
|
83
|
+
*/
|
|
84
|
+
private static func computeScore(
|
|
85
|
+
from signals: [[String: Any]]
|
|
86
|
+
) -> Int {
|
|
87
|
+
signals.reduce(0) { acc, signal in
|
|
88
|
+
guard let confidence = signal["confidence"] as? String else {
|
|
89
|
+
return acc
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
switch confidence {
|
|
93
|
+
case "high": return acc + 30
|
|
94
|
+
case "medium": return acc + 15
|
|
95
|
+
case "low": return acc + 5
|
|
96
|
+
default: return acc
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// MARK: - Score Explanation
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
Builds an informational explanation describing how the integrity
|
|
105
|
+
score was derived from the detected signals.
|
|
106
|
+
|
|
107
|
+
IMPORTANT:
|
|
108
|
+
- This metadata is informational only.
|
|
109
|
+
- It MUST NOT influence scoring or enforcement decisions.
|
|
110
|
+
*/
|
|
111
|
+
private static func buildScoreExplanation(
|
|
112
|
+
from signals: [[String: Any]]
|
|
113
|
+
) -> [String: Any] {
|
|
114
|
+
|
|
115
|
+
var high = 0
|
|
116
|
+
var medium = 0
|
|
117
|
+
var low = 0
|
|
118
|
+
|
|
119
|
+
let contributors = signals.compactMap {
|
|
120
|
+
$0["id"] as? String
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for signal in signals {
|
|
124
|
+
switch signal["confidence"] as? String {
|
|
125
|
+
case "high":
|
|
126
|
+
high += 1
|
|
127
|
+
case "medium":
|
|
128
|
+
medium += 1
|
|
129
|
+
case "low":
|
|
130
|
+
low += 1
|
|
131
|
+
default:
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return [
|
|
137
|
+
"totalSignals": signals.count,
|
|
138
|
+
"byConfidence": [
|
|
139
|
+
"high": high,
|
|
140
|
+
"medium": medium,
|
|
141
|
+
"low": low
|
|
142
|
+
],
|
|
143
|
+
"contributors": contributors
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// MARK: - Environment
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
Builds a static environment descriptor attached to every report.
|
|
151
|
+
|
|
152
|
+
This metadata is informational only and MUST NOT be used
|
|
153
|
+
to infer compromise on its own.
|
|
154
|
+
*/
|
|
155
|
+
private static func buildEnvironment(
|
|
156
|
+
platform: String,
|
|
157
|
+
isEmulator: Bool
|
|
158
|
+
) -> [String: Any] {
|
|
159
|
+
[
|
|
160
|
+
// Platform identifier (e.g. "ios", "android").
|
|
161
|
+
"platform": platform,
|
|
162
|
+
|
|
163
|
+
// Whether the app is running inside an emulator or simulator.
|
|
164
|
+
"isEmulator": isEmulator,
|
|
165
|
+
|
|
166
|
+
// Debug build flag reserved for future use.
|
|
167
|
+
// Currently always false on iOS.
|
|
168
|
+
"isDebugBuild": false
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// MARK: - Timestamp
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
Returns the current UNIX timestamp in milliseconds.
|
|
176
|
+
|
|
177
|
+
This timestamp represents the moment the integrity
|
|
178
|
+
report was assembled, not when individual signals
|
|
179
|
+
were detected.
|
|
180
|
+
*/
|
|
181
|
+
private static func currentTimestamp() -> Int {
|
|
182
|
+
Int(Date().timeIntervalSince1970 * 1000)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Centralized logger for the Integrity plugin.
|
|
5
|
+
|
|
6
|
+
Responsibilities:
|
|
7
|
+
- Provide a single logging entry point
|
|
8
|
+
- Support runtime-controlled verbose logging
|
|
9
|
+
- Keep logging behavior consistent across files
|
|
10
|
+
|
|
11
|
+
Forbidden:
|
|
12
|
+
- Controlling application logic
|
|
13
|
+
- Being queried for flow decisions
|
|
14
|
+
*/
|
|
15
|
+
enum IntegrityLogger {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
Controls whether debug logs are printed.
|
|
19
|
+
|
|
20
|
+
This value MUST be set once during plugin initialization
|
|
21
|
+
based on static configuration.
|
|
22
|
+
*/
|
|
23
|
+
static var verbose: Bool = false
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
Prints a verbose / debug log message.
|
|
27
|
+
|
|
28
|
+
Debug logs are automatically silenced
|
|
29
|
+
when `verbose` is false.
|
|
30
|
+
*/
|
|
31
|
+
static func debug(_ items: Any...) {
|
|
32
|
+
guard verbose else { return }
|
|
33
|
+
log(items)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
Prints an error log message.
|
|
38
|
+
|
|
39
|
+
Error logs are always printed regardless
|
|
40
|
+
of the verbose flag.
|
|
41
|
+
*/
|
|
42
|
+
static func error(_ items: Any...) {
|
|
43
|
+
log(items)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// MARK: - Internal log printer
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
Low-level log printer with a consistent prefix.
|
|
51
|
+
|
|
52
|
+
This function MUST NOT be used outside this file.
|
|
53
|
+
*/
|
|
54
|
+
private func log(
|
|
55
|
+
_ items: [Any],
|
|
56
|
+
separator: String = " ",
|
|
57
|
+
terminator: String = "\n"
|
|
58
|
+
) {
|
|
59
|
+
CAPLog.print("⚡️ Integrity -", terminator: separator)
|
|
60
|
+
|
|
61
|
+
for (index, item) in items.enumerated() {
|
|
62
|
+
CAPLog.print(
|
|
63
|
+
item,
|
|
64
|
+
terminator: index == items.count - 1
|
|
65
|
+
? terminator
|
|
66
|
+
: separator
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|