보안팀과 앱 심사 담당자를 위해

Peel의 보안

요약하면, 보드 내용은 링크 프래그먼트에 있는 키로 브라우저에서 암호화되기 때문에 저희 서버는 읽을 수 없는 암호문만 저장하고 전달해요. 정확히 어떻게 하는지, 한계는 어디까지인지 아래에 설명해요.

요약

  • 카드 내용, 이름, 보드 제목, 투표, 스티커는 브라우저에서 생성한 보드 키로 AES-256-GCM 암호화돼요. 키는 URL 프래그먼트(#k=)에 담겨 다니며 서버로 전송되지 않아요.
  • 진행자 작업, 보드 헤더, 스냅숏은 진행자의 Ed25519 키로 서명돼요. 보드 ID는 이 키에서 파생되므로 클라이언트가 키 바꿔치기를 감지할 수 있어요.
  • 숨긴 카드는 공개 전까지 진행자 키로 봉인(X25519 sealed box)되고, 솔트를 넣은 커밋먼트로 묶여 있어 공개된 내용을 바꿔치기할 수 없어요.
  • 기본은 익명: 작업에는 작성자 ID가 없고, 투표와 반응에는 보드별 무작위 ID를 써요.
  • 참여자는 인증을 하지 않아요. 분석 SDK도, 광고도, 서드파티 스크립트도 없어요. 결제 페이지의 Paddle.js만 예외예요.
  • Cloudflare만 써요. 계정에 연결되지 않은 보드는 마지막 방문 후 60일이 지나면 삭제돼요.

데이터 흐름

URL의 프래그먼트(# 뒤의 모든 부분)는 브라우저가 처리하며 HTTP 요청에 포함되지 않아요. Peel은 보드 키를 바로 여기에 넣어요.

데이터 흐름: 키는 링크에서 기기 안에서만 읽히고, 네트워크를 오가는 건 암호문과 최소한의 메타데이터뿐이에요.

위협 모델

서버가 완전히 침해되거나 호기심을 갖더라도(저희 자신을 포함해서) 팀이 나눈 이야기를 최대한 알 수 없도록 설계했어요.

보호하는 대상

  • 카드 내용, 선택한 GIF, 실명 모드에서는 작성자 이름.
  • 참여자 이름과 아바타, 보드 이름, 사용자 지정 템플릿 제목.
  • 누가 무엇에 투표하거나 반응했는지, 누가 어떤 카드를 썼는지.
  • 회고의 무결성: 단계, 공개, 진행자의 결정은 위조할 수 없어요.

고려하는 공격자

  • 저희 서버, 악의적인 내부자, 저장소에 침입한 사람: 이들이 얻는 건 암호문과 메타데이터뿐이에요.
  • 네트워크 공격자: 모든 구간에 TLS를 쓰고, 키는 네트워크를 지나지 않아요.
  • 키를 바꿔치기하는 서버: 보드 ID는 진행자의 공개 키에서 파생되고, 클라이언트는 서명된 헤더를 검증해요.
  • 훔쳐보거나 위조하려는 참여자: 숨긴 카드는 진행자 키로 봉인되고, 진행자 작업에는 진행자 서명이, 카드 수정에는 그 카드의 키가 필요해요.
  • 오프라인 작성자 대신 다시 게시하는 진행자: 커밋먼트 때문에 내용을 바꿀 수 없어요.

솔직히 말해 범위 밖인 것

  • 참여자 링크를 가진 사람은 누구나 보드를 읽을 수 있어요. 링크가 곧 접근 권한이에요. 보드를 다루듯 신중하게 공유하세요.
  • 침해된 기기, 악성 브라우저 확장 프로그램, 어깨 너머로 화면을 보는 사람.
  • 트래픽 분석: 서버는 타이밍, 크기, 개수를 볼 수 있어요(데이터 맵 참고).
  • 서버에 의한 서비스 거부: 메시지를 버리거나 늦출 수는 있지만 읽거나 위조할 수는 없어요.

암호화 방식

모든 프리미티브는 브라우저의 WebCrypto API에서 가져와요. 자체 제작 암호도, 서드파티 암호 라이브러리도 쓰지 않아요.

키알고리즘용도
K_boardAES-256-GCM, 96비트 무작위 IV모든 작업, 스냅숏, 접속 상태 업데이트를 암호화해요. 추가 데이터(AAD)에 보드 ID, 작업 유형, 작업 ID를 묶어 두어 암호문을 다른 보드나 슬롯에 재사용할 수 없어요. 링크 프래그먼트 #k=에만 존재해요.
K_ownerEd25519진행자의 권한이에요. 진행자 작업(단계, 타이머, 스포트라이트, 템플릿, 설정, 투표 공개, 카드 숨기기), 보드 헤더, 스냅숏에 서명해요. 엄격하게 증가하는 카운터로 재전송 공격을 막아요. 진행자 링크(&o=)에만 들어 있어요.
보드 IDSHA-256, 앞 80비트, base32진행자의 공개 키에서 파생되므로 ID 자체가 어떤 키가 보드의 주인인지 증명해요. 키의 해시가 ID와 맞지 않는 헤더는 클라이언트가 거부해요.
K_facX25519 sealed box: 임시 X25519 + HKDF-SHA256 + AES-256-GCM카드를 쓰는 동안 숨긴 카드는 진행자의 공개 키로 봉인돼요. 진행자 링크(&f=)로만 열 수 있어요.
카드 키Ed25519, 카드마다 키 하나작성자만 가지고 있어요. 수정과 공개에 이 키로 서명해요. 신원은 드러내지 않고 진위만 증명해요.
커밋먼트128비트 무작위 솔트를 쓴 HMAC-SHA256숨긴 카드는 내용에 대한 커밋먼트를 게시해요. 공개할 때 내용과 솔트가 게시되고 모든 클라이언트가 이를 확인하므로, 아무도 내용을 바꿔치기할 수 없어요.
접속 상태K_board 기반 AES-256-GCM이름, 아바타, 입력 중 표시도 다른 모든 것처럼 암호화돼요.
키 번들패스키 PRF 또는 복구 코드에서 HKDF-SHA256으로 파생, AES-256-GCM로그인한 진행자는 보드 키를 여러 기기에 동기화할 수 있어요. 번들은 기기에서 암호화되고, 서버에는 암호문과 래핑된 키만 저장돼요.

서명 메시지는 도메인이 분리되어 있어(예: peel/sig/v1, peel/header/v1, peel/snap/v1) 한 용도의 서명을 다른 용도에 재사용할 수 없어요.

숨긴 카드와 공개

  1. 카드를 쓰는 동안 작성자의 브라우저는 카드를 진행자의 X25519 키로 봉인하고, 솔트를 넣은 커밋먼트를 게시해요. 평문은 작성자의 기기에만 남아요.
  2. 다른 참여자에게는 숨겨진 카드로 보여요. 참여자도, 서버도 복호화할 수 없어요.
  3. 진행자의 브라우저는 봉인된 카드를 열 수 있어요(“👁 나만 보여요” 미리보기).
  4. 공개할 때 각 작성자가 카드를 K_board로 암호화하고 카드 키로 서명해서 솔트와 함께 다시 게시해요.
  5. 작성자가 오프라인이면 진행자가 대신 다시 게시해요. 모든 클라이언트가 커밋먼트를 확인하므로 내용은 작성자가 쓴 것과 같아야 해요.

진행자가 기술적으로 미리 볼 수 있기 때문에, 카드가 숨겨져 있는 동안 모든 보드에는 모두에게 “진행자가 미리 볼 수 있어요” 라벨이 표시돼요. 숨길 수 있는 설정이 아니에요.

익명성

  • 작업에는 작성자 ID가 없어요. 서버는 카드와 사람을 연결할 수 없고, 기본 설정에서는 진행자도 마찬가지예요.
  • 투표와 반응은 이름과 연결되지 않은 보드별 무작위 ID로 기록돼요.
  • 입력 중 표시는 누군가 쓰고 있다는 것만 보여 주고, 무엇을 쓰는지는 절대 보여 주지 않아요.
  • 실명 모드는 보드에서 명시적으로 켜는 설정이에요. 이때 작성자 이름은 암호화된 카드 안에 담겨 전달돼요.
  • 참여자는 로그인하지 않으므로, 연결 지을 계정 자체가 없어요.

데이터 맵

저희가 가진 모든 데이터와 그 위치, 읽을 수 있는 사람, 보관 기간이에요.

데이터위치읽을 수 있는 주체보관 기간
카드, 투표, 반응, 스티커, 액션 아이템, 보드 이름, 사용자 지정 템플릿 제목Durable Object 저장소, 암호문 형태보드 링크를 가진 사람보드가 삭제될 때까지
작업 봉투: 유형, 서버 순번과 시각, 크기, 진행자 카운터Durable Object 저장소Peel보드가 삭제될 때까지
참여자 이름, 아바타, 입력 중 표시암호문으로 전달, 연결된 동안 메모리에 보관보드 링크를 가진 사람연결된 동안
보드 상태: 단계, 참여자 수와 카드 수, 카탈로그 템플릿 ID, 잠금 여부, 생성 및 만료 시각Durable Object, D1, 설치된 경우 Slack/Teams 메시지Peel, 우리 팀의 Slack/Teams 채널보드가 존재하는 동안
진행자 계정: 제공자 ID, 이름, 공유한 경우 이메일, 연결된 제공자, 생성일, 세션D1Peel계정을 삭제할 때까지
키 번들과 래핑된 키D1, 암호문 형태나만 (패스키 또는 복구 코드)계정을 삭제할 때까지
요금제, 구독 상태, Paddle 고객 ID와 구독 IDD1, PaddlePeel, Paddle세법이 정한 기간 동안
Slack/Teams 워크스페이스, 채널, 메시지 ID, 봇 토큰(AES-GCM 암호화)D1Peel앱을 제거할 때까지
IP 주소Cloudflare 엣지, 일시적으로(전송, 요청 제한)Cloudflare, Peel의 요청 제한기저희 데이터베이스에 저장하지 않음
일별 카운터(생성된 보드 수, 도달한 단계)D1Peel집계만, 식별자 없음

서버가 할 수 있는 일과 없는 일

할 수 있는 일

  • 작업 유형, 크기, 타이밍, 단계, 참여자 수와 카드 수, 카탈로그 템플릿 ID, 잠금 여부 보기.
  • 작업 전달을 버리거나, 늦추거나, 거부하기(서비스 거부).
  • 네트워크 엣지에서 IP 주소 보기.
  • 보드 수와 도달한 단계를 집계로 세기.
  • 보드 삭제하기(만료, 신고 처리).

할 수 없는 일

  • 카드 내용, 이름, 보드 제목, 사용자 지정 템플릿 제목, 투표, 스티커 읽기.
  • 공개 전 숨긴 카드 읽기(공개 후에도 마찬가지).
  • 진행자 작업 위조하기: 진행자의 서명이 필요해요.
  • 자기 키로 바꿔치기하기: 보드 ID 때문에 바로 들통나요.
  • 공개된 카드 내용 바꾸기: 커밋먼트와 카드 서명 때문에 들통나요.
  • 누가 어떤 카드를 썼는지 알아내기.

다른 웹 앱과 마찬가지로, 저희가 제공하는 코드는 믿고 쓰셔야 해요. 그래서 그 범위를 작게 유지해요. 엄격한 콘텐츠 보안 정책(CSP), 자체 출처로만 제한한 script-src, 서드파티 스크립트 없음. 프로토콜은 요청하시면 검토용으로 공개해요.

Slack과 Microsoft Teams

  • 메시지에는 보드 링크와 메타데이터(제목, 단계, 개수)만 담겨요. 카드 내용은 진행자가 요약을 올리고 확인했을 때만 전송돼요.
  • 참여 카드에는 키가 포함된 참여자 링크가 들어 있어요. 그 채널을 볼 수 있는 사람은 누구나 참여할 수 있어요. 채널에 링크를 붙여 넣는 것과 같아요.
  • 링크 미리보기에는 단계와 개수만 보이고, 내용은 절대 보이지 않아요.
  • Slack 권한 범위: commands, chat:write, links:read, links:write. users:read는 팀이 멘션을 켤 때만 요청해요.
  • 봇 토큰은 저장 시 암호화돼요(AES-GCM, 키는 Worker 시크릿으로 보관). Slack 요청 서명과 Bot Framework 토큰은 요청마다 검증해요.
  • 앱을 제거하면 모든 게시가 멈추고 토큰이 삭제돼요.

웹과 인프라

  • Cloudflare만 써요: 1801 Labs 계정의 Pages, Workers, Durable Objects, D1, R2, KV. 다른 서버는 없어요.
  • 모든 구간 TLS, HSTS, 엄격한 콘텐츠 보안 정책.
  • 분석 SDK도, 광고도, 서드파티 스크립트도 없어요. 유일한 예외는 결제 페이지에서만 불러오는 Paddle.js예요.
  • 쿠키는 하나, 로그인한 진행자에게만: peel_session, httpOnly, Secure, SameSite=Lax.
  • GIF 검색과 미디어는 저희 API를 거치므로 참여자의 IP 주소가 GIPHY에 닿지 않아요.
  • 폰트와 에셋은 직접 호스팅해요. CDN 폰트도, 추적 픽셀도 없어요.
  • IP별·보드별 요청 제한, 신고 기능, 진행자 잠금.

계정과 보관

  • 참여자는 인증을 하지 않아요. 진행자는 보드를 보관하거나 유료 기능을 쓸 때만 Google(Teams에서는 Entra ID)로 로그인해요.
  • 계정에는 ID, 제공자에게서 받은 이름과 이메일, 그리고 패스키(WebAuthn PRF)나 복구 코드로 여는 암호화된 키 번들이 저장돼요. 로그인은 소유권을 줄 뿐, 내용에 접근하게 해 주지는 않아요.
  • 보드를 계정에 연결할 때는 진행자 서명으로 소유권을 증명해요. 보드 키는 절대 전송되지 않아요.
  • 계정 없는 보드는 마지막 방문 후 60일이 지나면 삭제되고, 방문할 때마다 기간이 다시 시작돼요. 저장소는 Durable Object 알람이 삭제해요.
  • 계정을 삭제하면 세션, 키 번들, 라이브러리가 삭제되고, 보드는 다시 60일 만료 대상이 돼요.

책임 있는 공개

문제를 발견했나요? 먼저 알려 주시면 함께 해결할게요. 신고는 영업일 기준 3일 안에 확인해요.

[email protected]

  • 공개하기 전에 고칠 수 있도록 충분한 시간(목표는 90일)을 주세요.
  • 본인의 보드와 계정으로만 테스트해 주세요. 다른 사람의 데이터에 접근하거나 서비스 품질을 떨어뜨리면 안 돼요.
  • 이 규칙을 지킨 선의의 연구에는 법적 조치를 취하지 않아요.
  • 기계가 읽을 수 있는 연락처: /.well-known/security.txt.

Company 요금제 고객에게는 보안 검토 자료(설문 답변, 하위 처리자, 데이터 흐름)를 제공해요. DPA · 개인정보 처리방침