native-update 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.
Files changed (184) hide show
  1. package/CapacitorNativeUpdate.podspec +18 -0
  2. package/LICENSE +21 -0
  3. package/Readme.md +451 -0
  4. package/android/build.gradle +92 -0
  5. package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
  6. package/android/gradle.properties +17 -0
  7. package/android/proguard-rules.pro +29 -0
  8. package/android/settings.gradle +2 -0
  9. package/android/src/main/AndroidManifest.xml +34 -0
  10. package/android/src/main/java/com/aoneahsan/nativeupdate/AppReviewPlugin.kt +153 -0
  11. package/android/src/main/java/com/aoneahsan/nativeupdate/AppUpdatePlugin.kt +275 -0
  12. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundNotificationManager.kt +390 -0
  13. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateManager.kt +46 -0
  14. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +333 -0
  15. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +251 -0
  16. package/android/src/main/java/com/aoneahsan/nativeupdate/CapacitorNativeUpdatePlugin.kt +265 -0
  17. package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +526 -0
  18. package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +99 -0
  19. package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +249 -0
  20. package/dist/esm/__tests__/bundle-manager.test.d.ts +1 -0
  21. package/dist/esm/__tests__/bundle-manager.test.js +123 -0
  22. package/dist/esm/__tests__/bundle-manager.test.js.map +1 -0
  23. package/dist/esm/__tests__/config.test.d.ts +1 -0
  24. package/dist/esm/__tests__/config.test.js +69 -0
  25. package/dist/esm/__tests__/config.test.js.map +1 -0
  26. package/dist/esm/__tests__/integration.test.d.ts +1 -0
  27. package/dist/esm/__tests__/integration.test.js +78 -0
  28. package/dist/esm/__tests__/integration.test.js.map +1 -0
  29. package/dist/esm/__tests__/security.test.d.ts +1 -0
  30. package/dist/esm/__tests__/security.test.js +54 -0
  31. package/dist/esm/__tests__/security.test.js.map +1 -0
  32. package/dist/esm/__tests__/version-manager.test.d.ts +1 -0
  33. package/dist/esm/__tests__/version-manager.test.js +45 -0
  34. package/dist/esm/__tests__/version-manager.test.js.map +1 -0
  35. package/dist/esm/app-review/app-review-manager.d.ts +24 -0
  36. package/dist/esm/app-review/app-review-manager.js +195 -0
  37. package/dist/esm/app-review/app-review-manager.js.map +1 -0
  38. package/dist/esm/app-review/index.d.ts +5 -0
  39. package/dist/esm/app-review/index.js +6 -0
  40. package/dist/esm/app-review/index.js.map +1 -0
  41. package/dist/esm/app-review/platform-review-handler.d.ts +20 -0
  42. package/dist/esm/app-review/platform-review-handler.js +138 -0
  43. package/dist/esm/app-review/platform-review-handler.js.map +1 -0
  44. package/dist/esm/app-review/review-conditions-checker.d.ts +22 -0
  45. package/dist/esm/app-review/review-conditions-checker.js +155 -0
  46. package/dist/esm/app-review/review-conditions-checker.js.map +1 -0
  47. package/dist/esm/app-review/review-rate-limiter.d.ts +23 -0
  48. package/dist/esm/app-review/review-rate-limiter.js +164 -0
  49. package/dist/esm/app-review/review-rate-limiter.js.map +1 -0
  50. package/dist/esm/app-review/types.d.ts +41 -0
  51. package/dist/esm/app-review/types.js +2 -0
  52. package/dist/esm/app-review/types.js.map +1 -0
  53. package/dist/esm/app-update/app-update-checker.d.ts +13 -0
  54. package/dist/esm/app-update/app-update-checker.js +104 -0
  55. package/dist/esm/app-update/app-update-checker.js.map +1 -0
  56. package/dist/esm/app-update/app-update-installer.d.ts +19 -0
  57. package/dist/esm/app-update/app-update-installer.js +123 -0
  58. package/dist/esm/app-update/app-update-installer.js.map +1 -0
  59. package/dist/esm/app-update/app-update-manager.d.ts +28 -0
  60. package/dist/esm/app-update/app-update-manager.js +199 -0
  61. package/dist/esm/app-update/app-update-manager.js.map +1 -0
  62. package/dist/esm/app-update/app-update-notifier.d.ts +14 -0
  63. package/dist/esm/app-update/app-update-notifier.js +100 -0
  64. package/dist/esm/app-update/app-update-notifier.js.map +1 -0
  65. package/dist/esm/app-update/index.d.ts +6 -0
  66. package/dist/esm/app-update/index.js +7 -0
  67. package/dist/esm/app-update/index.js.map +1 -0
  68. package/dist/esm/app-update/platform-app-update.d.ts +19 -0
  69. package/dist/esm/app-update/platform-app-update.js +129 -0
  70. package/dist/esm/app-update/platform-app-update.js.map +1 -0
  71. package/dist/esm/app-update/types.d.ts +58 -0
  72. package/dist/esm/app-update/types.js +12 -0
  73. package/dist/esm/app-update/types.js.map +1 -0
  74. package/dist/esm/background-update/background-scheduler.d.ts +17 -0
  75. package/dist/esm/background-update/background-scheduler.js +195 -0
  76. package/dist/esm/background-update/background-scheduler.js.map +1 -0
  77. package/dist/esm/background-update/index.d.ts +3 -0
  78. package/dist/esm/background-update/index.js +3 -0
  79. package/dist/esm/background-update/index.js.map +1 -0
  80. package/dist/esm/background-update/notification-manager.d.ts +29 -0
  81. package/dist/esm/background-update/notification-manager.js +89 -0
  82. package/dist/esm/background-update/notification-manager.js.map +1 -0
  83. package/dist/esm/core/analytics.d.ts +70 -0
  84. package/dist/esm/core/analytics.js +137 -0
  85. package/dist/esm/core/analytics.js.map +1 -0
  86. package/dist/esm/core/cache-manager.d.ts +72 -0
  87. package/dist/esm/core/cache-manager.js +275 -0
  88. package/dist/esm/core/cache-manager.js.map +1 -0
  89. package/dist/esm/core/config.d.ts +48 -0
  90. package/dist/esm/core/config.js +83 -0
  91. package/dist/esm/core/config.js.map +1 -0
  92. package/dist/esm/core/errors.d.ts +51 -0
  93. package/dist/esm/core/errors.js +80 -0
  94. package/dist/esm/core/errors.js.map +1 -0
  95. package/dist/esm/core/logger.d.ts +21 -0
  96. package/dist/esm/core/logger.js +109 -0
  97. package/dist/esm/core/logger.js.map +1 -0
  98. package/dist/esm/core/performance.d.ts +53 -0
  99. package/dist/esm/core/performance.js +140 -0
  100. package/dist/esm/core/performance.js.map +1 -0
  101. package/dist/esm/core/plugin-manager.d.ts +66 -0
  102. package/dist/esm/core/plugin-manager.js +148 -0
  103. package/dist/esm/core/plugin-manager.js.map +1 -0
  104. package/dist/esm/core/security.d.ts +93 -0
  105. package/dist/esm/core/security.js +315 -0
  106. package/dist/esm/core/security.js.map +1 -0
  107. package/dist/esm/definitions.d.ts +639 -0
  108. package/dist/esm/definitions.js +103 -0
  109. package/dist/esm/definitions.js.map +1 -0
  110. package/dist/esm/index.d.ts +12 -0
  111. package/dist/esm/index.js +16 -0
  112. package/dist/esm/index.js.map +1 -0
  113. package/dist/esm/live-update/bundle-manager.d.ts +94 -0
  114. package/dist/esm/live-update/bundle-manager.js +310 -0
  115. package/dist/esm/live-update/bundle-manager.js.map +1 -0
  116. package/dist/esm/live-update/certificate-pinning.d.ts +38 -0
  117. package/dist/esm/live-update/certificate-pinning.js +78 -0
  118. package/dist/esm/live-update/certificate-pinning.js.map +1 -0
  119. package/dist/esm/live-update/download-manager.d.ts +67 -0
  120. package/dist/esm/live-update/download-manager.js +319 -0
  121. package/dist/esm/live-update/download-manager.js.map +1 -0
  122. package/dist/esm/live-update/update-manager.d.ts +52 -0
  123. package/dist/esm/live-update/update-manager.js +294 -0
  124. package/dist/esm/live-update/update-manager.js.map +1 -0
  125. package/dist/esm/live-update/version-manager.d.ts +84 -0
  126. package/dist/esm/live-update/version-manager.js +335 -0
  127. package/dist/esm/live-update/version-manager.js.map +1 -0
  128. package/dist/esm/plugin.d.ts +6 -0
  129. package/dist/esm/plugin.js +283 -0
  130. package/dist/esm/plugin.js.map +1 -0
  131. package/dist/esm/security/crypto.d.ts +25 -0
  132. package/dist/esm/security/crypto.js +70 -0
  133. package/dist/esm/security/crypto.js.map +1 -0
  134. package/dist/esm/security/validator.d.ts +60 -0
  135. package/dist/esm/security/validator.js +143 -0
  136. package/dist/esm/security/validator.js.map +1 -0
  137. package/dist/esm/web.d.ts +74 -0
  138. package/dist/esm/web.js +595 -0
  139. package/dist/esm/web.js.map +1 -0
  140. package/dist/plugin.cjs.js +2 -0
  141. package/dist/plugin.cjs.js.map +1 -0
  142. package/dist/plugin.esm.js +2 -0
  143. package/dist/plugin.esm.js.map +1 -0
  144. package/dist/plugin.js +3 -0
  145. package/dist/plugin.js.map +1 -0
  146. package/docs/APP_REVIEW_GUIDE.md +768 -0
  147. package/docs/BUNDLE_SIGNING.md +264 -0
  148. package/docs/LIVE_UPDATES_GUIDE.md +650 -0
  149. package/docs/MIGRATION.md +192 -0
  150. package/docs/NATIVE_UPDATES_GUIDE.md +694 -0
  151. package/docs/QUICK_START.md +606 -0
  152. package/docs/README.md +111 -0
  153. package/docs/REMAINING_FEATURES.md +139 -0
  154. package/docs/api/app-review-api.md +259 -0
  155. package/docs/api/app-update-api.md +238 -0
  156. package/docs/api/events-api.md +451 -0
  157. package/docs/api/live-update-api.md +265 -0
  158. package/docs/background-updates.md +392 -0
  159. package/docs/examples/advanced-scenarios.md +410 -0
  160. package/docs/examples/basic-usage.md +185 -0
  161. package/docs/features/app-reviews.md +975 -0
  162. package/docs/features/app-updates.md +785 -0
  163. package/docs/features/live-updates.md +633 -0
  164. package/docs/getting-started/configuration.md +468 -0
  165. package/docs/getting-started/installation.md +209 -0
  166. package/docs/getting-started/quick-start.md +379 -0
  167. package/docs/guides/deployment-guide.md +333 -0
  168. package/docs/guides/migration-from-codepush.md +142 -0
  169. package/docs/guides/security-best-practices.md +1057 -0
  170. package/docs/guides/testing-guide.md +373 -0
  171. package/docs/production-readiness.md +478 -0
  172. package/docs/security/certificate-pinning.md +122 -0
  173. package/docs/server-requirements.md +147 -0
  174. package/ios/Plugin/AppReview/AppReviewPlugin.swift +158 -0
  175. package/ios/Plugin/AppUpdate/AppUpdatePlugin.swift +234 -0
  176. package/ios/Plugin/BackgroundUpdate/BackgroundNotificationManager.swift +329 -0
  177. package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +396 -0
  178. package/ios/Plugin/CapacitorNativeUpdatePlugin.m +45 -0
  179. package/ios/Plugin/CapacitorNativeUpdatePlugin.swift +190 -0
  180. package/ios/Plugin/Info.plist +43 -0
  181. package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +689 -0
  182. package/ios/Plugin/LiveUpdate/WebViewConfiguration.swift +45 -0
  183. package/ios/Plugin/Security/SecurityManager.swift +289 -0
  184. package/package.json +90 -0
