Lorsque nous avons conçu la couche LLM de JieGou, nous avions une contrainte que la plupart des plateformes n’ont pas : les clients doivent pouvoir utiliser leurs propres clés API, et nous ne devons jamais voir les clés en clair au repos. Cet article couvre le fonctionnement de notre système Bring Your Own Key (BYOK), le schéma de chiffrement, l’architecture de routage des fournisseurs et les garde-fous qui maintiennent le tout en fonctionnement fiable.
Pourquoi le BYOK est important
La plupart des plateformes IA proxifient vos appels à travers leurs propres clés API. Cela signifie que vos données transitent par leurs comptes, votre utilisation est soumise à leurs limites de débit, et vous n’avez aucun contrôle sur les modèles ou endpoints utilisés.
Avec le BYOK, chaque client connecte ses propres clés API Anthropic, OpenAI ou Google. Les appels vont directement au fournisseur en utilisant les identifiants du client. JieGou orchestre le workflow mais ne voit pas les payloads de requête ou de réponse lorsque les clés BYOK sont utilisées.
Chiffrement des clés : AES-256-GCM
Les clés API sont chiffrées au repos avec AES-256-GCM avec un vecteur d’initialisation de 12 octets et un tag d’authentification de 16 octets. La clé de chiffrement est une valeur de 256 bits dérivée d’une chaîne hexadécimale de 64 caractères stockée comme variable d’environnement — elle ne touche jamais la base de données.
Le format de stockage est une concaténation encodée en Base64 de IV + authTag + ciphertext. Nous stockons un préfixe sûr de 8 caractères à côté pour l’affichage (« sk-proj… ») afin que les utilisateurs puissent identifier quelle clé est stockée sans la déchiffrer.
Les clés sont stockées dans une collection Firestore account_api_keys avec des champs pour l’identifiant de compte, le nom du fournisseur, le blob de clé chiffré et un indicateur de validité.
Flux de résolution des clés
Lorsqu’une étape de workflow a besoin d’appeler un LLM, le résolveur de clés exécute cette séquence :
- Contrôle du plan — Vérifier si l’abonnement du compte autorise le BYOK (mis en cache dans Redis avec un TTL de 10 minutes).
- Recherche dans le cache Redis — Les clés déchiffrées sont mises en cache pendant 5 minutes. Une valeur sentinelle (
__none__) indique qu’une clé a été recherchée précédemment et n’existe pas, évitant les lectures Firestore répétées. - Recherche Firestore — Si le cache manque, récupérer depuis
account_api_keys. - Vérification de validité — Ignorer les clés marquées comme invalides par le système d’auto-invalidation.
- Déchiffrement — Le déchiffrement AES-256-GCM se produit à la volée, en mémoire.
- Fail-open — Si quoi que ce soit tourne mal à n’importe quelle étape, se rabattre sur la clé API de la plateforme. Ne jamais dégrader l’expérience utilisateur.
La conception fail-open est délibérée. Si Redis est down, si le déchiffrement échoue, si la lecture Firestore renvoie une erreur — le workflow s’exécute quand même, en utilisant les clés de la plateforme. Les utilisateurs voient leur travail aboutir plutôt qu’une erreur cryptique.
Routage des fournisseurs
La couche LLM est construite sur le Vercel AI SDK avec une abstraction de fournisseur qui prend en charge Anthropic, OpenAI et Google. Chaque fournisseur a deux chemins d’instanciation :
Clé plateforme (singleton) — Une instance partagée créée au démarrage, utilisée pour les comptes du tier gratuit ou comme solution de repli BYOK.
BYOK (éphémère) — Une nouvelle instance de fournisseur créée par appel avec la clé déchiffrée du client. Cette instance n’est pas mise en cache — elle est utilisée pour une seule requête et supprimée, de sorte que les clés déchiffrées ne persistent pas en mémoire.
Les workflows peuvent spécifier différents modèles par étape. Un pipeline de contenu pourrait utiliser Claude pour la rédaction nuancée à l’étape 1 et GPT pour l’extraction de données structurées à l’étape 2. Le routage des fournisseurs gère cela de manière transparente.
Auto-invalidation
Lorsqu’un appel LLM renvoie une erreur d’authentification (HTTP 401, 402 ou 403, ou des corps de réponse correspondant à des patterns comme « invalid api key » ou « unauthorized »), le système marque automatiquement cette clé comme invalide dans Firestore et l’évince du cache Redis.
L’utilisateur reçoit un message clair : « Votre clé API {Provider} est invalide ou a été révoquée. Veuillez mettre à jour votre clé API dans les paramètres du compte. » Les appels suivants se rabattent sur les clés de la plateforme jusqu’à ce que l’utilisateur fournisse une nouvelle clé.
Nous vérifions contre 11 patterns d’erreur connus à travers les fournisseurs. Cela détecte les clés renouvelées, les clés révoquées et les clés qui ont dépassé leurs limites de dépenses.
Circuit Breaker
Chaque fournisseur LLM a un circuit breaker par fournisseur (pas par compte — un seul fournisseur en panne affecte tout le monde).
Le breaker se déclenche après 5 erreurs dans une fenêtre de 60 secondes. Une fois ouvert, il reste ouvert pendant 30 secondes avant d’autoriser une seule requête de sonde (état semi-ouvert). Si la sonde réussit, le circuit se ferme.
Seules les pannes côté serveur comptent : réponses 5xx, timeouts et erreurs de connexion. Les erreurs client (4xx) comme une clé API invalide ne déclenchent pas le breaker — elles sont gérées par l’auto-invalidation à la place.
Le circuit breaker lui-même est fail-open. Si Redis n’est pas disponible pour le suivi d’état, toutes les requêtes sont autorisées. C’est cohérent avec notre philosophie globale : quand l’infrastructure est dégradée, laisser le travail de l’utilisateur se poursuivre.
Contrôle de concurrence
Chaque compte est limité à 10 appels LLM concurrents via un sémaphore basé sur Redis. Cela empêche un seul compte de consommer toutes les connexions disponibles à un fournisseur lors d’une grande exécution par lots.
Le sémaphore utilise INCR/DECR avec un filet de sécurité TTL de 5 minutes (si un processus plante sans décrémenter, le compteur expire automatiquement). En cas de dépassement de capacité, le compteur est immédiatement décrémenté pour éviter une fuite. Comme tout le reste, c’est fail-open en cas d’erreur Redis.
Leçons apprises
Le fail-open est le bon défaut pour les fonctionnalités optionnelles. Le BYOK est une amélioration, pas une exigence. Si le pipeline de chiffrement, la couche de cache ou la résolution de clé échoue, l’utilisateur doit quand même pouvoir exécuter son workflow. Nous journalisons l’échec pour investigation mais ne bloquons jamais l’exécution.
Les valeurs sentinelles de cache empêchent les ruées. Sans la sentinelle __none__, un compte sans clés BYOK toucherait Firestore à chaque appel LLM. Mettre en cache le résultat négatif pendant 5 minutes maintient les lectures Firestore prévisibles.
Les instances de fournisseur éphémères empêchent la fuite de clés. En créant une nouvelle instance de fournisseur par appel et en la laissant être collectée par le garbage collector, nous minimisons la fenêtre pendant laquelle une clé déchiffrée existe en mémoire. Ce n’est pas aussi robuste qu’un module de sécurité matériel, mais c’est une réduction significative de la surface d’attaque pour une application SaaS.
Des circuit breakers par fournisseur avec une concurrence par compte est la bonne granularité. Les pannes de fournisseur sont des événements globaux ; la concurrence est une préoccupation par tenant. Les mélanger serait soit trop agressif (casser un compte casse le breaker pour tout le monde) soit trop permissif (pas de protection contre les pannes à l’échelle du fournisseur).