Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c9c7eae
fix(securite): audit 2026-08-03 — 16 findings corriges
Aug 3, 2026
78a0576
fix(securite): 5 findings Gemini + retrait total de Google ML Kit
Aug 3, 2026
ad9d0b5
chore: formatage de tool/ + registrant macOS + audit/ ignore
Aug 3, 2026
7f70f3a
feat(ui): Reglages presentes comme « A propos » + titres agrandis
Aug 3, 2026
0d6fd3f
feat(ui): titres des Reglages au bleu de la marque (cs.primary)
Aug 3, 2026
2c9cb7f
fix(securite): sortie du camouflage accessible sans ouvrir le coffre
Aug 3, 2026
3464c8d
fix(securite): 13 findings du 2e audit Gemini
Aug 3, 2026
ecfafce
feat(securite): avertissement avant la panique + incitation a la sauv…
Aug 3, 2026
f086347
fix(ux): boucle d'invite biometrique au lancement
Aug 3, 2026
fd86f50
fix(sec): la vue heritier n'etait plus refermee au retour au premier …
Aug 4, 2026
249920d
feat(ux): annoncer la desactivation de la biometrie apres changement …
Aug 4, 2026
65ff3ff
feat(ui): bandeaux d'information au bleu de la marque
Aug 4, 2026
fdc8b57
sec: regle de mot de passe unique appliquee aux 5 points d'entree
Aug 4, 2026
d15808c
fix(ux): annuler l'invite biometrique n'est plus presente comme un echec
Aug 4, 2026
94827aa
fix(ux): bouton Deverrouiller desactive quand le champ est vide
Aug 4, 2026
39089a9
sec: sauvegarde corrompue au partage + 6e tampon de mot de passe non …
Aug 4, 2026
e8904d1
sec: les 3 dernieres findings ouvertes sont traitees
Aug 4, 2026
cdd6533
ui: separer visuellement portee, types et categories dans la barre de…
Aug 4, 2026
7e043ab
feat(ux): confirmation avant d'abandonner une saisie non enregistree
Aug 4, 2026
519bf47
ci: fournir GITHUB_TOKEN a claude-code-action
Aug 4, 2026
aafc443
ci: syntaxe Exodus + modele fixe, et README bilingue (EN)
Aug 4, 2026
3297915
docs: README bilingue — version francaise
Aug 4, 2026
79b9a04
ci: authentifier claude-review par l'abonnement, pas par cle API
Aug 4, 2026
6253222
sec: pas d'apprentissage clavier sur les champs de carte bancaire
Aug 4, 2026
5c1bc5b
sec: 4 findings critiques/elevees de l'audit GPT — 2 pertes de donnees
Aug 4, 2026
f0cfea2
sec: fin de l'audit GPT — F3, F5, F6, F7, F8, F9
Aug 4, 2026
fed2e56
ci: rendre claude-review non bloquant en attendant sa resolution
Aug 4, 2026
a142890
sec: _createSlot annulait un verrouillage survenu pendant sa creation
Aug 4, 2026
1dd1631
sec: le deverrouillage biometrique pouvait annuler une panique
Aug 4, 2026
6c6621f
sec: relecture Codex — 3 defauts dans mes propres correctifs
Aug 4, 2026
1060923
docs: deux commentaires qui affirmaient encore le contraire du code
Aug 4, 2026
a0e55bc
ui: le message de mot de passe faible chevauchait les boutons du dial…
Aug 4, 2026
1e00b15
feat(audit): le score devient proportionnel et tient compte des fuites
Aug 4, 2026
4998552
chore: v2.6.0 (versionCode 39)
Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,36 @@ jobs:
working-directory: pass_tech
run: flutter pub get

# AUDIT 2026-08-03 — `--no-fatal-infos` remplacé par le durcissement de la
# doctrine (`ci-et-chaine-de-publication.md` §4.1). Vérifié en local avant
# d'être posé ici : l'analyse rend « No issues found » sur ce dépôt, donc
# le durcissement ne coûte RIEN aujourd'hui et empêche la dérive demain.
- name: Analyze
working-directory: pass_tech
run: flutter analyze --no-fatal-infos
run: flutter analyze --fatal-infos --fatal-warnings

# AUDIT 2026-08-03 — nouveau contrôle. `tool/generate_icon.dart` n'était
# pas formaté au moment de l'ajout : sans cette barrière, le dépôt dérive
# fichier par fichier et les diffs de revue se noient dans du reformatage.
- name: Format
working-directory: pass_tech
run: dart format --output=none --set-exit-if-changed .

