security: fix all review findings, bump to 1.0.19 (build 20)

CRITICAL
- SftpProvider: replace PromiscuousVerifier with TofuHostKeyVerifier
  (trust-on-first-use; stores SHA-256 fingerprints in EncryptedSharedPreferences;
  rejects key changes on subsequent connections)

HIGH
- GoogleDriveProvider: replace raw string interpolation with buildJsonObject
  in uploadFile, createDirectory, and moveFile to prevent JSON injection
- DropboxProvider: replace all raw JSON strings and Dropbox-API-Arg headers
  with buildJsonObject for the same reason
- OAuthHelper: add cryptographically random state parameter to Dropbox and
  OneDrive authorization URLs (stored alongside the PKCE verifier)
- OAuthRedirectActivity: validate returned state against stored value before
  exchanging the authorization code (CSRF protection)

MEDIUM
- WebDavProvider: block cross-host redirects in the manual redirect interceptor
  so Authorization headers are never forwarded to a different server
- AccountSetupScreen: set FLAG_SECURE on the window while credential fields
  are visible to prevent screenshots and screen-recording capture
- libs.versions.toml: security-crypto alpha06 → stable 1.0.0;
  biometric-ktx alpha05 → biometric 1.1.0 (stable, non-ktx artifact matches
  the BiometricManager/BiometricPrompt API actually used in MainActivity)
- CredentialStore: migrate to security-crypto 1.0.0 API (MasterKeys.getOrCreate
  + positional create() args); add saveHostKey/getHostFingerprint for SFTP TOFU

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 18:08:40 +00:00
parent 894c2ffe78
commit be3f46287a
12 changed files with 128 additions and 38 deletions
@@ -2,6 +2,7 @@ package com.syncflow.ui.auth
import android.accounts.AccountManager
import android.app.Activity
import android.view.WindowManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
@@ -201,6 +202,15 @@ private fun CredentialContent(
) {
val provider = state.providerType ?: return
// Prevent screenshots and screen recording while credentials are visible
val activity = LocalContext.current as? Activity
DisposableEffect(Unit) {
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
onDispose {
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
Column(
modifier = modifier
.padding(horizontal = 20.dp)