À propos du projet

Wappie est un serveur API WhatsApp multi-tenant doté d'une archive scellée, accompagné d'un client web consommant la même API. Il connecte des systèmes d'entreprise à WhatsApp via HTTP et WebSocket, avec des espaces de travail partagés, des permissions par numéro et un client de messagerie. Le serveur, le CLI, le client web et l'administration de base sont open source sous licence Apache-2.0 ; l'hébergement géré et la facturation commerciale sont maintenus séparément, le pilote hébergé est gratuit et accessible uniquement sur invitation, et les paiements y sont simulés. Fonctionnellement, le serveur appaire un appareil WhatsApp depuis le terminal à l'aide d'un code à huit caractères (saisi sous Appareils connectés) ou d'un QR code rendu dans le terminal, puis scelle le trafic entrant de cet appareil. Il ingère la synchronisation de l'historique, les modifications de projets, les révocations et les réactions, enregistre les accusés de réception et suit la version que chaque lecteur avait à l'écran. Les médias entrants sont stockés exactement tels que servis par le CDN de Meta, et le téléchargement et l'envoi de médias sortants sont pris en charge, tout comme le mode vue unique. Le contenu structuré (localisation, sondage, contact, événement), les contacts, les noms et photos de profil, le remplissage à la demande et une couche de conversation avec comptage des non-lus, coches, présence, groupes et sondages sont tous implémentés. L'accès se fait via un outil CLI (`wsctl`) et une API HTTP/WebSocket, avec un point de terminaison hébergé et `/v1/ws` pour WebSocket. Le modèle de protection de l'archive est la pièce maîtresse du projet. Les médias WhatsApp arrivent déjà chiffrés en AES-256-CBC avec un HMAC encrypt-then-MAC sous une clé média de 32 octets ; le texte chiffré est stocké tel quel et seule la clé média est scellée à une clé publique d'appareil. Les corps de messages sont scellés avec HPKE (RFC 9180, X25519 + HKDF-SHA256 + AES-256-GCM) sous une clé de contenu couvrant un lot, ce que le README justifie pour des raisons de coût plutôt que de débit. Le serveur ne détient que des clés publiques : il peut sceller mais ne peut pas ouvrir. Chaque appareil possède une paire de clés d'archive générée par le client qui l'a appairé ; la partie privée est scellée à la clé publique de chaque compte autorisé à lire cet appareil (une concession de clé) puis oubliée. Les clés privées de compte sont générées dans le navigateur lors de l'inscription, enveloppées sous une clé dérivée d'Argon2id liée à l'adresse du compte, et ne sont jamais transmises ; un code de récupération enveloppe cette même clé une seconde fois. Le navigateur effectue le HPKE, la gestion des clés de contenu, le traitement des corps de messages, des noms de contacts, des photos de profil et le déchiffrement des pièces jointes à l'intérieur de la page. Les permissions sont stratifiées : les clés API portent une portée `read`, `send` ou `full` ; un membre n'accède qu'aux appareils qui lui sont concédés ; un propriétaire ou un administrateur accède à l'enveloppe de chaque appareil et peut appairer, concéder, créer des clés et basculer un appareil entre le mode discret et loud ; aucune portée n'accède à la configuration du tenant. Un tiers reçoit un compte de service — une paire de clés sans mot de passe, avec des appareils concédés comme pour une personne et accessible via une clé API agissant en son nom. La rétention est désactivée par défaut ; un tenant peut définir une fenêtre que le serveur applique toutes les heures aux messages, reçus, événements de groupe et pièces jointes, tandis que les discussions et contacts restent. Une personne peut être effacée de l'archive sur tous les appareils, et la suppression d'un appareil ou la réinitialisation d'une archive supprime ses objets de pièce jointe du stockage. Les journaux masquent les identifiants à tous les niveaux. L'exécution locale nécessite PostgreSQL 18 ou plus récent pour `uuidv7()`, et un rôle non-superutilisateur car les superutilisateurs contournent la sécurité au niveau des lignes (RLS). Le stockage d'objets est optionnel : sans configuration, les pièces jointes sont mises en file d'attente dans la base de données jusqu'à ce qu'un stockage apparaisse. Une cible `make dev-up` lance Postgres et MinIO dans Docker. Les cibles de test couvrent le formatage, la validation, la mise en page, les tests avec détection de race-condition, le typecheck et le build du client navigateur, la couverture et le fuzzing, et chaque test s'exécute dans son propre schéma Postgres. Le client web est construit avec Vite dans `web/dist` et servi par `WS_WEB_DIR` ; rien n'est intégré dans le binaire Go, et sans build présent, le serveur ne sert que l'API. Pendant le développement, le serveur Vite proxy `/v1` vers le port Go, car le gestionnaire de websocket n'accepte que les connexions de même origine. Le README expose clairement les limites plutôt que d'en suggérer davantage. Un attaquant exécutant du code sur un serveur actif voit le texte en clair entre le déchiffrement Signal, le scellement et le stockage, donc le scellement au repos protège un disque volé, une sauvegarde fuitée ou un dump de base de données, mais pas un processus compromis. Le magasin de session whatsmeow doit rester lisible par le processus ; quiconque le vole peut usurper l'appareil et lire les nouveaux messages, mais pas l'archive. Le texte sortant passe en clair, tout comme les médias sortants, car le téléchargement de WhatsApp n'accepte que le texte clair ; les médias entrants ne sont jamais déchiffrés côté serveur. Les métadonnées des pièces jointes (type, taille, dimensions, durée, hachages) et les métadonnées de routage, y compris les reçus, sont lisibles, donc un dump de base de données révèle le graphe social et qui a lu quoi et quand, mais pas le contenu. Révoquer une concession empêche l'obtention future d'une clé mais ne peut pas rappeler une copie déjà déverrouillée, puisque la clé résidait dans un navigateur. Perdre tout chemin d'accès entraîne la perte permanente de l'archive pour tout le monde. Le matériel de session du navigateur réside dans IndexedDB sous forme de texte chiffré avec des clés WebCrypto non extractibles, et le client livre une politique de sécurité du contenu (CSP) et ne charge aucun JavaScript tiers. Le tableau d'état liste les phases terminées, du squelette, des migrations et du crypto-média jusqu'à l'appairage, l'ingestion, la projection edit/revoke/react, les médias entrants et sortants, la synchronisation de l'historique, la tentative de média, les contacts, le remplissage à la demande, le client web, les clés et comptes par appareil, la couche de conversation, l'incognito et les quotas, ainsi qu'un passage d'audit de sécurité ; la console d'administration est listée comme étape suivante. Des vecteurs de test inter-implémentations sont générés en Go et ouverts par les deux implémentations, incluant des cas négatifs tels qu'un blob déplacé vers une autre ligne ou présenté sous un autre type, afin que le client navigateur ne puisse pas s'accorder silencieusement avec lui-même. Un outil `seeddemo` écrit une petite conversation fictive via le pipeline d'ingestion réel pour développer le client sans appairer de téléphone.