# ⚠️ AUDIT 2026-08-03 — le `|| echo "No tests yet"` a été RETIRÉ.
#
# Il faisait passer le job au vert QUOI QU'IL ARRIVE : un test en échec
# produisait exactement la même pastille verte qu'une suite intacte. Sur
# un gestionnaire de mots de passe dont la suite couvre le round-trip
# AES-GCM, la migration v3→v4 et le rembourrage anti-corrélation, c'est
# la garantie la plus coûteuse du dépôt qui était neutralisée — et
# l'écriture même du garde-fou (« pas encore de tests ») avait cessé
# d'être vraie : il y en a 139.
#
# Une CI qui ne peut pas échouer ne teste rien ; elle rassure, ce qui est
# pire que pas de CI du tout.
- name: Run tests
working-directory: pass_tech
run: flutter test || echo "No tests yet"
run: flutter test

# Build debug APK pour valider que la compilation passe end-to-end
# (pas besoin de keystore release en CI).
Expand Down
72 changes: 70 additions & 2 deletions .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
name: Claude Review

# Review automatique des PR par Claude Code (anthropics/claude-code-action).
# Coût : ~0.10-0.50 € par review (tokens API Anthropic, paye à l'usage).
# Tourne sur l'ABONNEMENT Claude (jeton OAuth), pas sur l'API : aucune
# facturation à l'usage. L'en-tête annonçait « ~0,10-0,50 € par review, tokens
# API Anthropic, payé à l'usage » — cela n'a jamais correspondu à cette
# configuration, et c'est ce malentendu qui a fait poser une clé API là où il
# fallait un jeton d'abonnement.
#
# Mention @claude dans une issue ou PR pour invoquer manuellement.
# Auto-review activé sur opened/synchronize.
Expand Down Expand Up @@ -43,6 +47,26 @@ jobs:
github.event.comment.author_association == 'COLLABORATOR'))
runs-on: ubuntu-latest
timeout-minutes: 15
# CI 2026-08-04 — job NON BLOQUANT.
#
# L'authentification a été corrigée par étapes : `github_token` fourni (le
# jeton d'app GitHub manquait), modèle fixé explicitement, puis
# `claude_code_oauth_token` à la place d'une clé API que l'abonnement ne
# fournit pas. Chaque étape a fait progresser la requête — la durée est
# passée de 227 ms à plus de 2 s — mais quelque chose reste refusé en aval,
# sans message exploitable dans le journal.
#
# En attendant, ce job mettait au rouge CHAQUE pull request, alors que les
# quatre autres — analyse, 153 tests, permissions de l'APK, traceurs — sont
# verts. Un voyant rouge permanent finit par ne plus rien signifier : on
# cesse de le regarder, et le jour où un vrai échec arrive, il passe
# inaperçu. C'est la raison de ce drapeau, pas la commodité.
#
# La revue automatique fait par ailleurs doublon avec ce qui a été fait à la
# main : audit ligne à ligne, quatre passes Gemini, une relecture ChatGPT.
#
# À retirer dès que le job réussit une fois.
continue-on-error: true
permissions:
contents: write # pour commit des fixes si demandé
pull-requests: write # pour commenter sur la PR
Expand All @@ -58,7 +82,51 @@ jobs:
- name: Run Claude Code
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# CI 2026-08-04 — authentification par ABONNEMENT et non par clé API.
#
# Le workflow réclamait `ANTHROPIC_API_KEY`. Le secret existait, mais
# la requête était rejetée en ~200 ms pour un coût de 0 $ — donc
# refusée à l'authentification, avant tout traitement, et cela avec
# deux modèles différents.
#
# Raison : l'abonnement Claude et l'API sont deux produits distincts,
# facturés séparément. Une clé API exige un compte API approvisionné,
# que payer l'abonnement ne fournit pas.
#
# `claude_code_oauth_token` est la voie prévue pour un abonnement. Le
# jeton se génère avec `claude setup-token` (« requires Claude
# subscription ») et vit dans le secret ci-dessous.
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# CI 2026-08-04 — le jeton du workflow, au lieu de l'app GitHub.
#
# Sans lui, l'action tente d'obtenir un jeton via l'application
# GitHub « Claude Code », qui n'est pas autorisée sur ce dépôt :
# App token exchange failed: 401 Unauthorized
# Claude Code is not installed on this repository
# Le job échouait donc sur CHAQUE pull request, quel qu'en soit le
# contenu — la clé API était bien présente, c'est l'app qui manquait.
#
# Les entrées de l'action décrivent `github_token` comme
# « optional if using GitHub App » : les deux sont des voies
# ALTERNATIVES, et le workflow n'en fournissait aucune.
#
# `GITHUB_TOKEN` porte déjà les droits nécessaires — ils sont
# déclarés dans le bloc `permissions:` ci-dessus (contents,
# pull-requests, issues). Installer l'app reste possible si l'on veut
# que les commentaires soient signés par elle plutôt que par
# `github-actions[bot]`.
github_token: ${{ secrets.GITHUB_TOKEN }}
# CI 2026-08-04 — modèle FIXÉ explicitement.
#
# Sans cette ligne, l'action retenait `claude-opus-5[1m]`, la variante
# à contexte étendu, et la requête était rejetée en 440 ms pour un
# coût de 0 $ — donc refusée avant tout traitement, faute d'accès à ce
# modèle sur cette clé.
#
# Laisser un défaut implicite décider du modèle en intégration
# continue est de toute façon une mauvaise idée : le coût par revue et
# la disponibilité changent sans qu'aucun fichier du dépôt ne bouge.
claude_args: "--model claude-sonnet-5"
# Prompt pour les auto-reviews PR (skip pour @claude mentions
# qui suivent l'instruction du commentaire).
prompt: |
Expand Down
161 changes: 161 additions & 0 deletions .github/workflows/promesses.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Promesses

