보안
우리의 보안 모델을 한 문장으로: 서버에는 공격할 것이 아무것도 없습니다. 아래의 모든 내용은 DevTools에서 직접 확인할 수 있습니다.
아키텍처
생성기는 Cloudflare Pages에서 제공되는 정적 단일 페이지 애플리케이션입니다: 사용자 계정 없음, 인증 없음, 생성 흐름 어디에도 백엔드 코드 경로 없음. QR 생성, 인코딩, 스캔, 렌더링의 모든 작업은 브라우저 내에서 완전히 실행됩니다. 그 옆에는 작은 서버리스 엔드포인트 하나가 있으며, 숨기지 않고 여기에 명시합니다: /api/contact(문의 양식으로, 제출한 내용을 Cloudflare D1에 저장하고 저희에게 이메일로 보냅니다). QR 코드를 만드는 동안에는 호출되지 않습니다. URL 안전성 확인 도구는 check.qr.abundera.ai의 별도 제품입니다. QR 코드 안전 여부 확인
위협 모델
생성기는 사용자 데이터를 수집, 저장, 전송하지 않으며(위의 두 엔드포인트는 귀하가 명시적으로 보낸 것만 처리합니다), 가장 일반적인 웹 앱 위협인 자격 증명 도용, 데이터베이스 침해, 세션 하이재킹, 서버 측 주입 등은 여기에 해당되지 않습니다. 나머지 공격 표면은 출처에서 제공되는 정적 자산 번들(HTML, CSS, JavaScript)입니다. 우리는 다음을 가정합니다:
- 출처 침해: 공격자가 번들된 자산 중 하나를 악성으로 교체하는 경우. 아래의 CSP가 피해 범위를 제한하며, 캐시 버스터 토큰으로 알려진 정상 버전으로 롤백하기 쉽습니다.
- 사용자 제공 입력: QR 페이로드, vCard 이름, WiFi 비밀번호, 일괄 CSV 행, 스캔된 이미지 내용. 모든 입력 경로는 DOM에 다시 렌더링될 때 신뢰할 수 없는 것으로 처리됩니다.
- 사용자 제공 이미지: vCard 사진 URL 및 로고 업로드. 이미지는 캔버스에 렌더링되며, 원시 마크업으로 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은 /contact/에서만 쓰이는 Turnstile 위젯입니다.'wasm-unsafe-eval'은 QR 인코더 라이브러리의 WebAssembly 컴파일 경로에 필요하며, 전통적인eval()은 허용하지 않습니다. 사전 배포 검사에서 모든 HTML 페이지의 인라인 스크립트를 스캔하여 빌드를 실패시킵니다.style-src 'self' 'unsafe-inline'— 실제 양보입니다. QR 미리보기가 개별 모듈의 스타일 속성에 픽셀 수준 색상을 인라인으로 계산합니다. 해시 기반 허용 목록도 가능하지만 배포 없이 스타일을 업데이트하면 실패합니다. 타협: 약간 약한 스타일 정책을 수용하지만, 스타일은 어차피 데이터를 유출할 수 없습니다(CSS에는connect-src도달 범위가 없음).img-src 'self' data: blob: https:— 인라인 QR 렌더링을 위한data:, 내보내기 다운로드 URL을 위한blob:, 사용자 제공 vCard 사진 및 아바타 URL을 위한https:. 사용자 제공 URL은 실행되지 않고 렌더링만 됩니다. 이 지시문이 아웃바운드 채널이기도 하다는 점에 유의하세요: 페이지에서 실행되는 어떤 스크립트든 임의의 HTTPS 호스트에 이미지를 요청하고 URL에 데이터를 담을 수 있습니다. 이 기능이 필요하기 때문에 이를 받아들이며, 정책이 완벽하다고 설명하는 대신 여기서 그 사실을 명시합니다.connect-src 'self' https: https://challenges.cloudflare.com— 주의 깊게 읽어야 할 지시문입니다.https:는 스킴 소스입니다:fetch(), XMLHttpRequest, TLS를 통한 WebSocket, EventSource,sendBeacon()이 입력한 곳뿐 아니라 모든 HTTPS 오리진에 도달할 수 있게 합니다. 이는 vCard 사진 필드와 URL 안전성 확인이 붙여넣은 주소를 가져올 수 있도록 하기 위한 것입니다. 악성 스크립트에 의한 유출을 막지는 못합니다. 스크립트가 이미 저희 권한으로 실행되고 있다면 어떤 CSP 지시문도 막지 못합니다. 유출을 막는 것은 저희가 배포한 코드가 그런 요청을 하지 않는다는 사실이며, 이는 Network 탭에서 직접 확인할 수 있습니다.frame-src 'self' https://challenges.cloudflare.com— Turnstile 위젯만 프레임될 수 있습니다.frame-ancestors 'none'— 다른 사이트가 우리를 임베드할 수 없습니다. 클릭재킹을 방지합니다.base-uri 'self'— 악성으로 삽입된<base>태그가 상대 URL을 공격자 출처로 리디렉션할 수 없습니다.form-action 'self'— 삽입된 폼은 우리 출처에만 다시 게시할 수 있습니다. 문의 양식이 어딘가로 제출되는 유일한 폼입니다.
CSP가 할 수 없는 일
CSP는 브라우저가 코드를 어디서 불러올지 제한합니다. 불러온 코드가 무엇을 하는지는 검증하지 않습니다. 저희 오리진이 침해된다면(위협 모델의 첫 번째 항목), 공격자의 스크립트는 위에서 설명한 connect-src와 img-src 허용 범위를 포함해 저희와 동일한 권한으로 실행됩니다. 이에 대한 저희의 방어는 CSP보다 앞단에 있습니다: 배포는 범위가 지정된 토큰을 가진 단일 Cloudflare 계정을 통해 나가고, 자산 파일명은 콘텐츠 해시가 적용되어 파일이 바뀌면 이를 참조하는 모든 URL이 바뀌며, 서비스 워커는 동일 출처 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=()— 카메라는 자체 스캐너에만 허용되며, 마이크와 위치는 임베드된 경우에도 명시적으로 거부됩니다.
서비스 워커
서비스 워커(site/sw.js)는 동일 출처 자산만 캐시합니다. fetch 핸들러는 교차 출처 요청과 GET이 아닌 메서드를 명시적으로 거부합니다. /sw.js에서 로직을 확인할 수 있습니다(따라 읽을 수 있을 만큼 압축되지 않은 소스가 제공됩니다). 캐시 쓰기는 event.waitUntil()로 래핑되어 내비게이션 중간에 삭제되지 않습니다.
입력 살균
사용자 입력을 받는 모든 렌더링 경로는 신뢰할 수 없는 텍스트로 처리합니다:
- QR 페이로드 미리보기는
innerHTML이 아닌textContent를 사용합니다. - 공유 대상(클립보드,
navigator.share())은 사용자 텍스트를 마크업이 아닌 문자열로 전달합니다. - SVG 내보내기는 인코더 라이브러리에서 생성되며, 사용자 콘텐츠는 SVG 요소로 삽입되지 않고
<image>xlink:href에 base64로 인코딩됩니다. - 인쇄 미리보기는
document.write()가 아닌 blob URL을 사용합니다. - localStorage 파싱은
try/catch로 래핑되어, 손상된 항목은 라이브 코드 경로로 예외를 풀지 않고 신선한 빈 기본값을 생성합니다. - 사용자 제공 URL이 "링크 열기" 버튼으로 표시될 때
http(s)://로 제한되며,javascript:및data:스키마는 거부됩니다.
교차 출처 이미지 가져오기
사용자가 vCard 사진 또는 로고로 https: URL을 붙여넣으면, 브라우저는 CORS와 CSP의 img-src 허용 목록을 적용하여 가져옵니다. 이미지는 캔버스에 렌더링됩니다. 라이브 DOM이 되지 않고, 코드로 실행되지 않으며, 우리 출처에 도달하지 않습니다. 가져오기는 브라우저 → 원격 이미지이고 결과는 클라이언트 측에서 페인팅됩니다. 원격 이미지 URL을 제어하는 공격자는 URL이 로드되었다는 것(자체 서버의 로그 줄)을 추적할 수 있지만 우리 페이지에서 아무것도 유출할 수 없습니다.
서브리소스 무결성(SRI)
우리가 제공하는 모든 JavaScript와 CSS는 동일 출처입니다. 타사 스크립트나 스타일시트를 로드하지 않으므로 SRI 해시가 적용되지 않습니다. 타사 자산을 로드하게 되면 SRI integrity 속성을 함께 제공하고 이 페이지에 해시 업데이트 프로세스를 문서화하겠습니다.
취약점 보고
코드, 배포, 또는 제공하는 종속성에서 Abundera QR에 영향을 미치는 보안 문제를 발견했다면 security@abundera.ai로 비공개 보고해 주세요. 72시간 이내에 분류하는 것을 목표로 합니다. /.well-known/security.txt 파일의 연락처를 통해서도 연락하실 수 있습니다.
버그 바운티 없음(아직)
현재 유료 바운티를 제공하지 않지만, 확인된 모든 유효한 보고는 변경 로그에 기재되고 공개 감사를 받습니다.
위의 내용 확인
이 페이지의 모든 주장은 우리를 신뢰하지 않고도 브라우저 DevTools에서 직접 확인할 수 있습니다:
- CSP: DevTools → 네트워크 →
/→ 헤더.Content-Security-Policy응답 헤더를 읽으세요. - HSTS + 보안 헤더: 같은 위치 —
Strict-Transport-Security,X-Frame-Options등이 모두 표시됩니다. - 아웃바운드 호출 없음: DevTools → 네트워크 → Fetch/XHR. QR을 생성하세요. 수가 0으로 유지되는 것을 확인하세요.
- 서비스 워커 범위: DevTools → 애플리케이션 → 서비스 워커. 스크립트 소스와 캐시된 자산 목록을 확인하세요.
- 쿠키 없음: DevTools → 애플리케이션 → 쿠키. 비어 있습니다.
- 전체 안내: 매니페스토의 인증 섹션.
연락처
보안 공개: security@abundera.ai