Security
Our security model in one sentence: there's nothing on a server to attack. Everything below is falsifiable from your DevTools.
Architecture
Abundera QR is a static single-page application served from Cloudflare Pages. There is no application server, no database, no user accounts, no authentication, no API endpoints, and no backend code path that processes user data. Every QR generation, encoding, scanning, and rendering operation runs entirely inside your browser.
Threat model
Because we collect, store, and transmit no user data, the most common web-app threats, credential theft, database breach, session hijacking, server-side injection, do not apply. The remaining attack surface is the static asset bundle (HTML, CSS, JavaScript) served from our origin. We design assuming:
- Origin compromise: an attacker substitutes one of our bundled assets for a malicious one. CSP below limits blast radius; cache-buster tokens make rollback to a known-good version easy.
- User-supplied input: QR payloads, vCard names, WiFi passwords, batch CSV rows, scanned image contents. Every input path is treated as untrusted when it renders back to the DOM.
- User-supplied images: vCard photo URLs and logo uploads. Images are rendered into a canvas, never embedded into the DOM as raw markup.
- Third-party browser extensions: out of scope. If an extension in your browser has permission to modify every page, it can modify ours. Our guarantees hold when the page is loaded unmodified.
Content Security Policy, by directive
The current policy (verify in Response headers for any request):
Content-Security-Policy:
default-src 'self';
script-src 'self' 'wasm-unsafe-eval';
worker-src 'self' blob:;
style-src 'self' 'unsafe-inline';
font-src 'self';
img-src 'self' data: blob: https:;
connect-src 'self' https:;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'What each directive lets us do and where it compromises:
default-src 'self', the hard floor. Anything we don't explicitly relax stays same-origin.script-src 'self' 'wasm-unsafe-eval', no inline<script>, noeval().'wasm-unsafe-eval'is needed by our QR encoder library's WebAssembly compilation path; it does NOT permit traditionaleval(). Our own pre-deploy check scans every HTML page for inline scripts and fails the build.style-src 'self' 'unsafe-inline', a real concession. Our QR preview computes pixel-level colours inline (style attributes on individual modules). A hash-based allowlist would work but would fail on any style update without a deploy. Tradeoff: we accept the slightly weaker style policy; styling can't exfiltrate data anyway (CSS has noconnect-srcreach).img-src 'self' data: blob: https:,data:for inline QR renders,blob:for export download URLs,https:for user-supplied vCard photo URLs. User-supplied URLs never execute, only render.connect-src 'self' https:, fetch() is restricted to our origin plus user-initiated HTTPS fetches (e.g., scanning a photo URL). Prevents exfiltration via DNS / WebSocket / beacon to arbitrary hosts.frame-ancestors 'none', no other site can embed us. Prevents clickjacking.base-uri 'self', a malicious injected<base>tag can't redirect relative URLs to an attacker's origin.form-action 'self', any injected form can only post back to our origin. We have no forms that send data server-side; this is belt-and-suspenders.
Different CSP policies apply to /bio/* (relaxed img-src for user-supplied avatars) and /embed/* (relaxed frame-ancestors for intentional embedding). Both are documented in site/_headers.
Transport + framing headers
- HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload, 1 year, all subdomains, eligible for the HSTS preload list. Downgrades to HTTP are refused by conformant browsers. - X-Frame-Options: DENY, redundant with CSP
frame-ancestors, kept for older browser coverage. - X-Content-Type-Options: nosniff, prevents MIME confusion attacks.
- Referrer-Policy: strict-origin-when-cross-origin, outbound link clicks leak the origin but not the path.
- Permissions-Policy:
camera=(self), microphone=(), geolocation=(), camera permitted only for our own scanner; mic and location explicitly denied even if embedded.
Service worker
Our service worker (site/sw.js) caches only same-origin assets. The fetch handler explicitly rejects cross-origin requests and non-GET methods, you can read the logic on GitHub. Cache writes are wrapped in event.waitUntil() so they can't be dropped mid-navigation.
Input sanitization
Every rendering path that accepts user input treats it as untrusted text:
- QR payload previews use
textContent, notinnerHTML. - Share targets (clipboard,
navigator.share()) pass user text as a string, never as markup. - SVG exports are generated from our encoder library; user content is base64-encoded into
<image>xlink:href, not injected as SVG elements. - Print previews use a blob URL, not
document.write(). - localStorage parsing is wrapped in
try/catch, a corrupt entry yields a fresh empty default, never an exception that could unwind into a live code path. - User-supplied URLs shown as "Open link" buttons are restricted to
http(s)://,javascript:anddata:schemes are rejected.
Cross-origin image fetching
When a user pastes a https: URL as a vCard photo or logo, the browser fetches it subject to CORS and our CSP's img-src allowlist. The image renders into a canvas. It never becomes live DOM, never runs as code, and never reaches our origin, the fetch is browser → remote image, and the result is painted client-side. An attacker controlling a remote image URL can track that the URL was loaded (a log line on their own server) but cannot exfiltrate anything from our page.
Subresource Integrity (SRI)
All JavaScript and CSS we ship is same-origin. We do not load third-party scripts or stylesheets, so SRI hashes are not applicable. If we ever load a third-party asset, we'll ship an SRI integrity attribute on it and document the hash-update process in this page.
Reporting a vulnerability
If you discover a security issue affecting Abundera QR, whether in our code, our deployment, or in a dependency we ship, please report it privately to security@abundera.ai. We aim to triage within 72 hours. You can also reach us via the contact details in our /.well-known/security.txt file.
No bug bounty (yet)
We do not currently offer paid bounties, but every confirmed valid report receives credit in the changelog and our public thanks.
Verify any of the above
Every claim on this page is falsifiable from your browser's DevTools without trusting us:
- CSP: DevTools → Network →
/→ Headers. Read theContent-Security-Policyresponse header. - HSTS + security headers: same place, all
Strict-Transport-Security,X-Frame-Options, etc. are visible. - No outbound calls: DevTools → Network → Fetch/XHR. Generate a QR. Watch the count stay at zero.
- Service worker scope: DevTools → Application → Service Workers. Verify the script source and cached asset list.
- No cookies: DevTools → Application → Cookies. Empty.
- Full walkthrough: the manifesto's verification section.
Contact
Security disclosures: security@abundera.ai