Sicurezza
Il nostro modello di sicurezza in una frase: non c'è nulla su un server da attaccare. Tutto ciò che segue è verificabile dai tuoi DevTools.
Architettura
Il generatore è un'applicazione a pagina singola statica distribuita da Cloudflare Pages: nessun account utente, nessuna autenticazione e nessun percorso di codice backend in nessun punto del flusso di generazione. Ogni operazione di generazione, codifica, scansione e rendering QR viene eseguita interamente nel tuo browser. Un piccolo endpoint serverless affianca il generatore ed è elencato qui invece di essere nascosto: /api/contact (il modulo di contatto, che archivia quanto invii in Cloudflare D1 e ce lo invia via email). Non viene chiamato mentre crei un codice QR; il controllo di sicurezza URL è un prodotto separato su check.qr.abundera.ai. Verifica se un codice QR è sicuro
Modello di minaccia
Poiché il generatore non raccoglie, archivia o trasmette dati utente (i due endpoint sopra gestiscono solo ciò che invii loro esplicitamente), le minacce più comuni alle app web, furto di credenziali, violazione del database, hijacking di sessione, iniezione lato server, non si applicano a esso. La superficie di attacco residua è il bundle di asset statici (HTML, CSS, JavaScript) distribuito dalla nostra origine. Progettiamo assumendo:
- Compromissione dell'origine: un attaccante sostituisce uno dei nostri asset inclusi con uno malevolo. Il CSP di seguito limita il raggio d'azione; i token cache-buster rendono facile il rollback a una versione nota come valida.
- Input fornito dall'utente: payload QR, nomi vCard, password WiFi, righe CSV batch, contenuti di immagini scansionate. Ogni percorso di input viene trattato come non attendibile quando viene renderizzato nel DOM.
- Immagini fornite dall'utente: URL di foto vCard e caricamenti di loghi. Le immagini vengono renderizzate in un canvas, mai incorporate nel DOM come markup grezzo.
- Estensioni browser di terze parti: fuori perimetro. Se un'estensione nel tuo browser ha il permesso di modificare ogni pagina, può modificare anche la nostra. Le nostre garanzie valgono quando la pagina viene caricata non modificata.
Content Security Policy, per direttiva
La policy qui sotto viene letta dal file _headers effettivamente distribuito al momento della build di questa pagina, quindi non può divergere da ciò che invia l'edge. Verificala nelle intestazioni di risposta di qualsiasi richiesta.
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'Cosa permette ogni direttiva e dove scende a compromessi:
default-src 'self', il limite inferiore. Tutto ciò che non allentiamo esplicitamente rimane same-origin.script-src 'self' https://abundera.ai 'wasm-unsafe-eval' https://challenges.cloudflare.com, nessun<script>inline, nessuneval().abundera.aiè il nostro dominio principale e serve un unico script condiviso, il selettore di lingua.challenges.cloudflare.comè il widget Turnstile usato solo su /contact/.'wasm-unsafe-eval'è richiesto dal percorso di compilazione WebAssembly della nostra libreria encoder QR; NON consente il tradizionaleeval(). Il nostro controllo pre-deploy scansiona ogni pagina HTML alla ricerca di script inline e interrompe la build.style-src 'self' 'unsafe-inline', una vera concessione. La nostra anteprima QR calcola i colori a livello di pixel inline (attributi style sui singoli moduli). Una allowlist basata su hash funzionerebbe ma fallirebbe ad ogni aggiornamento di stile senza un deploy. Compromesso: accettiamo la policy di stile leggermente più debole; lo styling non può comunque esfiltrare dati (CSS non ha portataconnect-src).img-src 'self' data: blob: https:,data:per i render QR inline,blob:per gli URL di download dell'export,https:per gli URL di foto vCard e avatar forniti dall'utente. Gli URL forniti dall'utente non vengono mai eseguiti, solo renderizzati. Tieni presente che questa direttiva è anche un canale in uscita: qualsiasi script in esecuzione sulla pagina potrebbe richiedere un'immagine da qualsiasi host HTTPS e inserire dati nell'URL. Lo accettiamo perché la funzione ne ha bisogno, e lo dichiariamo qui invece di descrivere la policy come a tenuta stagna.connect-src 'self' https: https://challenges.cloudflare.com, quella da leggere con attenzione.https:è uno scheme source: consente afetch(), XMLHttpRequest, WebSocket su TLS, EventSource esendBeacon()di raggiungere qualsiasi origine HTTPS, non solo quelle che hai digitato. È lì perché il campo della foto vCard e il controllo di sicurezza URL possano recuperare un indirizzo che hai incollato. Non impedisce l'esfiltrazione da parte di uno script malevolo; nessuna direttiva CSP lo fa una volta che lo script è già in esecuzione come noi. Ciò che impedisce l'esfiltrazione è che il nostro codice distribuito non fa richieste di questo tipo, cosa che puoi osservare nella scheda Rete.frame-src 'self' https://challenges.cloudflare.com, solo il widget Turnstile può essere incorniciato.frame-ancestors 'none', nessun altro sito può incorporarci. Previene il clickjacking.base-uri 'self', un tag<base>iniettato in modo malevolo non può reindirizzare gli URL relativi all'origine di un attaccante.form-action 'self', qualsiasi form iniettata può inviare dati solo alla nostra origine. Il modulo di contatto è l'unico form che invia dati da qualche parte.
Cosa non può fare la CSP
La CSP limita da dove il browser può caricare codice. Non verifica cosa fa il codice caricato. Se la nostra origine fosse compromessa, il primo punto del modello di minaccia, lo script dell'attaccante verrebbe eseguito con gli stessi permessi dei nostri, incluse le concessioni connect-src e img-src sopra. Le nostre difese contro questo scenario si collocano a monte della CSP: i deploy escono attraverso un unico account Cloudflare con token con ambito limitato, i nomi dei file degli asset sono hashati in base al contenuto quindi un file sostituito cambia ogni URL che lo referenzia, e il service worker mette in cache solo risposte GET same-origin. Il manifesto descrive il sito così com'è distribuito. Non è un'affermazione che nessuna pagina web potrebbe mai inviare dati da qualche parte, e preferiamo che tu ci giudichi sull'affermazione più ristretta e verificabile.
Policy CSP diverse si applicano a /bio/* (img-src rilassato per avatar forniti dall'utente) e /embed/* (frame-ancestors rilassato per embedding intenzionale). Entrambe sono documentate in site/_headers.
Intestazioni di trasporto e framing
- HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload, 1 anno, tutti i sottodomini, idoneo per la lista di preload HSTS. I downgrade a HTTP vengono rifiutati dai browser conformi. - X-Frame-Options: DENY, ridondante con CSP
frame-ancestors, mantenuto per la copertura dei browser più datati. - X-Content-Type-Options: nosniff, previene gli attacchi di confusione MIME.
- Referrer-Policy: strict-origin-when-cross-origin, i click sui link in uscita rivelano l'origine ma non il percorso.
- Permissions-Policy:
camera=(self), microphone=(), geolocation=(), la fotocamera è consentita solo per il nostro scanner; microfono e posizione sono esplicitamente negati anche se incorporati.
Service worker
Il nostro service worker (site/sw.js) memorizza nella cache solo gli asset same-origin. Il gestore fetch rifiuta esplicitamente le richieste cross-origin e i metodi non-GET, puoi leggere la logica in /sw.js (il sorgente è distribuito abbastanza non minificato da poter essere seguito). Le scritture in cache sono avvolte in event.waitUntil() in modo che non possano essere interrotte a metà navigazione.
Sanificazione dell'input
Ogni percorso di rendering che accetta input utente lo tratta come testo non attendibile:
- Le anteprime del payload QR usano
textContent, noninnerHTML. - I target di condivisione (clipboard,
navigator.share()) passano il testo utente come stringa, mai come markup. - Gli export SVG vengono generati dalla nostra libreria encoder; il contenuto utente viene codificato in base64 in
<image>xlink:href, non iniettato come elementi SVG. - Le anteprime di stampa usano un URL blob, non
document.write(). - Il parsing di localStorage è avvolto in
try/catch, una voce corrotta produce un nuovo default vuoto, mai un'eccezione che potrebbe svolgersi in un percorso di codice attivo. - Gli URL forniti dall'utente mostrati come pulsanti "Apri link" sono limitati a
http(s)://, gli schemijavascript:edata:vengono rifiutati.
Recupero di immagini cross-origin
Quando un utente incolla un URL https: come foto vCard o logo, il browser lo recupera soggetto a CORS e alla allowlist img-src del nostro CSP. L'immagine viene renderizzata in un canvas. Non diventa mai DOM attivo, non viene mai eseguita come codice e non raggiunge mai la nostra origine, il fetch è browser → immagine remota, e il risultato viene disegnato lato client. Un attaccante che controlla un URL di immagine remota può tracciare che l'URL è stato caricato (una riga di log sul proprio server) ma non può esfiltrare nulla dalla nostra pagina.
Subresource Integrity (SRI)
Tutto il JavaScript e il CSS che distribuiamo è same-origin. Non carichiamo script o fogli di stile di terze parti, quindi gli hash SRI non sono applicabili. Se mai caricheremo un asset di terze parti, includeremo un attributo SRI integrity su di esso e documenteremo il processo di aggiornamento dell'hash in questa pagina.
Segnalare una vulnerabilità
Se scopri un problema di sicurezza che interessa Abundera QR, nel nostro codice, nel nostro deployment o in una dipendenza che distribuiamo, ti preghiamo di segnalarlo privatamente a security@abundera.ai. Puntiamo a fare triage entro 72 ore. Puoi anche contattarci tramite i dettagli di contatto nel nostro file /.well-known/security.txt.
Nessun bug bounty (ancora)
Al momento non offriamo premi in denaro, ma ogni segnalazione valida confermata riceve credito nel changelog e il nostro ringraziamento pubblico.
Verifica quanto sopra
Ogni affermazione in questa pagina è verificabile dai DevTools del tuo browser senza doversi fidare di noi:
- CSP: DevTools → Network →
/→ Headers. Leggi l'intestazione di rispostaContent-Security-Policy. - HSTS + intestazioni di sicurezza: stesso posto, tutti
Strict-Transport-Security,X-Frame-Options, ecc. sono visibili. - Nessuna chiamata in uscita: DevTools → Network → Fetch/XHR. Genera un QR. Guarda il contatore rimanere a zero.
- Scope del service worker: DevTools → Application → Service Workers. Verifica la sorgente dello script e l'elenco degli asset memorizzati nella cache.
- Nessun cookie: DevTools → Application → Cookies. Vuoto.
- Procedura completa: la sezione di verifica del manifesto.
Contatto
Segnalazioni di sicurezza: security@abundera.ai