Salta al contenuto principale

Capacitor Privacy Screen: nascondi i contenuti dell'app

Aggiungi una privacy screen Capacitor per nascondere i contenuti dell'app nell'app switcher e bloccare gli screenshot, con controllo per schermata, config e limitazioni di piattaforma.

Illustrazione delle impostazioni di sicurezza per aggiungere una privacy screen a un'app Capacitor

Una privacy screen Capacitor nasconde i contenuti dell'app quando l'utente apre l'app switcher e, su Android, blocca screenshot e registrazione schermo. Senza di essa, iOS e Android scattano un'istantanea dell'ultima schermata, lasciando visibili saldi bancari, dati medici o un code usa e getta nella lista delle app recenti. @capgo/capacitor-privacy-screen lo aggiunge con una sola chiamata o un flag di config, e ti lascia proteggere solo le schermate che ne hanno bisogno.

Cosa espone l'app switcher

Quando un utente esce dalla tua app, entrambi i sistemi catturano un'immagine dello schermo corrente:

  • iOS scatta uno snapshot quando l'app smette di essere attiva e lo mostra nell'app switcher, e brevemente durante il riavvio dell'app.
  • Android mostra un'anteprima live o in cache nelle App Recenti.

Chiunque guardi il telefono vede quello snapshot. Lo stesso vale per chi condivide lo schermo durante una chiamata o per un utente che registra lo schermo per un ticket di supporto. Gli screenshot vengono anche sincronizzati con le librerie fotografiche cloud, ed è proprio lì che i dati sensibili rischiano di trapelare.

Quali app ne hanno bisogno

Tipo di app Cosa può trapelare
Settore bancario, fintech, crypto Saldo, numeri carta, IBAN, transazioni
Password manager, authenticator Password, codici TOTP, chiavi di recupero
Salute e ambito medico Diagnosi, prescrizioni, referti
Messaging Conversazioni private
HR, payroll, legale Stipendi, contratti
App di dating, media privati Photos and matches

Security review e penetration test per queste categorie segnalano spesso la mancanza di protezione nell'app switcher. OWASP MASVS include controlli sui dati sensibili esposti tramite UI, e gli auditor testano proprio questo.

Installa

bun add @capgo/capacitor-privacy-screen
bunx cap sync

Il plugin supporta Capacitor 8 con la versione 8.x.

Opzione 1: proteggi l'intera app all'avvio

Impostalo in capacitor.config.ts. Il plugin nativo lo legge al caricamento, prima dell'esecuzione del JavaScript, così anche il primo snapshot è protetto:

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.bank',
  appName: 'Example Bank',
  webDir: 'dist',
  plugins: {
    PrivacyScreen: {
      enabled: true,
      android: {
        dimBackground: false,
        privacyModeOnActivityHidden: 'splash',
      },
      ios: {
        blurEffect: 'dark',
      },
    },
  },
};

export default config;

Esegui bunx cap sync dopo aver modificato la config. La enabled opzione viene letta solo dalla config di Capacitor, non è un'opzione di runtime.

Opzione 2: proteggere schermi specifici

import { PrivacyScreen } from '@capgo/capacitor-privacy-screen';

export async function enterSensitiveScreen() {
  await PrivacyScreen.enable({
    android: { dimBackground: true },
    ios: { blurEffect: 'light' },
  });
}

export async function leaveSensitiveScreen() {
  await PrivacyScreen.disable();
}

const { enabled } = await PrivacyScreen.isEnabled();

Collega questi hook al tuo router per coprire ogni percorso in entrata e in uscita. Con Vue Router, ad esempio:

router.afterEach(async (to) => {
  if (to.meta.sensitive) {
    await PrivacyScreen.enable();
  } else {
    await PrivacyScreen.disable();
  }
});

Con Angular, usa una guard o una NavigationEnd subscription. Con React Router, basta un hook nel layout component. L'importante è che la navigazione indietro, i deep link e il cambio tab seguano la stessa logica, altrimenti uno schermo sensibile potrebbe restare scoperto.

Cosa fa ogni opzione

Android

  • La protezione utilizza WindowManager.LayoutParams.FLAG_SECURE. Quando attiva, screenshot e registrazioni dello schermo sono bloccati e l'anteprima dei recenti viene nascosta.
  • dimBackground: true mostra un overlay oscurato nell'app switcher. Con false, il plugin mostra la tua splash drawable se disponibile, altrimenti applica l'oscuramento.
  • privacyModeOnActivityHidden controlla cosa viene mostrato quando l'activity è coperta da altro, ad esempio dal prompt biometrico di sistema: 'none' (default), 'dim' o 'splash'.
  • La vecchia preventScreenshots opzione è deprecata e viene ignorata. FLAG_SECURE viene sempre applicata quando la protezione è attiva. Per permettere uno screenshot in una schermata, chiama disable() prima di esso e enable() dopo.

iOS

  • Quando l'app torna attiva, il plugin aggiunge un overlay per evitare che lo snapshot dell'app switcher mostri i tuoi contenuti.
  • blurEffect: 'light' o 'dark' usa il blur nativo. 'none' (default) mostra la launch screen se disponibile, altrimenti uno sfondo di sistema.
  • While enabled, app content is also placed in a secure rendering layer. Treat iOS screenshot blocking as best effort: Apple does not offer an official API to block screenshots, and behavior can change between iOS versions.

