Sécurité
Notre modèle de sécurité en une phrase : il n'y a rien sur un serveur à attaquer. Tout ce qui suit est vérifiable depuis vos DevTools.
Architecture
Le générateur est une application monopage statique servie depuis Cloudflare Pages : pas de comptes utilisateur, pas d'authentification, et aucun chemin de code backend nulle part dans le flux de génération. Chaque opération de génération, d'encodage, de scan et de rendu de QR s'exécute entièrement dans votre navigateur. Un petit point de terminaison serverless se trouve à côté et est listé ici plutôt que caché : /api/contact (le formulaire de contact, qui stocke ce que vous soumettez dans Cloudflare D1 et nous l'envoie par e-mail). Il n'est pas appelé lorsque vous créez un code QR ; le vérificateur de sécurité d'URL est un produit séparé sur check.qr.abundera.ai. Vérifier si un QR code est sûr
Modèle de menace
Parce que le générateur ne collecte, ne stocke ni ne transmet aucune donnée utilisateur (les deux points de terminaison ci-dessus ne traitent que ce que vous leur envoyez explicitement), les menaces les plus courantes des applications web — vol de credentials, violation de base de données, détournement de session, injection côté serveur — ne s'y appliquent pas. La surface d'attaque restante est le bundle d'assets statiques (HTML, CSS, JavaScript) servi depuis notre origine. Nous concevons en supposant :
- Compromission de l'origine : un attaquant substitue l'un de nos assets groupés par un asset malveillant. La CSP ci-dessous limite le rayon de l'explosion ; les tokens cache-buster facilitent le retour à une version connue comme valide.
- Entrée fournie par l'utilisateur : payloads QR, noms vCard, mots de passe WiFi, lignes CSV par lot, contenus d'images scannées. Chaque chemin d'entrée est traité comme non fiable lorsqu'il est rendu à nouveau dans le DOM.
- Images fournies par l'utilisateur : URLs de photos vCard et uploads de logos. Les images sont rendues dans un canvas, jamais intégrées dans le DOM comme balisage brut.
- Extensions de navigateur tierces : hors périmètre. Si une extension dans votre navigateur a la permission de modifier chaque page, elle peut modifier la nôtre. Nos garanties tiennent lorsque la page est chargée sans modification.
Politique de Sécurité du Contenu — par directive
La politique ci-dessous est lue depuis le fichier _headers déployé au moment de la compilation de cette page, elle ne peut donc pas diverger de ce que l'edge envoie. Vérifiez-la dans les en-têtes de réponse de toute requête.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://abundera.ai 'wasm-unsafe-eval' https://challenges.cloudflare.com;
worker-src 'self' blob:;
style-src 'self' 'unsafe-inline' https://abundera.ai;
font-src 'self';
img-src 'self' data: blob: https:;
connect-src 'self' https: https://challenges.cloudflare.com;
frame-src 'self' https://challenges.cloudflare.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'Ce que chaque directive nous permet de faire et où elle représente un compromis :
default-src 'self'— le plancher dur. Tout ce que nous ne relâchons pas explicitement reste de même origine.script-src 'self' https://abundera.ai 'wasm-unsafe-eval' https://challenges.cloudflare.com— pas de<script>inline, pas deeval().abundera.aiest notre propre domaine parent et sert un seul script partagé, le sélecteur de langue.challenges.cloudflare.comest le widget Turnstile utilisé uniquement sur /contact/.'wasm-unsafe-eval'est nécessaire par le chemin de compilation WebAssembly de notre bibliothèque d'encodage QR ; il ne permet PAS leeval()traditionnel. Notre propre vérification pré-déploiement scanne chaque page HTML à la recherche de scripts inline et fait échouer le build.style-src 'self' 'unsafe-inline'— une vraie concession. Notre aperçu QR calcule des couleurs au niveau pixel inline (attributs de style sur des modules individuels). Une liste d'autorisations basée sur des hash fonctionnerait mais échouerait à toute mise à jour de style sans déploiement. Compromis : nous acceptons la politique de style légèrement plus faible ; les styles ne peuvent pas exfiltrer de données de toute façon (CSS n'a pas de portéeconnect-src).img-src 'self' data: blob: https:—data:pour les rendus QR inline,blob:pour les URLs de téléchargement d'export,https:pour les URLs de photos vCard et d'avatars fournies par l'utilisateur. Les URLs fournies par l'utilisateur ne s'exécutent jamais, elles se rendent seulement. Notez que cette directive est aussi un canal sortant : n'importe quel script exécuté sur la page pourrait demander une image à n'importe quel hôte HTTPS et glisser des données dans l'URL. Nous l'acceptons parce que la fonctionnalité en a besoin, et nous le disons ici plutôt que de présenter la politique comme étanche.connect-src 'self' https: https://challenges.cloudflare.com— celle à lire attentivement.https:est une source de schéma : elle permet àfetch(), XMLHttpRequest, WebSocket sur TLS, EventSource etsendBeacon()d'atteindre n'importe quelle origine HTTPS, pas seulement celles que vous avez saisies. Elle est là pour que le champ photo vCard et la vérification de sécurité d'URL puissent récupérer une adresse que vous avez collée. Elle n'empêche pas l'exfiltration par un script malveillant ; aucune directive CSP ne le fait une fois que le script s'exécute déjà en notre nom. Ce qui empêche l'exfiltration, c'est que notre code livré ne fait aucune requête de ce type, ce que vous pouvez observer dans l'onglet Network.frame-src 'self' https://challenges.cloudflare.com— seul le widget Turnstile peut être intégré dans un cadre.frame-ancestors 'none'— aucun autre site ne peut nous intégrer. Prévient le clickjacking.base-uri 'self'— une balise<base>injectée de façon malveillante ne peut pas rediriger les URLs relatives vers l'origine d'un attaquant.form-action 'self'— tout formulaire injecté ne peut poster qu'à notre origine. Le formulaire de contact est le seul formulaire qui envoie des données où que ce soit.
Ce que CSP ne peut pas faire
CSP limite les origines depuis lesquelles le navigateur chargera du code. Elle ne vérifie pas ce que fait le code chargé. Si notre origine était compromise, le premier point du modèle de menace, le script de l'attaquant s'exécuterait avec les mêmes permissions que les nôtres, y compris les autorisations connect-src et img-src ci-dessus. Nos défenses contre ce scénario se situent en amont de CSP : les déploiements passent par un seul compte Cloudflare avec des jetons à portée limitée, les noms de fichiers d'assets sont hachés par contenu, ce qui fait qu'un fichier substitué change chaque URL qui le référence, et le service worker ne met en cache que les réponses GET de même origine. Le manifeste décrit le site tel qu'il est livré. Ce n'est pas une affirmation qu'aucune page web ne pourrait jamais envoyer de données quelque part, et nous préférons que vous nous teniez à cette déclaration plus étroite et vérifiable.
Des politiques CSP différentes s'appliquent à /bio/* (img-src assoupli pour les avatars fournis par l'utilisateur) et /embed/* (frame-ancestors assoupli pour l'intégration intentionnelle). Les deux sont documentées dans site/_headers.
En-têtes de transport et de cadrage
- HSTS :
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload— 1 an, tous les sous-domaines, éligible pour la liste de préchargement HSTS. Les rétrogradations vers HTTP sont refusées par les navigateurs conformes. - X-Frame-Options: DENY — redondant avec CSP
frame-ancestors, conservé pour la couverture des navigateurs plus anciens. - X-Content-Type-Options: nosniff — prévient les attaques de confusion MIME.
- Referrer-Policy: strict-origin-when-cross-origin — les clics sur des liens sortants révèlent l'origine mais pas le chemin.
- Permissions-Policy :
camera=(self), microphone=(), geolocation=()— caméra autorisée uniquement pour notre propre scanner ; micro et localisation explicitement refusés même en cas d'intégration.
Service worker
Notre service worker (site/sw.js) met en cache uniquement les assets de même origine. Le gestionnaire fetch rejette explicitement les requêtes cross-origin et les méthodes non-GET — vous pouvez lire la logique dans /sw.js (le code source est livré assez peu minifié pour rester lisible). Les écritures en cache sont enveloppées dans event.waitUntil() afin qu'elles ne puissent pas être abandonnées en cours de navigation.
Assainissement des entrées
Chaque chemin de rendu qui accepte une entrée utilisateur la traite comme du texte non fiable :
- Les aperçus de payload QR utilisent
textContent, pasinnerHTML. - Les cibles de partage (presse-papiers,
navigator.share()) transmettent le texte utilisateur comme une chaîne, jamais comme du balisage. - Les exports SVG sont générés par notre bibliothèque d'encodage ; le contenu utilisateur est encodé en base64 dans
<image>xlink:href, pas injecté comme éléments SVG. - Les aperçus d'impression utilisent une URL blob, pas
document.write(). - L'analyse de localStorage est enveloppée dans
try/catch— une entrée corrompue produit une nouvelle valeur par défaut vide, jamais une exception qui pourrait se dérouler dans un chemin de code actif. - Les URLs fournies par l'utilisateur affichées comme boutons "Ouvrir le lien" sont limitées à
http(s)://— les schémasjavascript:etdata:sont rejetés.
Récupération d'images cross-origin
Quand un utilisateur colle une URL https: comme photo vCard ou logo, le navigateur la récupère en respectant CORS et la liste d'autorisation img-src de notre CSP. L'image est rendue dans un canvas. Elle ne devient jamais du DOM actif, ne s'exécute jamais comme du code, et n'atteint jamais notre origine — la récupération se fait navigateur → image distante, et le résultat est rendu côté client. Un attaquant contrôlant une URL d'image distante peut savoir que l'URL a été chargée (une ligne de log sur son propre serveur) mais ne peut pas exfiltrer quoi que ce soit de notre page.
Intégrité des Sous-Ressources (SRI)
Tout le JavaScript et CSS que nous livrons est de même origine. Nous ne chargeons pas de scripts ou feuilles de style tiers, donc les hash SRI ne sont pas applicables. Si jamais nous chargeons un asset tiers, nous lui ajouterons un attribut integrity SRI et documenterons le processus de mise à jour des hash dans cette page.
Signaler une vulnérabilité
Si vous découvrez un problème de sécurité affectant Abundera QR — que ce soit dans notre code, notre déploiement, ou une dépendance que nous livrons — veuillez le signaler de façon privée à security@abundera.ai. Nous visons un triage sous 72 heures. Vous pouvez également nous contacter via les coordonnées de notre fichier /.well-known/security.txt.
Pas de programme de bug bounty (pour l'instant)
Nous n'offrons actuellement pas de primes payées, mais chaque rapport valide confirmé reçoit un crédit dans le changelog et nos remerciements publics.
Vérifiez tout ce qui précède
Chaque affirmation sur cette page est falsifiable depuis les DevTools de votre navigateur sans nous faire confiance :
- CSP : DevTools → Réseau →
/→ En-têtes. Lisez l'en-tête de réponseContent-Security-Policy. - HSTS + en-têtes de sécurité : même endroit — tous
Strict-Transport-Security,X-Frame-Options, etc. sont visibles. - Pas d'appels sortants : DevTools → Réseau → Fetch/XHR. Générez un QR. Observez le compteur rester à zéro.
- Portée du service worker : DevTools → Application → Service Workers. Vérifiez la source du script et la liste des assets en cache.
- Pas de cookies : DevTools → Application → Cookies. Vide.
- Guide complet : la section de vérification du manifeste.
Contact
Divulgations de sécurité : security@abundera.ai