Version : 1.0 — date : 2026-04-24 — périmètre : app iOS (SwiftUI, iOS 17+), commit courant.
- Portée & données traitées
- Authentification & identité
- Stockage local
- Réseau & API tierces
- Protections OS mobilisées
- Conformité RGPD / CNIL
- Conformité App Store
- Menaces & mitigations
- Findings & recommandations priorisées
- Statut "dispositif médical" (MDR EU)
RettApp est une application mobile autonome, sans backend propre. Les seules interactions réseau sortantes sont :
- L'authentification Sign in with Apple (Apple ID Servers)
- Le module Actualités (Statamic REST
https://afsr.fr/api) — actuellement désactivé (FeatureFlags.newsEnabled = false) - Les notifications locales (pas de push distant pour l'instant malgré
aps-environment)
| Donnée | Sensibilité (RGPD) | Stockage | Où |
|---|---|---|---|
| Prénom de l'enfant | Donnée identifiante directe d'un mineur | SwiftData | Appareil |
| Date de naissance enfant | Donnée identifiante de mineur | SwiftData | Appareil |
| Flag "a de l'épilepsie" | Donnée de santé (art. 9 RGPD) | SwiftData | Appareil |
| Liste des médicaments | Donnée de santé | SwiftData | Appareil |
| Heures de prise | Donnée de santé | SwiftData | Appareil |
| Historique des crises (date, durée, type, déclencheur, notes) | Donnée de santé | SwiftData | Appareil |
| Apple User ID (hash opaque) | Identifiant pseudonyme | Keychain | Appareil |
Aucune de ces données n'est exfiltrée vers les serveurs de l'AFSR ou un tiers dans l'état actuel du code. Le traitement de données de santé concernant un mineur déclenche toutefois un ensemble d'obligations RGPD décrites en §6.
- Implémentation :
ASAuthorizationAppleIDProvider+SignInWithAppleButtonSwiftUI, scope demandé.fullNameuniquement (pas.email). - Stockage : l'Apple User ID (identifiant opaque
001234.abc...) est persisté en Keychain (AuthManager), servicefr.afsr.RettApp, accountafsr.auth.appleUserID. - Restauration de session : au lancement, on valide que
credentialState(forUserID:)renvoie.authorizedavant de considérer l'utilisateur comme connecté. Sirevoked/notFound, la session Keychain est purgée. - Déconnexion :
AuthManager.signOut()supprime l'item Keychain ; les données locales restent intactes par design (l'app est multi-session pour un même appareil familial).
- ✅ Pas de mot de passe, pas de SMS, pas d'adresse email stockée par l'app
- ✅ Apple User ID est opaque et spécifique à l'app (impossible à recouper avec d'autres apps)
- ✅ Usage de Keychain (chiffré au repos par le Secure Enclave) plutôt que
UserDefaults
⚠️ L'item Keychain utilise la classe par défaut (kSecClassGenericPassword) sanskSecAttrAccessibleexplicite → il reste accessible même quand l'appareil est verrouillé. Fix recommandé : ajouterkSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.⚠️ Aucun verrouillage biométrique de l'app en soi. Si plusieurs personnes utilisent l'iPhone du parent, toutes peuvent voir les données. Fix recommandé : proposer un verrouillage Face ID/Touch ID optionnel à l'ouverture de l'app (LocalAuthentication.framework).⚠️ Le fluxhandle()accepte la session sans vérifieridentityTokenouauthorizationCode(qui devraient être vérifiés côté serveur dans une architecture full-stack, mais sans backend on ne peut pas).
- Fichier SQLite créé par
ModelContainerdans le container de l'app (~/Library/Application Support/default.store). - Chiffrement au repos : iOS applique automatiquement Data Protection classe
NSFileProtectionCompletetant que l'appareil est verrouillé (si un code est configuré). Le fichier SQLite hérite de la classe par défaut du target. À vérifier et forcer viaModelConfigurationou en ajoutant l'attributcom.apple.developer.default-data-protection = NSFileProtectionCompletedans les entitlements. - Sauvegardes iCloud/iTunes : le fichier est par défaut inclus dans les backups. Pour un parent qui partage son iCloud avec le conjoint, cela signifie que les données de santé de l'enfant peuvent se retrouver sur un autre appareil. Recommandation : marquer le fichier avec
URLResourceKey.isExcludedFromBackupKey = true, ou documenter clairement le comportement dans la politique de confidentialité.
- Un seul item : l'Apple User ID (voir §2).
- Service :
fr.afsr.RettApp, account :afsr.auth.appleUserID. - Chiffrement : via Secure Enclave, clé dérivée du code de déverrouillage de l'appareil.
- Point d'amélioration : voir §2 sur
kSecAttrAccessible.
- Exports CSV (
SeizureHistoryView.exportCSV,SettingsView.exportAllCSV) et templates d'import écrivent dansFileManager.default.temporaryDirectory. - iOS purge automatiquement ce répertoire, mais pas immédiatement — les fichiers y restent parfois plusieurs jours.
⚠️ Les fichiers CSV contiennent des données de santé nominatives (nom du médicament, date des crises). Si un utilisateur connecte son iPhone à un ordi non chiffré et partage le volume, ces fichiers sont accessibles.- Fix recommandé : supprimer explicitement le fichier temporaire après le partage réussi (callback
UIActivityViewController.completionWithItemsHandler).
- Utilisation : clé
afsr.seizure.recordingStartedAtpour reprendre un chronomètre de crise après crash ou kill de l'app. - Pas sensible : une simple timestamp, pas d'info médicale dedans. Pas de risque majeur.
- Module Actualités désactivé (feature flag). Aucun appel réseau n'est fait en dehors des services Apple.
- Pas d'analytics, pas de crash reporter, pas de SDK tiers. Aucun Firebase, Sentry, Mixpanel, etc.
- App Transport Security :
NSAllowsArbitraryLoads = falsedansInfo.plist— impossible de parler à un serveur HTTP non-TLS par accident.
- Endpoint
https://afsr.fr/api/collections/actualites/entries— TLS forcé. - Token bearer (
APIConfig.apiKey) dans le binaire si configuré : mauvaise pratique. Les secrets embarqués dans une app iOS sont triviaux à extraire (class-dump, strings, jailbreak). Recommandation : si un token est nécessaire, ne pas le distribuer avec l'app — soit l'endpoint est public en lecture (collection publiée), soit passer par un intermédiaire (Lambda/Cloud Function) qui détient le secret. En pratique, pour des actualités publiques, aucun token ne devrait être requis. - URLCache : 50 Mo disque, 10 Mo RAM. Le cache stocke du HTML potentiellement privé ? Non, actualités publiques uniquement. OK.
- Contenu HTML affiché dans
WKWebView: risque d'XSS si le CMS est compromis et injecte du JS malveillant qui tenterait de lire d'autres contenus. Mitigation :WKWebViewest sandboxé et ne peut pas accéder au système de fichiers ni aux données SwiftData- Aucun handler JS→Swift n'est bridgé
- Recommandation : ajouter une Content-Security-Policy dans le HTML injecté (
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: https:; script-src 'none';">) pour interdire l'exécution JS dans le contenu des articles.
- Communication chiffrée, gérée par le framework système. Rien à auditer.
- ✅ Sandbox iOS : l'app ne peut pas lire les données d'autres apps.
- ✅ Keychain chiffré par Secure Enclave (voir §3).
- ✅ Data Protection — à renforcer explicitement (§3).
- ✅ Permissions granulaires : Camera (jeu regard), FaceID (jeu regard), HealthKit (entitlements actifs mais non utilisés actuellement), UserNotifications (médicaments).
- ✅ Sign in with Apple : pas de gestion de mot de passe côté app.
⚠️ App Attest / DeviceCheck : non utilisé. Pour une app sans backend et sans donnée partagée, l'apport est nul, OK de s'en passer.⚠️ Jailbreak detection : non implémenté. Sur un appareil jailbreaké, les protections Keychain et Data Protection peuvent être contournées. Détection simple possible (access("/Applications/Cydia.app", F_OK)) mais contournable ; pas prioritaire pour cette app.
- Catégorie : données de santé d'un mineur → art. 9 RGPD (catégorie particulière) + art. 8 (consentement parental requis pour < 15 ans en France).
- Finalité : suivi personnel par le parent/aidant. Pas de partage, pas de recherche, pas de marketing.
- Base légale : art. 9.2.h (médecine préventive, intérêt vital) OU art. 9.2.a (consentement explicite). Le plus propre ici est art. 9.2.a consentement explicite du titulaire de l'autorité parentale.
| Exigence | Statut | Action à faire |
|---|---|---|
| Politique de confidentialité accessible dans l'app | afsr.fr/confidentialite mais page à rédiger |
Rédiger avec DPO AFSR |
| Consentement explicite recueilli | ❌ Absent | Ajouter un écran de consentement au premier lancement, cocher explicitement "Je donne mon consentement pour le traitement des données de santé de mon enfant" |
| Droit d'accès / portabilité | ✅ Export CSV complet dans les Réglages | OK |
| Droit à l'effacement | ✅ "Effacer les données de l'application" dans les Réglages | OK |
| Droit à la rectification | ✅ Édition du profil et des enregistrements | OK |
| Minimisation | ✅ Seuls le prénom + date de naissance (optionnelle) sont demandés | OK |
| Conservation limitée | Documenter dans la CGU : "vous êtes responsable de la purge ; l'app ne supprime rien automatiquement" | |
| Registre des traitements (RGPD art. 30) | ❌ À créer côté AFSR | Tenu par le DPO |
| Sous-traitants / DPA | ✅ Aucun (pas de backend, pas de SDK tiers) | N/A |
| Transferts hors UE | Mentionner dans la politique de confidentialité | |
| Notification de violation | Procédure à définir | |
| DPIA (analyse d'impact) | À faire si déploiement national |
Ajouter un écran dédié après le Sign in with Apple et avant le setup du profil :
Consentement parental
---------------------
RettApp traite des données de santé concernant votre enfant
(épilepsie, médicaments, crises). Ces données sont stockées
uniquement sur cet appareil et ne sont transmises à personne.
[ ] Je confirme être le titulaire de l'autorité parentale
[ ] Je consens au traitement de ces données par l'application
[Lire la politique de confidentialité]
[Continuer] [Refuser]
Un refus doit empêcher l'usage de l'app (pas juste un warning).
| Exigence | Statut | Action |
|---|---|---|
Privacy Manifest (PrivacyInfo.xcprivacy) |
❌ Absent — obligatoire depuis iOS 17 pour les apps qui utilisent des APIs à raison requise | À créer — voir ci-dessous |
| App Tracking Transparency | ✅ N/A (aucun tracking) | — |
| Kids Category / pour enfants | — | |
Usage Descriptions (NS*UsageDescription) |
✅ Toutes présentes (Camera, Face ID, Health Share, Health Update) | OK |
| Sign in with Apple | ✅ Entitlements et flow implémentés correctement | OK |
| Rejet "Sensitive health data" | OK si §6 appliqué | |
| Age rating | À confirmer dans App Store Connect |
Créer RettApp/Resources/PrivacyInfo.xcprivacy :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>CA92.1</string></array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>C617.1</string></array>
</dict>
</array>
</dict>
</plist>Les réponses à la question "Quelles données collectez-vous ?" :
- Contact Info > Name : oui (prénom enfant), liée à l'utilisateur, utilisée pour App Functionality uniquement, non partagée, non trackée.
- Health & Fitness : oui, liée à l'utilisateur, App Functionality, non partagée, non trackée.
- Identifiers > User ID : oui (Apple User ID), App Functionality, non partagée, non trackée.
- Tout le reste : non.
Modèle : STRIDE simplifié appliqué à une app mobile sans backend.
| Menace | Vecteur | Impact | Mitigation en place | Gap |
|---|---|---|---|---|
| Spoofing | Quelqu'un se connecte avec ton Apple ID | Accès aux données | Apple ID = Face ID/Touch ID | ❌ Pas de 2e verrouillage app-level |
| Tampering local | Jailbreak + modification du SQLite | Falsification de l'historique | Sandbox iOS + chiffrement au repos | |
| Repudiation | "Ce n'est pas moi qui ai enregistré cette crise" | Faible (usage perso) | Timestamp fiable | OK |
| Information disclosure | Partage de l'iPhone, backup iCloud, export CSV | Divulgation de données de santé | Keychain, tempDirectory partiel |
|
| Denial of Service | — | Faible (pas de backend) | — | N/A |
| Elevation of privilege | App bug permettant écriture hors sandbox | Aucune écriture hors app | Sandbox iOS, pas de code natif C unsafe | OK |
-
iPhone perdu/volé, code déverrouillage simple : Data Protection tient tant que l'appareil reste verrouillé. Une fois déverrouillé par brute-force (code à 4 chiffres = 10 000 combinaisons), les données sont en clair. → Fix : sensibiliser à utiliser un code à 6 chiffres ou biométrie + ajouter un verrouillage Face ID dans l'app.
-
Partage du téléphone au sein de la famille : autre membre voit tout. → Fix : verrouillage Face ID optionnel dans Réglages.
-
Fuite via CSV exporté envoyé à un médecin : le fichier quitte l'écosystème Apple. C'est voulu (portabilité) mais à documenter.
-
Injection XSS via CMS Statamic compromis (news réactivé) : cf. §4, sandbox
WKWebViewlimite le blast radius. -
Reverse engineering du binaire pour extraire un token API : aucun token sensible n'est embarqué aujourd'hui. À respecter.
- Créer
PrivacyInfo.xcprivacy— obligatoire iOS 17, l'app sera rejetée sans. (§7) - Rédiger et publier la politique de confidentialité sur
afsr.fr/confidentialite— lien déjà présent dans l'app mais la page doit exister. (§6) - Ajouter un écran de consentement parental explicite au premier lancement — exigence RGPD art. 8 + 9. (§6)
- Forcer
NSFileProtectionCompletevia entitlements pour garantir le chiffrement au repos du SQLite SwiftData. Ajouter dansRettApp.entitlements:<key>com.apple.developer.default-data-protection</key> <string>NSFileProtectionComplete</string>
- Durcir le Keychain : ajouter
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnlydansAuthManager. - Ajouter un verrouillage biométrique (Face ID/Touch ID) optionnel dans les Réglages (
LocalAuthenticationframework) — defense in depth. - Exclure le store SwiftData des backups iCloud (
URLResourceKey.isExcludedFromBackupKey) OU documenter que les backups sont chiffrés si l'utilisateur active l'iCloud chiffré de bout en bout. - Supprimer les CSV temporaires après partage via
UIActivityViewController.completionWithItemsHandler.
- CSP dans le HTML des articles (quand News réactivé) —
<meta http-equiv="Content-Security-Policy">dans le wrapper HTML deNewsDetailView. - DPIA (analyse d'impact RGPD) à conduire par le DPO de l'AFSR si l'app dépasse quelques centaines d'utilisateurs.
- Journalisation des actions sensibles (export, effacement) dans un log local consultable par l'utilisateur, non transmis. Utile en cas de litige / audit.
- Registre des traitements RGPD art. 30 tenu par le DPO AFSR.
- Ne jamais introduire de SDK tiers (analytics, crash reporting) sans repasser cet audit. Préférer
MetricKit(Apple, pas de sortie réseau) pour des métriques anonymisées si besoin.
- Détection jailbreak basique — faible valeur ajoutée pour une app de suivi personnel, à ignorer sauf demande spécifique.
- Test de pénétration externe si le module News devient un vrai backend (authent, écriture côté serveur).
- MASVS Level 1 (OWASP Mobile AppSec) : auto-évaluation, l'app coche la plupart des contrôles niveau 1 par design (pas de backend, pas de secrets, pas de SDK tiers).
┌──────────────────────────────────┐
│ Appareil iOS (utilisateur) │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Face ID │ │ Keychain │ │
│ │ + code │ │ (Apple │ │
│ │ │ │ User │ │
│ └────┬─────┘ │ ID) │ │
│ │ └──────────┘ │
│ ┌────▼─────────────────────┐ │
│ │ App RettApp │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │SwiftData│ │tempDir │ │ │
│ │ │(santé) │ │(exports)│ │ │
│ │ └────────┘ └────────┘ │ │
│ └──┬──────────────┬───────┘ │
│ │ │ │
└──────┼──────────────┼────────────┘
│ │
▼ ▼
┌────────┐ ┌──────────┐
│ Apple │ │ Partage │
│ IDaaS │ │ utilisat.│
└────────┘ └──────────┘
- Aucun SDK tiers ajouté sans audit
- Aucun secret embarqué (grep sur
apiKey,token,password,secretdans les sources) - Aucun log contenant des données de santé (grep sur
print(,os_log) - Le Privacy Manifest est à jour par rapport aux APIs effectivement utilisées
- La politique de confidentialité
afsr.fr/confidentialitereste publiée et accessible - Les entitlements actifs correspondent aux features réellement utilisées (pas de HealthKit actif si non utilisé)
- ATS reste strict (
NSAllowsArbitraryLoads = false) - Les tests de schéma SwiftData ne cassent pas la migration pour les données existantes d'utilisateurs
Audit à refaire à chaque ajout de dépendance tierce, activation du module News, ou ajout d'une synchronisation cloud.
RettApp n'est pas un dispositif médical au sens du Règlement européen 2017/745 (MDR), du UK Medical Device Regulations, ni des règles FDA. Aucune certification CE médicale, marquage UKCA, ou clearance FDA n'est requise dans le périmètre actuel.
D'après le MDCG 2019-11 (guide officiel européen de classification des logiciels santé) et l'art. 2 du MDR, un logiciel est qualifié de dispositif médical s'il est destiné au "diagnostic, traitement, prédiction, pronostic, atténuation" d'une maladie. Pour chaque fonctionnalité actuelle de RettApp :
| Fonctionnalité | Qualification |
|---|---|
| Journal des crises (date, durée, type, notes) | Patient diary / logbook → non-MD |
| Plan médicamenteux + rappels horaires | Aide à l'observance, dose saisie par l'utilisateur → non-MD |
| Export CSV vers le neurologue | Outil de communication patient/médecin → non-MD |
| Jeu eye-tracking « tarte à la crème » | Loisir / éveil, pas de revendication thérapeutique → non-MD |
L'app basculerait en dispositif médical classe IIa minimum (et donc obligation CE) si elle implémentait :
- Recommandation automatique de dose
- Détection automatique de crise (caméra, accéléromètre, IA)
- Alertes prédictives (pré-crise)
- Analyse statistique avec interprétation clinique ("votre épilepsie s'aggrave")
- Tout texte marketing du type "diagnostique l'épilepsie", "réduit les crises", "améliore le traitement"
- Onboarding : avertissement médical encadré + case "J'ai lu et compris" obligatoire avant de pouvoir continuer (
ProfileSetupView.disclaimerCard). - Réglages : section "Avertissement médical" toujours visible, mention de l'urgence (15/112).
- App Store Connect : déclaratif "Regulated medical device" → réponse
No, justifiée par l'absence de certification CE/FDA/UKCA. - Politique de confidentialité (à publier) : doit reprendre la mention "RettApp n'est pas un dispositif médical au sens du règlement UE 2017/745. Elle ne remplace pas le suivi par un professionnel de santé."
Avant déploiement national à grande échelle, faire valider cette analyse par un avocat spécialisé en santé numérique (ou consulter directement l'ANSM). Coût estimé : 1-2 h de conseil.
Pour le déclaratif Apple : https://developer.apple.com/help/app-store-connect/manage-app-information/declare-regulated-medical-device-status — sélectionner No par pays/région.