Toc, toc. Qui est là ? Touch ID. Encore.

Imagine ça : tu bosses dans ton terminal, tu consultes des secrets 1Password avec op read. Tu as besoin de l’API key de Linear. Touch ID. Celle d’OpenRouter. Touch ID. Celle de Gitea. Touch ID.

En une demi-heure, il m’a demandé le doigt quatorze fois.

Tu sais ce qui se passe quand un outil de sécurité t’interrompt quatorze fois en trente minutes ? Qu’à la cinquième, tu lis plus ce qu’il te demande. Tu poses le doigt par réflexe. “Ouais, peu importe, laisse-moi bosser.”

Et c’est exactement là que la sécurité part en vrille.

Auth fatigue : le problème que personne veut voir

Ça a un nom en sécurité : fatigue d’autorisation. Et c’est pas un concept nouveau. C’est le même principe qu’utilisent les attaques de MFA fatigue : bombarder l’utilisateur de demandes d’autorisation jusqu’à ce qu’il en accepte une par pur épuisement.

En 2022, un gamin de 17 ans est entré dans les systèmes internes d’Uber exactement comme ça. Il a envoyé des demandes de push notification d’authentification à l’employé encore et encore, en pleine nuit, jusqu’à ce que le type en accepte une pour qu’on le laisse dormir.

Évidemment, 1Password qui te demande Touch ID, c’est pas une attaque. Mais l’effet psychologique est identique : ça t’entraîne à approuver sans réfléchir.

C’est comme ces cookie banners qui apparaissent depuis des années sur chaque site. Au début tu les lisais. Maintenant tu cliques “Accepter tout” sans regarder. Félicitations : un mécanisme conçu pour protéger ta vie privée t’a appris à brader ta vie privée plus vite.

Pourquoi 1Password me demandait le doigt à chaque fois

Mon setup : j’utilise op read pour lire des secrets 1Password depuis le terminal. Ça marche nickel. Le problème c’est que j’utilise Claude Code (un assistant IA en terminal), et chaque commande qu’il exécute est un nouveau processus.

1Password a un timeout de session biométrique de 10 minutes d’inactivité, et ça se rafraîchit à chaque usage. En théorie, il devrait pas demander le doigt si souvent. Mais Claude Code réutilise pas les processus : chaque fois qu’il a besoin d’un secret, il lance un nouveau shell, et 1Password l’interprète comme une nouvelle session.

Résultat : Touch ID chaque fois que Claude a besoin d’un secret. Ce qui est constamment.

La solution : un cache de 40 lignes

L’idée est simple : un wrapper qui se place devant op dans le PATH. Quand tu fais op read, il regarde s’il a déjà le résultat en cache et frais. Si oui, il te le renvoie sans toucher 1Password. Sinon, il appelle le vrai op, met le résultat en cache, et voilà.

Pour toute autre sous-commande (op signin, op item list, etc.), ça passe direct au vrai op sans intervenir.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/bash
# ~/.local/bin/op — Wrapper de cache pour 1Password CLI
# Ne cache que 'op read'. Tout le reste passe direct au vrai op.
# TTL du cache configurable avec OP_CACHE_TTL (défaut: 3600s = 1h)

REAL_OP="/opt/homebrew/bin/op"
CACHE_DIR="${HOME}/.cache/op-cache"
CACHE_TTL="${OP_CACHE_TTL:-3600}"

# Ne cacher que 'op read'
if [[ "$1" == "read" ]]; then
    mkdir -p "$CACHE_DIR" && chmod 700 "$CACHE_DIR"

    # Hash de tous les arguments comme clé de cache
    CACHE_KEY=$(printf '%s\0' "$@" | shasum -a 256 | cut -d' ' -f1)
    CACHE_FILE="${CACHE_DIR}/${CACHE_KEY}"

    # Cache hit: fichier existe et pas expiré
    if [[ -f "$CACHE_FILE" ]]; then
        FILE_AGE=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE") ))
        if [[ $FILE_AGE -lt $CACHE_TTL ]]; then
            cat "$CACHE_FILE"
            exit 0
        fi
    fi

    # Cache miss ou expiré: appeler le vrai op
    RESULT=$("$REAL_OP" "$@")
    EXIT_CODE=$?

    # Ne cacher que si op a réussi
    if [[ $EXIT_CODE -eq 0 ]]; then
        printf '%s' "$RESULT" > "$CACHE_FILE"
        chmod 600 "$CACHE_FILE"
    fi

    printf '%s' "$RESULT"
    exit $EXIT_CODE
else
    # Toute autre sous-commande: pass-through direct
    exec "$REAL_OP" "$@"
fi

Tu sauvegardes ça dans ~/.local/bin/op, tu donnes les permissions d’exécution, et comme ~/.local/bin est avant /opt/homebrew/bin dans le PATH, ton wrapper intercepte les appels.

Les décisions de sécurité

