安全
我们的安全模型一句话概括:服务器上没有任何可被攻击的东西。以下所有内容均可通过您的 DevTools 验证。
架构
本生成器是一个由 Cloudflare Pages 提供服务的静态单页应用程序:没有用户账户、没有身份认证,生成流程中的任何环节都没有后端代码路径。每个 QR 的生成、编码、扫描和渲染操作完全在您的浏览器内运行。此外还有一个小型无服务器端点,我们把它列在这里而不是隐藏起来:/api/contact(联系表单,将您提交的内容存入 Cloudflare D1 并通过邮件发给我们)。制作 QR 码的过程中不会调用它;URL 安全检查工具是一个独立产品,位于 check.qr.abundera.ai。 检查二维码是否安全
威胁模型
由于本生成器不收集、存储或传输任何用户数据(上述两个端点只处理您主动发送给它们的内容),最常见的 Web 应用威胁, 凭证盗取、数据库泄露、会话劫持、服务器端注入, 对它均不适用。剩余的攻击面是从我们的源站提供的静态资产包(HTML、CSS、JavaScript)。我们的设计假设:
- 源站被攻陷:攻击者将我们捆绑的某个资产替换为恶意版本。下面的 CSP 限制了破坏范围;cache-buster 令牌使回滚到已知正常版本变得容易。
- 用户提供的输入:QR 载荷、vCard 姓名、Wi-Fi 密码、批量 CSV 行、扫描图片内容。每条输入路径在渲染回 DOM 时均被视为不可信。
- 用户提供的图片:vCard 照片 URL 和 Logo 上传。图片被渲染到 canvas 中,绝不作为原始标记嵌入 DOM。
- 第三方浏览器扩展:不在范围内。如果您浏览器中的扩展拥有修改每个页面的权限,它就能修改我们的页面。我们的保证在页面未被修改加载时有效。
内容安全策略, 按指令说明
以下策略在本页构建时从已部署的 _headers 文件中读取,因此不会与边缘节点实际发送的内容出现偏差。您可以在任意请求的响应头中核实它。
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'每条指令的作用及其妥协之处:
default-src 'self', 硬性底线。任何未明确放宽的内容均保持同源限制。script-src 'self' https://abundera.ai 'wasm-unsafe-eval' https://challenges.cloudflare.com, 禁止内联<script>,禁止eval()。abundera.ai是我们自己的父域名,提供一个共享脚本,即语言选择器。challenges.cloudflare.com是 Turnstile 组件,仅在 /contact/ 页面上使用。'wasm-unsafe-eval'是我们 QR 编码库的 WebAssembly 编译路径所需;它不允许传统的eval()。我们的预部署检查会扫描每个 HTML 页面是否有内联脚本,发现则构建失败。style-src 'self' 'unsafe-inline', 真实的妥协。我们的 QR 预览会内联计算像素级颜色(各模块上的 style 属性)。基于哈希的白名单可行,但在不部署的情况下更新样式就会失败。权衡:我们接受稍弱的样式策略;样式无法窃取数据(CSS 没有connect-src访问能力)。img-src 'self' data: blob: https:,data:用于内联 QR 渲染,blob:用于导出下载 URL,https:用于用户提供的 vCard 照片和头像 URL。用户提供的 URL 只会渲染,不会执行。请注意,这条指令同时也是一个出站通道:页面上运行的任何脚本都可以向任意 HTTPS 主机请求一张图片,并把数据放进 URL 里。我们接受这一点,是因为这个功能需要它,我们在这里说明这一点,而不是把这条策略描述成无懈可击。connect-src 'self' https: https://challenges.cloudflare.com, 这是最值得仔细阅读的一条。https:是一个 scheme source(协议来源):它允许fetch()、XMLHttpRequest、基于 TLS 的 WebSocket、EventSource 和sendBeacon()访问任意 HTTPS 源站,而不仅仅是您输入过的那些。设置它是为了让 vCard 照片字段和 URL 安全检查能够获取您粘贴的地址。它并不能阻止恶意脚本泄露数据;一旦脚本已经以我们的身份运行,没有任何 CSP 指令能做到这一点。真正防止数据泄露的是,我们发布的代码本身不会发出这类请求,这一点您可以在网络选项卡中亲眼观察到。frame-src 'self' https://challenges.cloudflare.com, 只有 Turnstile 组件可以被以 iframe 方式嵌入。frame-ancestors 'none', 其他网站无法嵌入我们。防止点击劫持。base-uri 'self', 恶意注入的<base>标签无法将相对 URL 重定向到攻击者的源站。form-action 'self', 任何注入的表单只能向我们自己的源站提交。唯一会真正提交数据的表单是联系表单。
CSP 做不到什么
CSP 限制的是浏览器会从哪里加载代码,它不会审查加载进来的代码做了什么。如果我们的源站被攻陷(威胁模型中的第一项),攻击者的脚本会拥有和我们的代码一样的权限,包括上面提到的 connect-src 和 img-src 的放宽项。我们针对这种情况的防线其实在 CSP 之外:部署通过一个使用限定权限令牌的 Cloudflare 账户进行,资产文件名带有内容哈希,因此被替换的文件会改变每一处引用它的 URL,Service Worker 也只缓存同源的 GET 响应。宣言描述的是本站实际发布的状态,而不是在声称任何网页都不可能把数据发送到别处;我们更希望您以这个更窄、也更可核实的说法来要求我们。
/bio/*(为用户提供的头像放宽 img-src)和 /embed/*(为有意嵌入放宽 frame-ancestors)适用不同的 CSP 策略。两者均记录在 site/_headers 中。
传输与框架头
- HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload, 1年,所有子域,符合 HSTS 预加载列表条件。合规浏览器拒绝降级到 HTTP。 - X-Frame-Options: DENY, 与 CSP
frame-ancestors重复,为兼容旧版浏览器而保留。 - X-Content-Type-Options: nosniff, 防止 MIME 混淆攻击。
- Referrer-Policy: strict-origin-when-cross-origin, 外链点击会泄露源站但不泄露路径。
- Permissions-Policy:
camera=(self), microphone=(), geolocation=(), 摄像头仅允许我们自己的扫描器使用;麦克风和位置即使在嵌入时也被明确拒绝。
Service Worker
我们的 Service Worker(site/sw.js)只缓存同源资产。fetch 处理程序明确拒绝跨源请求和非 GET 方法, 您可以在 /sw.js 中阅读逻辑(源码未经过度压缩,足以读懂)。缓存写入被包裹在 event.waitUntil() 中,因此不会在导航中途被丢弃。
输入净化
每条接受用户输入的渲染路径均将其视为不可信文本:
- QR 载荷预览使用
textContent,而非innerHTML。 - 分享目标(剪贴板、
navigator.share())将用户文本作为字符串传递,而非标记。 - SVG 导出由我们的编码库生成;用户内容被 base64 编码到
<image>的xlink:href中,而非作为 SVG 元素注入。 - 打印预览使用 blob URL,而非
document.write()。 - localStorage 解析被包裹在
try/catch中, 损坏的条目会产生一个全新的空默认值,而非可能展开到实时代码路径的异常。 - 显示为 "打开链接" 按钮的用户提供 URL 被限制为
http(s)://,javascript:和data:协议被拒绝。
跨源图片获取
当用户粘贴一个 https: URL 作为 vCard 照片或 Logo 时,浏览器根据 CORS 和我们 CSP 的 img-src 白名单获取它。图片被渲染到 canvas 中。它永远不会成为实时 DOM,不会作为代码运行,也不会到达我们的源站, 获取操作是浏览器到远程图片,结果在客户端绘制。控制远程图片 URL 的攻击者可以追踪该 URL 被加载(其服务器上的一行日志),但无法从我们的页面窃取任何内容。
子资源完整性(SRI)
我们提供的所有 JavaScript 和 CSS 均为同源。我们不加载第三方脚本或样式表,因此 SRI 哈希不适用。如果我们将来加载第三方资产,我们将在其上添加 SRI integrity 属性,并在本页记录哈希更新流程。
报告漏洞
如果您发现影响 Abundera QR 的安全问题, 无论是在我们的代码、部署还是我们提供的依赖项中, 请私下向 security@abundera.ai 报告。我们的目标是在 72 小时内完成分类处理。您也可以通过 /.well-known/security.txt 文件中的联系方式联系我们。
暂无漏洞赏金
我们目前不提供有偿赏金,但每一份经确认的有效报告都将在更新日志中获得致谢,并收到我们公开的感谢。
验证以上所有内容
本页每项声明均可通过浏览器 DevTools 验证,无需信任我们:
- CSP:DevTools → Network →
/→ Headers。查看Content-Security-Policy响应头。 - HSTS 及安全头:同一位置,
Strict-Transport-Security、X-Frame-Options等均可见。 - 无出站请求:DevTools → Network → Fetch/XHR。生成一个 QR 码。观察计数保持为零。
- Service Worker 范围:DevTools → Application → Service Workers。验证脚本来源和缓存资产列表。
- 无 Cookie:DevTools → Application → Cookies。空的。
- 完整演示:宣言的验证部分。
联系方式
安全披露:security@abundera.ai