# AUDIT 2026-08-03 — vérifie sur l'ARTEFACT ce que l'application promet dans sa
# politique de confidentialité. Aucun autre mécanisme du dépôt ne couvre ça :
# `ci.yml` analyse le source, `security.yml` scanne les CVE des dépendances.
# Ni l'un ni l'autre ne regarde ce que le binaire contient RÉELLEMENT.
#
# Ce workflow existe parce que le manifeste FUSIONNÉ de la release contenait une
# permission (`ACCESS_NETWORK_STATE`) absente du dépôt, plus les composants de
# télémétrie de Google, sans que personne le sache. Une promesse publique se
# vérifie à chaque commit — pas au moment de couper la release, quand la
# dépendance fautive a déjà trois semaines.

on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: '0 5 * * 1'
workflow_dispatch:

permissions:
contents: read

jobs:
artefact:
name: Permissions + trackers sur l'APK release
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v6

- name: Setup Java 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true

- name: Pub get
run: flutter pub get

# Le build release REFUSE de signer avec la clé de debug depuis SEC F15
# v2.5.2 — c'est volontaire, et il ne faut surtout pas l'assouplir. On
# fabrique donc une clé JETABLE, valable un jour, qui ne quitte jamais le
# runner.
#
# ⚠️ L'APK produit ici n'est PAS publiable : il ne porte pas le certificat
# de Files Tech, donc il ne peut pas mettre à jour une installation
# existante. Il n'existe que pour être analysé, et n'est jamais téléversé.
- name: Clé de signature jetable (analyse uniquement)
run: |
keytool -genkeypair -v -keystore android/ci-throwaway.jks \
-storepass ci-only -keypass ci-only -alias ci \
-keyalg RSA -keysize 2048 -validity 1 -dname "CN=CI,O=throwaway"
cat > android/key.properties <<'EOF'
storeFile=ci-throwaway.jks
storePassword=ci-only
keyPassword=ci-only
keyAlias=ci
EOF

# Release et non debug : c'est R8 qui décide de ce qui reste réellement
# dans le binaire distribué. Analyser un APK de debug sur-détecterait,
# puisque rien n'y est élagué.
- name: Build APK release (arm64)
run: flutter build apk --release --target-platform android-arm64

- name: Localiser aapt
id: sdk
run: |
AAPT=$(find "$ANDROID_SDK_ROOT/build-tools" -name aapt -type f | sort -V | tail -1)
echo "aapt=$AAPT" >> "$GITHUB_OUTPUT"
"$AAPT" version

# ── Contrôle 1 : la surface de permissions n'a pas grandi ──────────────
- name: Permissions conformes à expected-permissions.txt
run: |
APK=$(find build -name '*release*.apk' | head -1)
echo "APK analysé : $APK"

"${{ steps.sdk.outputs.aapt }}" dump permissions "$APK" \
| grep -oP "(?<=uses-permission: name=')[^']+" \
| sort -u > /tmp/actual.txt