Web

L'implementazione web traccia solo il flag abilitato, così il tuo code gira ovunque. I browser non possono nascondere le anteprime delle schede o bloccare gli screenshot.

Combinalo con la biometria

Un pattern comune per le app banking: sfoca il contenuto nel task switcher, poi richiedi Face ID o impronta digitale quando l'app viene riaperta dopo un po'. Il plugin privacyModeOnActivityHidden mantiene il contenuto coperto mentre è attivo il prompt biometrico di Android.

import { App } from '@capacitor/app';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';

// null until the app has really gone to the background
let backgroundedAt: number | null = null;

App.addListener('appStateChange', async ({ isActive }) => {
  if (!isActive) {
    backgroundedAt = Date.now();
    return;
  }
  const away = backgroundedAt === null ? 0 : Date.now() - backgroundedAt;
  backgroundedAt = null;
  if (away > 60_000) {
    await NativeBiometric.verifyIdentity({ reason: 'Confirm it is you' });
  }
});

Guarda il plugin biometrico nativo per setup e gestione errori.

Il plugin ufficiale e il plugin Capgo

Il team Capacitor pubblica anche @capacitor/privacy-screen con un API molto simile (enable, disable, isEnabled e le stesse opzioni di piattaforma). Il plugin Capgo mantiene quel API e aggiunge:

  • enabled: true nella config Capacitor, così la protezione è attiva prima che qualsiasi JavaScript venga eseguito. Il plugin ufficiale si abilita solo da JavaScript.
  • rendering sicuro dei contenuti su iOS quando attivo, documentato come parte del comportamento del plugin, oltre all'overlay nell'app switcher.
  • getPluginVersion() per la diagnostica.
  • Un'implementazione web che mantiene il flag di attivazione, così lo stesso code gira in una preview nel browser.

Se usi già il plugin ufficiale, il passaggio consiste quasi solo in un cambio di import.

Dettagli UX che contano

  • Non bloccare gli screenshot ovunque senza motivo. Gli utenti scattano screenshot di ricevute, codici di conferma e impostazioni. Proteggi le schermate con dati sensibili, non l'intera app, a meno che la tua policy di sicurezza non lo richieda.
  • Spiega il perché agli utenti. Se su Android un utente prova a fare uno screenshot e ottiene un'immagine nera, una breve nota nel tuo centro assistenza ridurrà i ticket di supporto.
  • Testa la condivisione dello schermo. Videochiamate e casting sono bloccati su Android mentre FLAG_SECURE è impostata. Se la tua app prevede la funzione “condividi schermo con il supporto”, disabilita la protezione per quel flusso.
  • Controlla l'accessibilità. Gli screen reader continuano a funzionare con FLAG_SECURE, ma testa comunque con TalkBack e VoiceOver.

Checklist per i test

  1. Apri una schermata sensibile e vai nell'app switcher. Il contenuto deve essere nascosto su entrambe le piattaforme.
  2. Su Android, prova uno screenshot e una registrazione schermo. Entrambi devono risultare neri o bloccati.
  3. Attiva un prompt biometrico e controlla lo sfondo.
  4. Torna da una schermata sensibile a una normale. La protezione deve disattivarsi se usi la modalità per singola schermata.
  5. Chiudi e riavvia l'app. Con enabled: true in config, il primo snapshot deve essere protetto.
  6. Testa su iPad con Split View e Slide Over. L'app potrebbe rimanere visibile quando non è attiva.

Risoluzione problemi

Lo snapshot mostra ancora il contenuto su iOS. Hai attivato la protezione dopo che l'app è passata in background, o solo su alcune rotte. Usa l'opzione di config per proteggere l'intera app.

Schermata nera nelle App Recenti su Android invece della splash screen. Imposta dimBackground: false e assicurati che il tuo splash drawable esista, altrimenti il plugin ripiega sul dimming.

Gli screenshot funzionano ancora su Android. Verifica isEnabled(). Un'altra parte della tua app potrebbe chiamare disable() durante la navigazione.

Protezione persa dopo un aggiornamento OTA. I valori di configurazione sono nativi. Se cambi plugins.PrivacyScreen nella capacitor.config.ts, rilascia una nuova build sugli store. A runtime enable() e disable() le chiamate nel tuo JavaScript possono essere modificate con Live updates di Capgo.

La privacy screen è solo uno degli elementi di una checklist di sicurezza mobile. Considera anche lo storage sicuro, il certificate pinning e il root detection. Capgo offre plugin per SSL pinning e root e jailbreak detection, e il nostro post su regole sulla privacy di Apple per le app Capacitor copre la parte relativa alla revisione. Tutte le opzioni del plugin sono nella documentazione di Privacy Screen.

Live updates per le app Capacitor

Quando un bug nel web-layer è live, rilascia la fix tramite Capgo invece di aspettare giorni l'approvazione degli store. Gli utenti ricevono l'update in background, mentre le modifiche native restano nel normale processo di review.

Ultimi articoli dal blog

Capgo ti offre gli insight migliori che ti servono per creare un'app mobile davvero professionale.