native-update
Version:
Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.
357 lines (279 loc) • 9.27 kB
Markdown
This document outlines the security measures and best practices implemented in the Capacitor Native Update plugin, following the official [Capacitor Security Guidelines](https://capacitorjs.com/docs/guides/security).
- [Security Principles](
- [Secure Update Process](
- [Platform-Specific Security](
- [Data Protection](
- [Network Security](
- [Input Validation](
- [Permission Management](
- [Security Configuration](
- [Security Checklist](
- **Never** embed API keys, encryption keys, or certificates in the plugin code
- All sensitive configuration must be provided at runtime by the host application
- Use environment-specific configuration files that are not committed to version control
- Multiple layers of security validation
- Fail securely - deny by default
- Comprehensive error handling without exposing sensitive information
- Request only necessary permissions
- Minimize access to system resources
- Isolate update processes from main application
```typescript
// Example of secure update verification
const verifyUpdate = async (bundle: UpdateBundle): Promise<boolean> => {
// 1. Verify HTTPS URL
if (!bundle.url.startsWith('https://')) {
throw new SecurityError('INSECURE_URL', 'Updates must use HTTPS');
}
// 2. Verify checksum
const calculatedChecksum = await calculateSHA256(bundle.data);
if (calculatedChecksum !== bundle.expectedChecksum) {
throw new SecurityError(
'INVALID_CHECKSUM',
'Bundle integrity check failed'
);
}
// 3. Verify signature (if configured)
if (config.requireSignature) {
const isValidSignature = await verifySignature(
bundle.data,
bundle.signature,
publicKey
);
if (!isValidSignature) {
throw new SecurityError(
'INVALID_SIGNATURE',
'Bundle signature verification failed'
);
}
}
return true;
};
```
- Prevent downgrade attacks by default
- Semantic version comparison
- Minimum native version requirements
```swift
// Store sensitive data in Keychain, not UserDefaults
let keychain = KeychainWrapper()
keychain.set(updateKey, forKey: "UpdateEncryptionKey")
```
```swift
// Validate file operations stay within app sandbox
guard let documentsPath = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first else {
throw UpdateError.invalidPath
}
let updatePath = documentsPath.appendingPathComponent("updates")
// Ensure path doesn't escape sandbox
guard updatePath.path.hasPrefix(documentsPath.path) else {
throw UpdateError.pathTraversal
}
```
```kotlin
// Use Android Keystore for sensitive data
val keyAlias = "UpdateKeyAlias"
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
// Generate or retrieve key
if (!keyStore.containsAlias(keyAlias)) {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).build()
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()
}
```
```java
@Permission(strings = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE
}, alias = "network")
@Permission(strings = {
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, alias = "storage")
public class NativeUpdatePlugin extends Plugin {
@PluginMethod
public void downloadUpdate(PluginCall call) {
if (!hasPermission("network")) {
requestPermissionForAlias("network", call, "handleNetworkPermission");
return;
}
// Proceed with download
}
}
```
1. **Sensitive Data**: Store in platform-specific secure storage (Keychain/Keystore)
2. **Update Metadata**: Store in app-specific directories with restricted permissions
3. **Temporary Files**: Use system temp directories and clean up immediately
### Encryption
```typescript
// Example encryption configuration
export interface EncryptionConfig {
algorithm: 'AES-256-GCM';
keyDerivation: 'PBKDF2';
iterations: 100000;
saltLength: 32;
}
```
```typescript
// Validate URLs before making requests
const validateUpdateUrl = (url: string): void => {
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== 'https:') {
throw new SecurityError('INSECURE_PROTOCOL', 'Only HTTPS URLs are allowed');
}
// Optional: Validate against whitelist
if (config.allowedHosts && !config.allowedHosts.includes(parsedUrl.host)) {
throw new SecurityError(
'UNAUTHORIZED_HOST',
'Update host not in whitelist'
);
}
};
```
```typescript
// Certificate pinning configuration
export interface CertificatePinning {
enabled: boolean;
certificates: string[]; // Base64 encoded certificates
includeSubdomains: boolean;
maxAge: number; // Seconds
}
```
```typescript
// Validate all inputs from JavaScript
const validateUpdateOptions = (options: any): UpdateOptions => {
// Type validation
if (typeof options !== 'object' || options === null) {
throw new ValidationError('Invalid options object');
}
// URL validation
if (typeof options.url !== 'string' || !options.url) {
throw new ValidationError('URL must be a non-empty string');
}
// Version validation
if (options.version && !isValidSemver(options.version)) {
throw new ValidationError('Invalid version format');
}
// File size validation
if (
options.maxSize &&
(typeof options.maxSize !== 'number' || options.maxSize <= 0)
) {
throw new ValidationError('maxSize must be a positive number');
}
return options as UpdateOptions;
};
```
```typescript
// Prevent directory traversal attacks
const sanitizePath = (path: string): string => {
// Remove any path traversal attempts
const cleaned = path.replace(/\.\./g, '').replace(/\/\//g, '/');
// Ensure path is within allowed directory
if (!cleaned.startsWith(ALLOWED_UPDATE_PATH)) {
throw new SecurityError('PATH_TRAVERSAL', 'Invalid update path');
}
return cleaned;
};
```
```xml
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Request at runtime for Android 6.0+ -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
```
```xml
<!-- Info.plist -->
<key>NSAppTransportSecurity</key>
<dict>
<!-- Only allow HTTPS connections -->
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
```
```typescript
const secureConfig: UpdateSecurityConfig = {
// Network Security
enforceHttps: true,
certificatePinning: {
enabled: true,
certificates: ['sha256/...'],
maxAge: 60 * 60 * 24 * 30, // 30 days
},
// Update Security
requireSignature: true,
allowDowngrade: false,
checksumAlgorithm: 'SHA-256',
// Storage Security
encryptStorage: true,
secureStorageKeys: ['updateKey', 'serverConfig'],
// Validation
maxBundleSize: 50 * 1024 * 1024, // 50MB
allowedHosts: ['updates.yourdomain.com'],
// Error Handling
exposeDetailedErrors: false,
logSecurityEvents: true,
};
```
- [ ] No hardcoded secrets or keys in code
- [ ] All network requests use HTTPS
- [ ] Input validation on all public methods
- [ ] Proper error handling without exposing internals
- [ ] Secure storage for sensitive data
- [ ] Permission requests are minimal and justified
- [ ] Security audit of all native code
- [ ] Penetration testing of update mechanism
- [ ] Certificate pinning configured for production
- [ ] All debug logs removed from production builds
- [ ] Documentation of security features complete
- [ ] Security configuration examples provided
- [ ] Regular dependency updates
- [ ] Security patch monitoring
- [ ] Incident response plan
- [ ] Regular security audits
- [ ] User security guidance updates
If you discover a security vulnerability, please email aoneahsan@gmail.com with:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
Please do **not** file public issues for security vulnerabilities.