# La permission de réception dynamique est générée par Flutter à
# partir de l'applicationId : elle est propre à l'app, de niveau
# signature, et n'élargit aucune surface externe.
grep -v 'DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION' /tmp/actual.txt \
> /tmp/actual-filtre.txt

grep -v '^\s*#' android/expected-permissions.txt \
| grep -v '^\s*$' | sort -u > /tmp/expected.txt

echo "── Attendues ──"; cat /tmp/expected.txt
echo "── Réelles ──"; cat /tmp/actual-filtre.txt

if ! diff -u /tmp/expected.txt /tmp/actual-filtre.txt; then
echo ""
echo "ECHEC : la liste des permissions de l'APK ne correspond plus."
echo " '+' = permission apparue et non déclarée. Trouver la"
echo " dépendance responsable avant toute chose :"
echo " grep -rn <PERMISSION> build/app/intermediates/manifest_merge_blame_file/"
echo " '-' = permission disparue : mettre à jour le fichier de"
echo " référence ET PRIVACY.md §10."
exit 1
fi
echo "OK : surface de permissions inchangée."

# ── Contrôle 2 : aucun tracker connu ──────────────────────────────────
# Le site annonce « aucun tracker » pour les huit applications, et
# App Manager Tech revendique une détection de trackers du même type.
# Un tracker signalé dans un binaire Files Tech par l'outil que les
# utilisateurs méfiants emploient eux-mêmes serait sans commune mesure
# avec le coût de ce contrôle.
- name: Aucun tracker connu (Exodus Privacy)
run: |
APK=$(find build -name '*release*.apk' | head -1)
# CI 2026-08-04 — syntaxe corrigée. `-j` est un DRAPEAU (sortie JSON),
# il ne prend pas de nom de fichier ; c'est `-o` qui reçoit la
# destination. Écrit `-j <fichier> <apk>`, l'outil prenait le fichier
# pour l'APK et refusait le vrai chemin en argument surnuméraire :
# exodus_analyze.py: error: unrecognized arguments: /w/…/app-release.apk
# La forme erronée vient de `ci-et-chaine-de-publication.md` §6, à
# corriger là-bas aussi.
docker run --rm -v "$PWD:/w" exodusprivacy/exodus-standalone \
-j -o /w/exodus.json "/w/$APK" || true
if [ ! -f exodus.json ]; then
echo "ECHEC : Exodus n'a produit aucun rapport."
exit 1
fi
COUNT=$(jq '.trackers | length' exodus.json)
echo "Trackers détectés : $COUNT"
jq -r '.trackers[]?.name' exodus.json || true
if [ "$COUNT" -ne 0 ]; then
echo "ECHEC : $COUNT tracker(s) dans l'APK release."
exit 1
fi
echo "OK : aucun tracker."

- name: Rapport Exodus
if: always()
uses: actions/upload-artifact@v7
with:
name: exodus-${{ github.sha }}
path: exodus.json
if-no-files-found: ignore
retention-days: 30

# La clé jetable et le key.properties généré ne doivent jamais survivre à
# l'exécution, même si une étape précédente a échoué.
- name: Purge de la clé jetable
if: always()
run: rm -f android/ci-throwaway.jks android/key.properties
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ app.*.map.json
# Ne doit jamais être versionnée : F-Droid et la CI doivent résoudre la
# dépendance git épinglée du pubspec.
pubspec_overrides.yaml

# Sorties d'audit IA externe (audit-gemini.ps1) — non versionnees.
audit/
28 changes: 24 additions & 4 deletions PRIVACY.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,29 @@ La présente Politique de confidentialité explique comment l'application **Pass

## 6. Réseau

- L'app utilise le réseau pour **deux fonctions strictement à impact local** :
1. **Vérification de mises à jour** : interroge `api.github.com/repos/gitubpatrice/pass_tech/releases/latest` (HTTPS, sans auth, sans cookie).
- **Pass Tech elle-même** n'utilise le réseau que pour **deux fonctions strictement à impact local** :
1. **Vérification de mises à jour** : interroge `api.github.com/repos/gitubpatrice/pass_tech/releases/latest` (HTTPS, sans auth, sans cookie). Suspendue tant que le mode panique est actif.
2. **Vérification HIBP** (Have I Been Pwned, opt-in) : envoie uniquement les **5 premiers caractères du SHA-1** d'un mot de passe (modèle k-anonymity). Le mot de passe ne quitte jamais l'appareil.
- Network Security Config refuse le HTTP en clair et les autorités utilisateur en release.
- Aucune télémétrie, rapport de crash ou analytics.
- **Nous n'opérons aucun serveur.** Aucune donnée n'est envoyée au développeur, aucun analytics maison, aucun rapport de crash.