Soyons honnêtes sur ce qu’on fait : sauvegarder des secrets en texte brut sur le disque. Ça sonne affreux. Mais remettons les choses en contexte.

Ce qui est mis en cache :

  • Seulement les résultats d’op read (lecture de secrets ponctuels)
  • Tout le reste passe direct au vrai op

Où c’est sauvegardé :

  • ~/.cache/op-cache/ avec permissions 700 (seulement ton utilisateur)
  • Chaque fichier de cache avec permissions 600 (seulement lecture/écriture pour toi)

Combien de temps ça dure :

  • 1 heure par défaut, configurable avec OP_CACHE_TTL

Les noms de fichiers :

  • Ce sont des hashes SHA-256 des arguments, ils révèlent pas quel secret ils contiennent

C’est plus dangereux qu’un .env.local ? Non. C’est exactement pareil. Tes fichiers .env.local sont aussi des secrets en texte brut sur disque avec des permissions restrictives. Et ça tu en as dans chaque projet.

C’est plus dangereux que ce que 1Password fait déjà ? L’app 1Password garde ton vault déchiffré en mémoire pendant qu’elle est déverrouillée. Notre cache est plus limité (seulement les secrets que tu as lus, pas tout le vault) mais moins sophistiqué (disque vs mémoire).

Ce qui nous inquiète (et qu’on sait pas résoudre)

Voici la partie honnête. On est pas experts en sécurité. On a pris des décisions qui nous semblent raisonnables, mais on loupe peut-être quelque chose. Quelques doutes :

1. Est-ce que le cache devrait se vider quand on verrouille l’écran ? Actuellement, si tu verrouilles le Mac et que quelqu’un accède au disque (vol, evil maid), les secrets en cache sont là. Même si si quelqu’un a accès à ton disque, tu as probablement déjà des problèmes plus gros (FileVault devrait protéger contre ça).

2. Il y a des race conditions ? Si deux processus font op read du même secret en même temps, les deux pourraient essayer d’écrire le cache simultanément. En pratique ça devrait pas causer de problèmes graves (le pire cas c’est une lecture partielle), mais c’est pas joli.

3. Le hash suffit ? On utilise SHA-256 des arguments comme nom de fichier. Si quelqu’un a accès à ~/.cache/op-cache/, il peut pas savoir quel secret il y a dans chaque fichier, mais il peut lire le contenu de tous. Les permissions 600 devraient l’empêcher, mais s’il y a un processus compromis qui tourne comme ton utilisateur…

Ce qu’on pourrait améliorer

Quelques idées qu’on a pas implémentées (pour l’instant) :

  • Nettoyage automatique des fichiers expirés (un cron ou un launchd qui purge périodiquement)
  • Chiffrement du cache avec une clé dérivée de la session
  • Notification quand un secret est servi depuis le cache ("(cached)" dans stderr)
  • Invalidation manuelle avec op cache clear ou similaire

C’est là que tu interviens

Écoute, j’écris ça avec l’honnêteté de quelqu’un qui sait qu’il est pas expert en sécurité. On a pris des décisions qui nous semblent sensées. Le threat model est clair : se protéger de la fatigue d’autorisation sans ouvrir de failles évidentes.

Mais “ça nous semble sensé” et “c’est sécurisé” sont deux choses très différentes.

Si tu t’y connais mieux en sécurité que nous (ce qui est pas difficile) et que tu vois une faille évidente, un edge case qu’on a pas considéré, ou simplement une meilleure façon de faire ça : dis-le moi. Sérieusement. Les commentaires sont ouverts et mon email aussi.

Je préfère qu’on me dise “ce que tu as fait c’est un bidouillage dangereux” plutôt que de l’apprendre quand c’est trop tard.

L’éléphant dans la pièce

Est-ce que 1Password devrait résoudre ça de base ? Ouais, probablement. Un timeout configurable par application, ou un mode “session de travail” qui maintiendrait l’autorisation active pendant une période définie, éliminerait le besoin de ce wrapper.

Mais tant qu’ils le font pas, l’alternative est pire : continuer à poser le doigt toutes les 30 secondes jusqu’à ce que ton cerveau décroche et que tu commences à approuver sans regarder.

Parce que c’est ça le paradoxe de la sécurité excessive : si l’outil t’embête trop, tu finis par être moins sécurisé que si tu l’utilisais pas. Au moins sans lui tu es conscient que tu es pas protégé. Avec la fatigue d’autorisation, tu crois être protégé pendant que tu approuves n’importe quoi les yeux fermés.

La meilleure serrure du monde sert à rien si le propriétaire laisse la porte ouverte parce qu’il en a marre de chercher la clé.


Lié : Si ça t’intéresse pourquoi on centralise tous les secrets dans 1Password, lis 39 millions de secrets fuitent sur GitHub. Et si tu veux voir ce qui se passe quand tu donnes trop de capacités à une IA (spoiler : elle envoie 44 emails inventés), Quand ton IA devient ton pire ennemi c’est l’histoire d’horreur.