@@ -0,0 +1,1057 @@
1
+ # Security Best Practices
2
+
3
+ Security is paramount when implementing update mechanisms in mobile applications. This guide covers comprehensive security measures to protect your app and users from potential threats while maintaining a seamless update experience.
4
+
5
+ ## Security Overview
6
+
7
+ The plugin implements multiple layers of security:
8
+
9
+ - **Transport Security**: HTTPS enforcement and certificate pinning
10
+ - **Content Security**: Cryptographic signatures and checksums
11
+ - **Input Validation**: Sanitization and validation of all inputs
12
+ - **Storage Security**: Secure storage of sensitive data
13
+ - **Access Control**: Permission-based security model
14
+
15
+ ## Core Security Principles
16
+
17
+ ### 1. Never Trust Input
18
+
19
+ ```typescript
20
+ // Always validate and sanitize inputs
21
+ class SecurityValidator {
22
+ static validateVersion(version: string): boolean {
23
+ // Semantic version regex
24
+ const versionRegex =
25
+ /^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/;
26
+
27
+ if (!versionRegex.test(version)) {
28
+ throw new Error('Invalid version format');
29
+ }
30
+
31
+ // Check for suspicious patterns
32
+ if (
33
+ version.includes('..') ||
34
+ version.includes('/') ||
35
+ version.includes('\\')
36
+ ) {
37
+ throw new Error('Version contains invalid characters');
38
+ }
39
+
40
+ return true;
41
+ }
42
+
43
+ static validateUrl(url: string): boolean {
44
+ try {
45
+ const parsed = new URL(url);
46
+
47
+ // Only allow HTTPS in production
48
+ if (
49
+ parsed.protocol !== 'https:' &&
50
+ process.env.NODE_ENV === 'production'
51
+ ) {
52
+ throw new Error('Only HTTPS URLs are allowed in production');
53
+ }
54
+
55
+ // Check against allowlist
56
+ if (!this.isAllowedHost(parsed.hostname)) {
57
+ throw new Error('Host not in allowlist');
58
+ }
59
+
60
+ return true;
61
+ } catch (error) {
62
+ throw new Error(`Invalid URL: ${error.message}`);
63
+ }
64
+ }
65
+
66
+ static validateBundleId(bundleId: string): boolean {
67
+ // Bundle ID should be alphanumeric with hyphens
68
+ const bundleRegex = /^[a-zA-Z0-9-]+$/;
69
+
70
+ if (!bundleRegex.test(bundleId)) {
71
+ throw new Error('Invalid bundle ID format');
72
+ }
73
+
74
+ // Prevent path traversal
75
+ if (bundleId.includes('..') || bundleId.includes('/')) {
76
+ throw new Error('Bundle ID contains invalid characters');
77
+ }
78
+
79
+ return true;
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### 2. Implement Defense in Depth
85
+
86
+ ```typescript
87
+ // Multiple security layers
88
+ class SecurityManager {
89
+ async validateUpdate(bundle: BundleInfo): Promise<ValidationResult> {
90
+ const validations = [
91
+ () => this.validateChecksum(bundle),
92
+ () => this.validateSignature(bundle),
93
+ () => this.validateSize(bundle),
94
+ () => this.validateMetadata(bundle),
95
+ () => this.validateContent(bundle),
96
+ ];
97
+
98
+ for (const validation of validations) {
99
+ const result = await validation();
100
+ if (!result.valid) {
101
+ return {
102
+ valid: false,
103
+ reason: result.reason,
104
+ securityLevel: 'HIGH',
105
+ };
106
+ }
107
+ }
108
+
109
+ return { valid: true };
110
+ }
111
+
112
+ private async validateChecksum(
113
+ bundle: BundleInfo
114
+ ): Promise<ValidationResult> {
115
+ try {
116
+ const calculatedHash = await this.calculateChecksum(bundle.path);
117
+
118
+ if (calculatedHash !== bundle.checksum) {
119
+ // Log security event
120
+ await this.logSecurityEvent('CHECKSUM_MISMATCH', {
121
+ bundleId: bundle.bundleId,
122
+ expected: bundle.checksum,
123
+ actual: calculatedHash,
124
+ });
125
+
126
+ return {
127
+ valid: false,
128
+ reason: 'Checksum validation failed',
129
+ };
130
+ }
131
+
132
+ return { valid: true };
133
+ } catch (error) {
134
+ return {
135
+ valid: false,
136
+ reason: `Checksum validation error: ${error.message}`,
137
+ };
138
+ }
139
+ }
140
+
141
+ private async validateSignature(
142
+ bundle: BundleInfo
143
+ ): Promise<ValidationResult> {
144
+ if (!bundle.signature) {
145
+ return {
146
+ valid: false,
147
+ reason: 'No signature provided',
148
+ };
149
+ }
150
+
151
+ try {
152
+ const publicKey = await this.getPublicKey();
153
+ const verified = await this.verifySignature(bundle, publicKey);
154
+
155
+ if (!verified) {
156
+ await this.logSecurityEvent('SIGNATURE_VERIFICATION_FAILED', {
157
+ bundleId: bundle.bundleId,
158
+ });
159
+
160
+ return {
161
+ valid: false,
162
+ reason: 'Signature verification failed',
163
+ };
164
+ }
165
+
166
+ return { valid: true };
167
+ } catch (error) {
168
+ return {
169
+ valid: false,
170
+ reason: `Signature validation error: ${error.message}`,
171
+ };
172
+ }
173
+ }
174
+ }
175
+ ```
176
+
177
+ ## Transport Security
178
+
179
+ ### HTTPS Enforcement
180
+
181
+ ```typescript
182
+ // Always use HTTPS in production
183
+ const secureConfig = {
184
+ liveUpdate: {
185
+ serverUrl: 'https://updates.yourserver.com', // Never use HTTP
186
+ enforceHttps: true,
187
+ },
188
+ security: {
189
+ enforceHttps: true,
190
+ allowInsecureConnections: false, // Only for development
191
+ },
192
+ };
193
+
194
+ // URL validation
195
+ class TransportSecurity {
196
+ static validateUpdateUrl(url: string): boolean {
197
+ const parsed = new URL(url);
198
+
199
+ // Enforce HTTPS
200
+ if (parsed.protocol !== 'https:') {
201
+ throw new Error('Only HTTPS URLs are allowed');
202
+ }
203
+
204
+ // Check for suspicious patterns
205
+ if (
206
+ url.includes('..') ||
207
+ (url.includes('localhost') && process.env.NODE_ENV === 'production')
208
+ ) {
209
+ throw new Error('Suspicious URL pattern detected');
210
+ }
211
+
212
+ return true;
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Certificate Pinning
218
+
219
+ ```typescript
220
+ // Implement certificate pinning for maximum security
221
+ const certificatePinningConfig = {
222
+ security: {
223
+ certificatePinning: {
224
+ enabled: true,
225
+ certificates: [
226
+ // SHA-256 hash of your server's certificate
227
+ 'sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=',
228
+ // Backup certificate
229
+ 'sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=',
230
+ ],
231
+ includeSubdomains: true,
232
+ maxAge: 31536000, // 1 year
233
+ reportUri: 'https://yourserver.com/hpkp-report',
234
+ },
235
+ },
236
+ };
237
+
238
+ // Certificate validation
239
+ class CertificatePinning {
240
+ private pinnedCertificates: string[] = [];
241
+
242
+ async validateCertificate(
243
+ hostname: string,
244
+ certificate: string
245
+ ): Promise<boolean> {
246
+ // Calculate SHA-256 hash of certificate
247
+ const certHash = await this.calculateSHA256(certificate);
248
+ const formattedHash = `sha256/${certHash}`;
249
+
250
+ // Check against pinned certificates
251
+ if (!this.pinnedCertificates.includes(formattedHash)) {
252
+ // Log security incident
253
+ await this.logSecurityIncident('CERTIFICATE_PIN_MISMATCH', {
254
+ hostname,
255
+ expectedCerts: this.pinnedCertificates,
256
+ actualCert: formattedHash,
257
+ });
258
+
259
+ return false;
260
+ }
261
+
262
+ return true;
263
+ }
264
+ }
265
+ ```
266
+
267
+ ## Content Security
268
+
269
+ ### Cryptographic Signatures
270
+
271
+ ```typescript
272
+ // Implement RSA-PSS signature verification
273
+ class SignatureVerification {
274
+ private publicKey: CryptoKey | null = null;
275
+
276
+ async initialize(publicKeyPem: string) {
277
+ try {
278
+ // Import public key
279
+ this.publicKey = await crypto.subtle.importKey(
280
+ 'spki',
281
+ this.pemToArrayBuffer(publicKeyPem),
282
+ {
283
+ name: 'RSA-PSS',
284
+ hash: 'SHA-256',
285
+ },
286
+ false,
287
+ ['verify']
288
+ );
289
+ } catch (error) {
290
+ throw new Error(`Failed to import public key: ${error.message}`);
291
+ }
292
+ }
293
+
294
+ async verifyBundle(bundle: BundleInfo): Promise<boolean> {
295
+ if (!this.publicKey) {
296
+ throw new Error('Public key not initialized');
297
+ }
298
+
299
+ if (!bundle.signature) {
300
+ throw new Error('No signature provided');
301
+ }
302
+
303
+ try {
304
+ // Create message to verify (bundle metadata + content hash)
305
+ const message = this.createSignatureMessage(bundle);
306
+ const messageBuffer = new TextEncoder().encode(message);
307
+
308
+ // Decode signature
309
+ const signatureBuffer = this.base64ToArrayBuffer(bundle.signature);
310
+
311
+ // Verify signature
312
+ const verified = await crypto.subtle.verify(
313
+ {
314
+ name: 'RSA-PSS',
315
+ saltLength: 32,
316
+ },
317
+ this.publicKey,
318
+ signatureBuffer,
319
+ messageBuffer
320
+ );
321
+
322
+ return verified;
323
+ } catch (error) {
324
+ console.error('Signature verification failed:', error);
325
+ return false;
326
+ }
327
+ }
328
+
329
+ private createSignatureMessage(bundle: BundleInfo): string {
330
+ // Create deterministic message for signature
331
+ return JSON.stringify({
332
+ bundleId: bundle.bundleId,
333
+ version: bundle.version,
334
+ checksum: bundle.checksum,
335
+ size: bundle.size,
336
+ timestamp: bundle.downloadTime,
337
+ });
338
+ }
339
+ }
340
+ ```
341
+
342
+ ### Checksum Validation
343
+
344
+ ```typescript
345
+ // Implement strong checksum validation
346
+ class ChecksumValidator {
347
+ async validateBundle(bundle: BundleInfo): Promise<boolean> {
348
+ try {
349
+ // Calculate SHA-512 checksum
350
+ const calculatedChecksum = await this.calculateSHA512(bundle.path);
351
+
352
+ // Compare with provided checksum
353
+ if (calculatedChecksum !== bundle.checksum) {
354
+ await this.logSecurityEvent('CHECKSUM_MISMATCH', {
355
+ bundleId: bundle.bundleId,
356
+ expected: bundle.checksum,
357
+ calculated: calculatedChecksum,
358
+ });
359
+
360
+ return false;
361
+ }
362
+
363
+ return true;
364
+ } catch (error) {
365
+ console.error('Checksum validation failed:', error);
366
+ return false;
367
+ }
368
+ }
369
+
370
+ private async calculateSHA512(filePath: string): Promise<string> {
371
+ // Platform-specific implementation
372
+ if (this.isWeb()) {
373
+ return this.calculateWebChecksum(filePath);
374
+ } else {
375
+ return this.calculateNativeChecksum(filePath);
376
+ }
377
+ }
378
+
379
+ private async calculateWebChecksum(filePath: string): Promise<string> {
380
+ const response = await fetch(filePath);
381
+ const buffer = await response.arrayBuffer();
382
+
383
+ const hashBuffer = await crypto.subtle.digest('SHA-512', buffer);
384
+ return this.arrayBufferToHex(hashBuffer);
385
+ }
386
+ }
387
+ ```
388
+
389
+ ## Input Validation and Sanitization
390
+
391
+ ### Comprehensive Input Validation
392
+
393
+ ```typescript
394
+ class InputValidator {
395
+ static validateUpdateRequest(request: UpdateRequest): ValidationResult {
396
+ const validations = [
397
+ () => this.validateVersion(request.version),
398
+ () => this.validateChannel(request.channel),
399
+ () => this.validateAppId(request.appId),
400
+ () => this.validateHeaders(request.headers),
401
+ () => this.validateParameters(request.parameters),
402
+ ];
403
+
404
+ for (const validation of validations) {
405
+ const result = validation();
406
+ if (!result.valid) {
407
+ return result;
408
+ }
409
+ }
410
+
411
+ return { valid: true };
412
+ }
413
+
414
+ private static validateVersion(version: string): ValidationResult {
415
+ // Check format
416
+ if (!version || typeof version !== 'string') {
417
+ return { valid: false, reason: 'Version must be a string' };
418
+ }
419
+
420
+ // Check length
421
+ if (version.length > 50) {
422
+ return { valid: false, reason: 'Version too long' };
423
+ }
424
+
425
+ // Check semantic version format
426
+ const semverRegex =
427
+ /^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/;
428
+ if (!semverRegex.test(version)) {
429
+ return { valid: false, reason: 'Invalid semantic version format' };
430
+ }
431
+
432
+ // Check for dangerous patterns
433
+ const dangerousPatterns = ['..', '/', '\\', '<', '>', '&', '"', "'"];
434
+ for (const pattern of dangerousPatterns) {
435
+ if (version.includes(pattern)) {
436
+ return {
437
+ valid: false,
438
+ reason: 'Version contains dangerous characters',
439
+ };
440
+ }
441
+ }
442
+
443
+ return { valid: true };
444
+ }
445
+
446
+ private static validateChannel(channel: string): ValidationResult {
447
+ // Allowlist of valid channels
448
+ const validChannels = [
449
+ 'production',
450
+ 'staging',
451
+ 'beta',
452
+ 'alpha',
453
+ 'development',
454
+ ];
455
+
456
+ if (!validChannels.includes(channel)) {
457
+ return { valid: false, reason: 'Invalid channel' };
458
+ }
459
+
460
+ return { valid: true };
461
+ }
462
+
463
+ private static validateAppId(appId: string): ValidationResult {
464
+ // App ID format: com.company.app
465
+ const appIdRegex = /^[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*$/;
466
+
467
+ if (!appIdRegex.test(appId)) {
468
+ return { valid: false, reason: 'Invalid app ID format' };
469
+ }
470
+
471
+ // Check length
472
+ if (appId.length > 100) {
473
+ return { valid: false, reason: 'App ID too long' };
474
+ }
475
+
476
+ return { valid: true };
477
+ }
478
+ }
479
+ ```
480
+
481
+ ### SQL Injection Prevention
482
+
483
+ ```typescript
484
+ // Prevent SQL injection in server-side queries
485
+ class DatabaseSecurity {
486
+ async getUpdateInfo(
487
+ appId: string,
488
+ version: string
489
+ ): Promise<UpdateInfo | null> {
490
+ // Use parameterized queries
491
+ const query = `
492
+ SELECT * FROM app_updates
493
+ WHERE app_id = ? AND version = ?
494
+ ORDER BY created_at DESC
495
+ LIMIT 1
496
+ `;
497
+
498
+ // Validate inputs before query
499
+ if (!InputValidator.validateAppId(appId).valid) {
500
+ throw new Error('Invalid app ID');
501
+ }
502
+
503
+ if (!InputValidator.validateVersion(version).valid) {
504
+ throw new Error('Invalid version');
505
+ }
506
+
507
+ // Execute with parameters
508
+ const result = await this.database.query(query, [appId, version]);
509
+ return result[0] || null;
510
+ }
511
+ }
512
+ ```
513
+
514
+ ## Storage Security
515
+
516
+ ### Secure Key Management
517
+
518
+ ```typescript
519
+ class SecureKeyManager {
520
+ private static readonly KEY_ALIAS = 'update_keys';
521
+
522
+ async storePrivateKey(privateKey: string): Promise<void> {
523
+ if (this.isAndroid()) {
524
+ // Use Android Keystore
525
+ await this.storeInAndroidKeystore(privateKey);
526
+ } else if (this.isIOS()) {
527
+ // Use iOS Keychain
528
+ await this.storeInKeychainServices(privateKey);
529
+ } else {
530
+ // Web - use secure storage if available
531
+ await this.storeInSecureStorage(privateKey);
532
+ }
533
+ }
534
+
535
+ async getPrivateKey(): Promise<string | null> {
536
+ try {
537
+ if (this.isAndroid()) {
538
+ return await this.getFromAndroidKeystore();
539
+ } else if (this.isIOS()) {
540
+ return await this.getFromKeychainServices();
541
+ } else {
542
+ return await this.getFromSecureStorage();
543
+ }
544
+ } catch (error) {
545
+ console.error('Failed to retrieve private key:', error);
546
+ return null;
547
+ }
548
+ }
549
+
550
+ private async storeInAndroidKeystore(privateKey: string): Promise<void> {
551
+ // Use Android Keystore for secure storage
552
+ const keyStore = await this.getAndroidKeyStore();
553
+ await keyStore.store(this.KEY_ALIAS, privateKey, {
554
+ requireAuthentication: true,
555
+ encryptionRequired: true,
556
+ });
557
+ }
558
+
559
+ private async storeInKeychainServices(privateKey: string): Promise<void> {
560
+ // Use iOS Keychain Services
561
+ const keychain = await this.getIOSKeychain();
562
+ await keychain.store(this.KEY_ALIAS, privateKey, {
563
+ accessibility: 'kSecAttrAccessibleWhenUnlockedThisDeviceOnly',
564
+ synchronizable: false,
565
+ });
566
+ }
567
+ }
568
+ ```
569
+
570
+ ### Secure Bundle Storage
571
+
572
+ ```typescript
573
+ class SecureBundleStorage {
574
+ private readonly BUNDLE_DIR = 'secure_bundles';
575
+
576
+ async storeBundleSecurely(
577
+ bundle: BundleInfo,
578
+ content: ArrayBuffer
579
+ ): Promise<string> {
580
+ // Generate unique file name
581
+ const fileName = await this.generateSecureFileName(bundle);
582
+ const filePath = path.join(this.BUNDLE_DIR, fileName);
583
+
584
+ // Encrypt content before storage
585
+ const encryptedContent = await this.encryptBundle(content);
586
+
587
+ // Store with proper permissions
588
+ await this.storeWithRestrictedPermissions(filePath, encryptedContent);
589
+
590
+ // Create metadata file
591
+ await this.createMetadataFile(bundle, fileName);
592
+
593
+ return filePath;
594
+ }
595
+
596
+ private async encryptBundle(content: ArrayBuffer): Promise<ArrayBuffer> {
597
+ // Generate random encryption key
598
+ const key = await crypto.subtle.generateKey(
599
+ { name: 'AES-GCM', length: 256 },
600
+ false,
601
+ ['encrypt', 'decrypt']
602
+ );
603
+
604
+ // Generate random IV
605
+ const iv = crypto.getRandomValues(new Uint8Array(12));
606
+
607
+ // Encrypt content
608
+ const encrypted = await crypto.subtle.encrypt(
609
+ { name: 'AES-GCM', iv },
610
+ key,
611
+ content
612
+ );
613
+
614
+ // Store key securely
615
+ await this.storeEncryptionKey(key);
616
+
617
+ // Combine IV and encrypted content
618
+ const result = new Uint8Array(iv.length + encrypted.byteLength);
619
+ result.set(iv);
620
+ result.set(new Uint8Array(encrypted), iv.length);
621
+
622
+ return result.buffer;
623
+ }
624
+
625
+ private async storeWithRestrictedPermissions(
626
+ filePath: string,
627
+ content: ArrayBuffer
628
+ ): Promise<void> {
629
+ // Platform-specific secure storage
630
+ if (this.isAndroid()) {
631
+ await this.storeAndroidSecure(filePath, content);
632
+ } else if (this.isIOS()) {
633
+ await this.storeIOSSecure(filePath, content);
634
+ } else {
635
+ await this.storeWebSecure(filePath, content);
636
+ }
637
+ }
638
+ }
639
+ ```
640
+
641
+ ## Access Control and Permissions
642
+
643
+ ### Permission-Based Security
644
+
645
+ ```typescript
646
+ class PermissionManager {
647
+ private permissions: Map<string, Permission> = new Map();
648
+
649
+ async requestUpdatePermission(context: UpdateContext): Promise<boolean> {
650
+ // Check if permission is already granted
651
+ if (await this.hasPermission('update', context)) {
652
+ return true;
653
+ }
654
+
655
+ // Request permission based on platform
656
+ if (this.isAndroid()) {
657
+ return await this.requestAndroidPermission(context);
658
+ } else if (this.isIOS()) {
659
+ return await this.requestIOSPermission(context);
660
+ } else {
661
+ return await this.requestWebPermission(context);
662
+ }
663
+ }
664
+
665
+ private async requestAndroidPermission(
666
+ context: UpdateContext
667
+ ): Promise<boolean> {
668
+ // Request necessary Android permissions
669
+ const permissions = [
670
+ 'android.permission.INTERNET',
671
+ 'android.permission.ACCESS_NETWORK_STATE',
672
+ 'android.permission.WRITE_EXTERNAL_STORAGE',
673
+ ];
674
+
675
+ for (const permission of permissions) {
676
+ const granted = await this.requestSystemPermission(permission);
677
+ if (!granted) {
678
+ return false;
679
+ }
680
+ }
681
+
682
+ return true;
683
+ }
684
+
685
+ async validatePermission(
686
+ action: string,
687
+ context: UpdateContext
688
+ ): Promise<boolean> {
689
+ const permission = this.permissions.get(action);
690
+
691
+ if (!permission) {
692
+ throw new Error(`Permission not found: ${action}`);
693
+ }
694
+
695
+ // Check conditions
696
+ for (const condition of permission.conditions) {
697
+ if (!(await condition.check(context))) {
698
+ return false;
699
+ }
700
+ }
701
+
702
+ return true;
703
+ }
704
+ }
705
+ ```
706
+
707
+ ## Security Monitoring and Logging
708
+
709
+ ### Security Event Logging
710
+
711
+ ```typescript
712
+ class SecurityLogger {
713
+ private logLevel: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' = 'INFO';
714
+
715
+ async logSecurityEvent(eventType: string, details: any): Promise<void> {
716
+ const event = {
717
+ timestamp: new Date().toISOString(),
718
+ type: eventType,
719
+ severity: this.getSeverity(eventType),
720
+ details: this.sanitizeDetails(details),
721
+ deviceInfo: await this.getDeviceInfo(),
722
+ appVersion: await this.getAppVersion(),
723
+ userId: await this.getUserId(), // Hash or anonymize
724
+ };
725
+
726
+ // Store locally
727
+ await this.storeLogLocally(event);
728
+
729
+ // Send to security monitoring service
730
+ if (event.severity === 'HIGH' || event.severity === 'CRITICAL') {
731
+ await this.sendToMonitoringService(event);
732
+ }
733
+ }
734
+
735
+ private getSeverity(
736
+ eventType: string
737
+ ): 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' {
738
+ const severityMap = {
739
+ CHECKSUM_MISMATCH: 'HIGH',
740
+ SIGNATURE_VERIFICATION_FAILED: 'CRITICAL',
741
+ CERTIFICATE_PIN_MISMATCH: 'CRITICAL',
742
+ INVALID_INPUT: 'MEDIUM',
743
+ PERMISSION_DENIED: 'HIGH',
744
+ SUSPICIOUS_ACTIVITY: 'HIGH',
745
+ RATE_LIMIT_EXCEEDED: 'MEDIUM',
746
+ };
747
+
748
+ return severityMap[eventType] || 'LOW';
749
+ }
750
+
751
+ private sanitizeDetails(details: any): any {
752
+ // Remove sensitive information
753
+ const sanitized = { ...details };
754
+
755
+ const sensitiveKeys = ['password', 'token', 'key', 'secret', 'privateKey'];
756
+
757
+ for (const key of sensitiveKeys) {
758
+ if (key in sanitized) {
759
+ sanitized[key] = '[REDACTED]';
760
+ }
761
+ }
762
+
763
+ return sanitized;
764
+ }
765
+ }
766
+ ```
767
+
768
+ ### Intrusion Detection
769
+
770
+ ```typescript
771
+ class IntrusionDetection {
772
+ private suspiciousActivity: Map<string, number> = new Map();
773
+ private readonly MAX_ATTEMPTS = 5;
774
+ private readonly TIME_WINDOW = 300000; // 5 minutes
775
+
776
+ async checkSuspiciousActivity(
777
+ clientId: string,
778
+ action: string
779
+ ): Promise<boolean> {
780
+ const key = `${clientId}:${action}`;
781
+ const attempts = this.suspiciousActivity.get(key) || 0;
782
+
783
+ if (attempts >= this.MAX_ATTEMPTS) {
784
+ await this.logSecurityEvent('RATE_LIMIT_EXCEEDED', {
785
+ clientId,
786
+ action,
787
+ attempts,
788
+ });
789
+
790
+ return true; // Suspicious
791
+ }
792
+
793
+ // Increment attempts
794
+ this.suspiciousActivity.set(key, attempts + 1);
795
+
796
+ // Reset after time window
797
+ setTimeout(() => {
798
+ this.suspiciousActivity.delete(key);
799
+ }, this.TIME_WINDOW);
800
+
801
+ return false;
802
+ }
803
+
804
+ async detectAnomalies(updateRequest: UpdateRequest): Promise<boolean> {
805
+ const anomalies = [
806
+ this.checkFrequency(updateRequest),
807
+ this.checkRequestPatterns(updateRequest),
808
+ this.checkGeolocation(updateRequest),
809
+ this.checkDeviceFingerprint(updateRequest),
810
+ ];
811
+
812
+ const suspiciousCount = (await Promise.all(anomalies)).filter(
813
+ Boolean
814
+ ).length;
815
+
816
+ if (suspiciousCount >= 2) {
817
+ await this.logSecurityEvent('SUSPICIOUS_ACTIVITY', {
818
+ anomalies: suspiciousCount,
819
+ request: updateRequest,
820
+ });
821
+
822
+ return true;
823
+ }
824
+
825
+ return false;
826
+ }
827
+ }
828
+ ```
829
+
830
+ ## Incident Response
831
+
832
+ ### Security Incident Handling
833
+
834
+ ```typescript
835
+ class SecurityIncidentHandler {
836
+ async handleSecurityIncident(incident: SecurityIncident): Promise<void> {
837
+ // Classify incident
838
+ const classification = this.classifyIncident(incident);
839
+
840
+ // Immediate response
841
+ await this.immediateResponse(incident, classification);
842
+
843
+ // Investigate
844
+ await this.investigate(incident);
845
+
846
+ // Remediate
847
+ await this.remediate(incident);
848
+
849
+ // Document
850
+ await this.documentIncident(incident);
851
+ }
852
+
853
+ private async immediateResponse(
854
+ incident: SecurityIncident,
855
+ classification: IncidentClassification
856
+ ): Promise<void> {
857
+ switch (classification.severity) {
858
+ case 'CRITICAL':
859
+ // Disable updates immediately
860
+ await this.disableUpdates();
861
+
862
+ // Notify security team
863
+ await this.notifySecurityTeam(incident);
864
+
865
+ // Isolate affected systems
866
+ await this.isolateAffectedSystems(incident);
867
+ break;
868
+
869
+ case 'HIGH':
870
+ // Increase monitoring
871
+ await this.increaseMonitoring();
872
+
873
+ // Review recent updates
874
+ await this.reviewRecentUpdates();
875
+ break;
876
+
877
+ case 'MEDIUM':
878
+ // Log and monitor
879
+ await this.logAndMonitor(incident);
880
+ break;
881
+ }
882
+ }
883
+
884
+ private async disableUpdates(): Promise<void> {
885
+ // Disable all update channels
886
+ await this.updateServerConfig({
887
+ updatesEnabled: false,
888
+ reason: 'Security incident response',
889
+ });
890
+
891
+ // Notify all clients
892
+ await this.broadcastSecurityAlert({
893
+ type: 'UPDATES_DISABLED',
894
+ message: 'Updates temporarily disabled due to security incident',
895
+ });
896
+ }
897
+
898
+ private async remediate(incident: SecurityIncident): Promise<void> {
899
+ switch (incident.type) {
900
+ case 'COMPROMISED_SIGNING_KEY':
901
+ await this.rotateSigningKeys();
902
+ await this.revokeCompromisedCertificates();
903
+ break;
904
+
905
+ case 'MALICIOUS_UPDATE':
906
+ await this.rollbackToSafeVersion();
907
+ await this.blacklistMaliciousBundle();
908
+ break;
909
+
910
+ case 'SERVER_COMPROMISE':
911
+ await this.isolateServers();
912
+ await this.rebuildeFromCleanBackup();
913
+ break;
914
+ }
915
+ }
916
+ }
917
+ ```
918
+
919
+ ## Production Security Checklist
920
+
921
+ ### Pre-Deployment Security Audit
922
+
923
+ ```typescript
924
+ class SecurityAudit {
925
+ async performSecurityAudit(): Promise<AuditResult> {
926
+ const checks = [
927
+ this.checkCryptographicImplementation(),
928
+ this.checkInputValidation(),
929
+ this.checkTransportSecurity(),
930
+ this.checkStorageSecurity(),
931
+ this.checkPermissions(),
932
+ this.checkLogging(),
933
+ this.checkIncidentResponse(),
934
+ ];
935
+
936
+ const results = await Promise.all(checks);
937
+
938
+ return {
939
+ passed: results.every((r) => r.passed),
940
+ results,
941
+ recommendations: this.generateRecommendations(results),
942
+ };
943
+ }
944
+
945
+ private async checkCryptographicImplementation(): Promise<CheckResult> {
946
+ const checks = [
947
+ this.verifySignatureAlgorithm(),
948
+ this.verifyHashAlgorithm(),
949
+ this.verifyKeyLength(),
950
+ this.verifyRandomness(),
951
+ ];
952
+
953
+ const results = await Promise.all(checks);
954
+
955
+ return {
956
+ category: 'Cryptographic Implementation',
957
+ passed: results.every((r) => r.passed),
958
+ details: results,
959
+ };
960
+ }
961
+
962
+ private async checkInputValidation(): Promise<CheckResult> {
963
+ const testCases = [
964
+ { input: '../../../etc/passwd', expected: 'rejected' },
965
+ { input: '<script>alert("xss")</script>', expected: 'rejected' },
966
+ { input: 'DROP TABLE users;', expected: 'rejected' },
967
+ { input: 'valid-version-1.0.0', expected: 'accepted' },
968
+ ];
969
+
970
+ const results = [];
971
+
972
+ for (const testCase of testCases) {
973
+ const result = await this.testInputValidation(testCase.input);
974
+ results.push({
975
+ input: testCase.input,
976
+ expected: testCase.expected,
977
+ actual: result,
978
+ passed: result === testCase.expected,
979
+ });
980
+ }
981
+
982
+ return {
983
+ category: 'Input Validation',
984
+ passed: results.every((r) => r.passed),
985
+ details: results,
986
+ };
987
+ }
988
+ }
989
+ ```
990
+
991
+ ### Security Configuration Template
992
+
993
+ ```typescript
994
+ // Production security configuration
995
+ const productionSecurityConfig = {
996
+ liveUpdate: {
997
+ // Enable all security features
998
+ requireSignature: true,
999
+ checksumAlgorithm: 'SHA-512',
1000
+ publicKey: process.env.UPDATE_PUBLIC_KEY,
1001
+ allowedHosts: ['updates.yourserver.com'],
1002
+ maxBundleSize: 50 * 1024 * 1024, // 50MB
1003
+ allowEmulator: false,
1004
+ },
1005
+
1006
+ security: {
1007
+ // Transport security
1008
+ enforceHttps: true,
1009
+ certificatePinning: {
1010
+ enabled: true,
1011
+ certificates: [
1012
+ process.env.CERT_HASH_PRIMARY,
1013
+ process.env.CERT_HASH_BACKUP,
1014
+ ],
1015
+ },
1016
+
1017
+ // Input validation
1018
+ validateInputs: true,
1019
+ sanitizeInputs: true,
1020
+
1021
+ // Storage security
1022
+ secureStorage: true,
1023
+ encryptBundles: true,
1024
+
1025
+ // Monitoring
1026
+ logSecurityEvents: true,
1027
+ enableIntrusionDetection: true,
1028
+
1029
+ // Rate limiting
1030
+ enableRateLimit: true,
1031
+ maxRequestsPerMinute: 60,
1032
+ },
1033
+
1034
+ monitoring: {
1035
+ // Security monitoring
1036
+ securityEventEndpoint: process.env.SECURITY_MONITOR_URL,
1037
+ alertThreshold: 'HIGH',
1038
+
1039
+ // Performance monitoring
1040
+ performanceEndpoint: process.env.PERFORMANCE_MONITOR_URL,
1041
+
1042
+ // Error tracking
1043
+ errorTrackingEndpoint: process.env.ERROR_TRACKING_URL,
1044
+ },
1045
+ };
1046
+ ```
1047
+
1048
+ ## Next Steps
1049
+
1050
+ - Review [Production Readiness](../production-readiness.md) checklist
1051
+ - Implement [Monitoring and Analytics](../examples/monitoring-setup.md)
1052
+ - Set up [Incident Response](../guides/incident-response.md) procedures
1053
+ - Configure [Update Server Security](../examples/server-security.md)
1054
+
1055
+ ---
1056
+
1057
+ Made with ❤️ by Ahsan Mahmood