From cdf178b1c374172dec43a883bf480bbbb62a34f5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 14 Apr 2026 04:15:27 +0200 Subject: [PATCH] =?UTF-8?q?Mobile=20app=20hardening=20=E2=80=94=20security?= =?UTF-8?q?=20+=20Android=2014=20compat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit capacitor.config.json: - webContentsDebuggingEnabled: true → false (was leaving Chrome DevTools able to attach to released builds) - allowMixedContent: true → false (API is HTTPS-only; no need to permit cleartext loads) - server.allowNavigation: ["*"] → restricted to pedshub.com / peds.danvics.com origins (prevents WebView following an attacker-controlled redirect) AndroidManifest.xml: - android:allowBackup="false" + data_extraction_rules.xml (Android system backup would otherwise copy EncryptedSharedPreferences containing the auth token into Google Cloud backups) - Removed USE_BIOMETRIC permission (feature removed earlier) AudioRecordingService.java: - startForeground(id, notif, TYPE_MICROPHONE) on Android 14+ (without the explicit type Android 14 kills the service with MissingForegroundServiceTypeException) - WakeLock cap: 1h → 8h (still bounded, onDestroy releases early) MainActivity.java: - Removed dead biometric code path and androidx.biometric imports mobile/package.json: - Dropped @aparajita/capacitor-biometric-auth — orphan dependency --- .../android/app/src/main/AndroidManifest.xml | 5 +- .../pedshub/scribe/AudioRecordingService.java | 17 +++++- .../java/com/pedshub/scribe/MainActivity.java | 55 ------------------- .../main/res/xml/data_extraction_rules.xml | 23 ++++++++ mobile/capacitor.config.json | 11 +++- mobile/package.json | 1 - 6 files changed, 48 insertions(+), 64 deletions(-) create mode 100644 mobile/android/app/src/main/res/xml/data_extraction_rules.xml diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 994ba68..e481b57 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,9 @@ - diff --git a/mobile/android/app/src/main/java/com/pedshub/scribe/AudioRecordingService.java b/mobile/android/app/src/main/java/com/pedshub/scribe/AudioRecordingService.java index 220f878..270a65d 100644 --- a/mobile/android/app/src/main/java/com/pedshub/scribe/AudioRecordingService.java +++ b/mobile/android/app/src/main/java/com/pedshub/scribe/AudioRecordingService.java @@ -6,6 +6,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; +import android.content.pm.ServiceInfo; import android.os.Build; import android.os.IBinder; import android.os.PowerManager; @@ -40,11 +41,13 @@ public class AudioRecordingService extends Service { return START_NOT_STICKY; } - // Acquire wake lock to keep CPU active during recording + // Acquire wake lock to keep CPU active during recording. + // 8h cap is a safety net — onDestroy() releases early when recording + // stops. The cap prevents a runaway lock if the service leaks. PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); if (pm != null) { wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG); - wakeLock.acquire(60 * 60 * 1000L); // 1 hour max + wakeLock.acquire(8 * 60 * 60 * 1000L); } // Stop action in notification @@ -65,7 +68,15 @@ public class AudioRecordingService extends Service { .addAction(android.R.drawable.ic_media_pause, "Stop Recording", stopPending) .build(); - startForeground(NOTIFICATION_ID, notification); + // Android 14 (SDK 34) requires the 3-arg form with an explicit + // foregroundServiceType matching the manifest declaration, else + // the service is killed with MissingForegroundServiceTypeException. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + startForeground(NOTIFICATION_ID, notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE); + } else { + startForeground(NOTIFICATION_ID, notification); + } return START_STICKY; } diff --git a/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java b/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java index 85ddc5d..19932c0 100644 --- a/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java +++ b/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java @@ -2,7 +2,6 @@ package com.pedshub.scribe; import android.Manifest; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.webkit.PermissionRequest; @@ -10,20 +9,15 @@ import android.webkit.WebChromeClient; import android.webkit.WebView; import androidx.annotation.NonNull; -import androidx.biometric.BiometricManager; -import androidx.biometric.BiometricPrompt; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.getcapacitor.BridgeActivity; -import java.util.concurrent.Executor; - public class MainActivity extends BridgeActivity { private static final int MIC_PERMISSION_CODE = 1001; private PermissionRequest pendingPermissionRequest; - private boolean biometricDone = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -79,55 +73,6 @@ public class MainActivity extends BridgeActivity { } } - // ── Biometric Authentication ────────────────────────────── - - private void showBiometricPrompt() { - BiometricManager biometricManager = BiometricManager.from(this); - int canAuth = biometricManager.canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK | - BiometricManager.Authenticators.DEVICE_CREDENTIAL); - - if (canAuth != BiometricManager.BIOMETRIC_SUCCESS) { - // No biometric or device credential available — skip - biometricDone = true; - return; - } - - Executor executor = ContextCompat.getMainExecutor(this); - - BiometricPrompt biometricPrompt = new BiometricPrompt(this, executor, - new BiometricPrompt.AuthenticationCallback() { - @Override - public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { - super.onAuthenticationSucceeded(result); - biometricDone = true; - } - - @Override - public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { - super.onAuthenticationError(errorCode, errString); - // Allow access even on error (user can cancel) - biometricDone = true; - } - - @Override - public void onAuthenticationFailed() { - super.onAuthenticationFailed(); - // Don't block — just let them retry or cancel - } - }); - - BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() - .setTitle("PedScribe") - .setSubtitle("Verify your identity to continue") - .setAllowedAuthenticators( - BiometricManager.Authenticators.BIOMETRIC_WEAK | - BiometricManager.Authenticators.DEVICE_CREDENTIAL) - .build(); - - biometricPrompt.authenticate(promptInfo); - } - // ── Background Recording Service Bridge ─────────────────── private void setupRecordingBridge() { diff --git a/mobile/android/app/src/main/res/xml/data_extraction_rules.xml b/mobile/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..7c2eaa5 --- /dev/null +++ b/mobile/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/mobile/capacitor.config.json b/mobile/capacitor.config.json index 24e66b8..9fde237 100644 --- a/mobile/capacitor.config.json +++ b/mobile/capacitor.config.json @@ -3,7 +3,12 @@ "appName": "PedScribe", "webDir": "src", "server": { - "allowNavigation": ["*"] + "allowNavigation": [ + "app.pedshub.com", + "*.pedshub.com", + "pedshub.com", + "peds.danvics.com" + ] }, "plugins": { "SplashScreen": { @@ -21,9 +26,9 @@ } }, "android": { - "allowMixedContent": true, + "allowMixedContent": false, "backgroundColor": "#2563eb", - "webContentsDebuggingEnabled": true, + "webContentsDebuggingEnabled": false, "appendUserAgent": "PedScribe-Android", "androidScheme": "https" }, diff --git a/mobile/package.json b/mobile/package.json index 40cd5dd..401e6be 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -16,7 +16,6 @@ "@capacitor/cli": "^6.0.0", "@capacitor/core": "^6.0.0", "@capacitor/ios": "^6.0.0", - "@aparajita/capacitor-biometric-auth": "^8.0.0", "@capacitor/haptics": "^6.0.0", "@capacitor/keyboard": "^6.0.0", "@capacitor/push-notifications": "^6.0.0",