### Aucune bibliothèque Google

L'application ne contient **aucun composant Google** : ni Play Services, ni
ML Kit, ni Firebase, ni transport de télémétrie.

Ce n'était pas le cas jusqu'au 2026-08-03. Le scan de QR code reposait alors sur
`mobile_scanner`, bâti sur **Google ML Kit**, qui entraînait à sa suite
`play-services-base`, `play-services-basement` et le composant de transport
`com.google.android.datatransport`. Cette dépendance ajoutait au passage une
permission `ACCESS_NETWORK_STATE` **que le dépôt ne déclarait nulle part**.

Le scan par caméra a donc été retiré, et avec lui les permissions `CAMERA` et
`ACCESS_NETWORK_STATE`. Pour ajouter un secret d'authentification à deux
facteurs, **collez l'URI `otpauth://…`** dans le champ prévu : l'application en
extrait le secret automatiquement. La plupart des services affichent cette URI
en toutes lettres sous le QR code, derrière un lien du type « impossible de
scanner ? ». Le secret peut aussi être saisi à la main.

## 7. Partage et transmission de données

Expand Down Expand Up @@ -107,7 +125,9 @@ Voir [SECURITY.md](./SECURITY.md).
| ------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `USE_BIOMETRIC` / `USE_FINGERPRINT` | Déverrouillage biométrique optionnel via Android BiometricPrompt. |
| `INTERNET` | Vérification de mises à jour (GitHub Releases) et HIBP (k-anonymity, opt-in). |
| `CAMERA` | Scanner un QR code 2FA pour ajouter un secret TOTP. Flux caméra traité localement, jamais enregistré. |

`CAMERA` et `ACCESS_NETWORK_STATE` ont été **retirées le 2026-08-03** avec le
scan de QR code — voir §6.

## 11. Enfants

Expand Down
27 changes: 23 additions & 4 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,28 @@ This Privacy Policy explains how the **Pass Tech** application — a 100% local

## 6. Network

- The app uses the network for **two strictly local-impact functions** :
1. **Update check** : queries `api.github.com/repos/gitubpatrice/pass_tech/releases/latest` (HTTPS, no auth, no cookie).
- **Pass Tech itself** uses the network for **two strictly local-impact functions** :
1. **Update check** : queries `api.github.com/repos/gitubpatrice/pass_tech/releases/latest` (HTTPS, no auth, no cookie). Suspended while panic mode is active.
2. **HIBP breach check** (Have I Been Pwned, opt-in) : sends only the **first 5 characters of the SHA-1** of a password (k-anonymity model). The actual password never leaves the device.
- Network Security Config rejects cleartext HTTP and user-installed CAs in release builds.
- No telemetry, crash reporting or analytics.
- **We operate no server.** No data is sent to the developer, no in-house analytics, no crash reporting.

### No Google libraries

The app contains **no Google components**: no Play Services, no ML Kit, no
Firebase, no telemetry transport.

This was not the case until 2026-08-03. QR code scanning then relied on
`mobile_scanner`, built on **Google ML Kit**, which pulled in
`play-services-base`, `play-services-basement` and the
`com.google.android.datatransport` transport component. That dependency also
added an `ACCESS_NETWORK_STATE` permission **declared nowhere in the repository**.

Camera scanning was therefore removed, and with it the `CAMERA` and
`ACCESS_NETWORK_STATE` permissions. To add a two-factor secret, **paste the
`otpauth://…` URI** into the corresponding field: the app extracts the secret
automatically. Most services display that URI in plain text below the QR code,
behind a "can't scan?" link. The secret can also be typed by hand.

## 7. Sharing and data transmission

Expand Down Expand Up @@ -106,7 +123,9 @@ See [SECURITY.md](./SECURITY.md) for the vulnerability disclosure policy.
| ------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `USE_BIOMETRIC` / `USE_FINGERPRINT` | Optional biometric unlock via Android BiometricPrompt. |
| `INTERNET` | Update check (GitHub Releases) and HIBP breach check (k-anonymity, opt-in). |
| `CAMERA` | Scan a 2FA QR code to add a TOTP secret. The camera feed is processed locally and never recorded. |

`CAMERA` and `ACCESS_NETWORK_STATE` were **removed on 2026-08-03** along with QR
code scanning — see §6.

## 11. Children

Expand Down
Loading
Loading