설계부터 제로 액세스 암호화
동기화된 프로필 데이터는 서버에 도달하기 전에 기기에서 암호화되므로, 서버는 읽을 수 없는 암호화된 블롭만 저장합니다. 캘린더 앱 연결은 캘린더 받은편지함과 읽기 전용 구독에 한정된, 문서화된 평문 예외입니다.
암호화 작동 방식
Graspee는 클라이언트 측 키 계층 구조를 사용합니다. 비밀번호와 랜덤 솔트를 200,000회 반복하는 PBKDF2로 처리하여 키 암호화 키(KEK)를 유도합니다. KEK는 랜덤 생성된 데이터 암호화 키(DEK)를 암호화합니다. DEK는 AES-GCM-256을 사용하여 모든 데이터를 암호화합니다. 비밀번호는 브라우저를 떠나지 않습니다.
제로 액세스의 의미
서버는 평문 데이터, 비밀번호, 암호화 키를 볼 수 없습니다. 데이터베이스에 완전히 접근하더라도 서버 운영자는 메모, 할 일, 거래 또는 기타 동기화된 콘텐츠를 읽을 수 없습니다. 동기화를 활성화하면 서버는 기기 간에 암호화된 블롭을 중계하지만 해독할 수 없습니다. 캘린더 앱 연결은 문서화된 예외입니다: 읽기 전용 구독 피드는 외부 캘린더 앱이 읽을 수 있어야 하므로 평문 산출물이며, 캘린더 받은편지함으로 보낸 빠른 추가 이벤트는 잠금 해제된 브라우저가 가져올 수 있도록 대기 중인 동안 평문으로 저장됩니다. 가져오기, 무시, 만료 또는 톰스톤 처리 후에는 대기 중이던 평문이 삭제됩니다.
세션 보안
DEK는 sessionStorage에 저장된 임시 세션 래퍼 키로 암호화됩니다. 페이지 새로고침 시에는 유지되지만 브라우저를 닫으면 삭제됩니다. 세션은 SameSite 및 Secure 플래그가 있는 HttpOnly 쿠키를 사용합니다. CSRF 보호는 출처 확인을 통해 적용됩니다. 속도 제한이 인증 엔드포인트를 보호합니다.
동기화 암호화
클라우드 동기화는 클라이언트 측에서 암호화된 Yjs CRDT 문서를 사용합니다. WebSocket 전송은 암호화된 페이로드만 전달합니다. 상태 벡터와 업데이트는 서버 측에서 크기 검증을 하지만 해독하지 않습니다. 압축은 데이터 손실을 방지하기 위해 낙관적 잠금을 사용합니다.
은행 연결 보안
Plaid 액세스 토큰, Flinks 로그인 ID 또는 MX 사용자/멤버 GUID와 같은 공급자 자격 증명은 암호화된 로컬 샤드에 저장되며, 서버 데이터베이스에는 절대 저장되지 않습니다. 서버는 패스스루 역할을 합니다: 공급자 API 호출을 중계하고 동기화 페이로드를 메모리에 잠시 보관할 수 있지만, 영구적인 공급자 데이터를 저장하지 않습니다. HMAC 소유 증명은 상태 없이 토큰을 계정에 바인딩합니다. 별도의 연결 링크 원장은 사용자별 링크 한도를 적용하기 위해 공급자 연결 ID의 비가역 HMAC 지문을 보관합니다. Flinks와 MX는 운영자가 활성화한 곳에서만 사용할 수 있습니다.
캘린더 앱 연결 예외
캘린더 앱 연결은 제로 액세스에 대한 의도적이고 제한된 평문 예외입니다. 외부 캘린더 앱이 .ics를 읽을 수 있어야 하므로 읽기 전용 구독 산출물은 평문이며, 캘린더 앱에서 빠른 추가한 이벤트는 잠금 해제된 브라우저가 가져올 수 있도록 대기 중인 동안 캘린더 받은편지함에 평문으로 저장됩니다. 서버는 대기 중인 받은편지함 평문과 게시된 읽기 전용 구독 산출물을 보관하며, 자격 증명과 베어러 토큰에 대해서는 해시만 보관하고, 활성 구독 토큰은 잠금 해제된 브라우저의 암호화된 로컬 저장소에 보관됩니다. 대기 중인 이벤트는 7일 후 만료되며, 브라우저 가져오기·무시·만료·톰스톤 처리 후 대기 중이던 평문이 삭제됩니다. 구독 URL이 유출되면 설정에서 회전하여 기존 구독자를 모두 무효화하세요.
구독 및 결제
구독 결제는 호스팅된 결제 페이지를 통해 Stripe가 처리합니다. 카드 번호, 유효기간, CVC, 청구지 주소, Stripe Link 자격 증명은 Graspee 프런트엔드나 백엔드에 절대 들어오지 않으며, Stripe의 PCI-DSS Level 1 환경 내에만 존재합니다. Graspee는 계정과 구독을 연결하는 데 필요한 Stripe 발급 고객 ID와 구독 ID, 그리고 중단된 결제를 안전하게 재시도하는 데 사용되는 결제 상관관계 행을 저장합니다. 처음 구독할 때 사용자의 계정 이메일은 Stripe에 customer_email로 전송되며, 재시도된 결제가 동일한 멱등성 키로 재실행될 수 있도록 동일 상관관계 행의 요청 스냅샷에도 함께 저장됩니다. Stripe가 고객 식별자를 발급한 이후의 시도에서는 해당 식별자만 전송됩니다. 이메일이 포함된 요청 스냅샷은 시도가 진행 중이든 완료/실패했든 지연 결제 정산을 대기 중이든 관계없이 시도 생성 후 24시간이 지나면 항상 삭제됩니다. 상관관계 행 자체는 그 후 90일이 지나면 삭제됩니다. Stripe가 아직 정산하지 않은 지연 결제 시도는 향후의 성공/실패 이벤트로 조정될 수 있도록 이메일 없이 해당 기간까지 보관된 뒤, 완료/실패한 시도와 동일한 보존 정책에 따라 삭제됩니다. 모든 결제 상태 변경은 서명된 Stripe 웹훅을 통해 이루어집니다. 웹훅 서명은 계정 상태를 변경하기 전에 원시 요청 본문과 HMAC-SHA256으로 검증되며, 타임스탬프 허용 오차는 5분입니다. 유료 액세스는 결제 URL, 쿼리 문자열 또는 클라이언트 응답에서 부여되지 않으며, 오직 조정된 구독에서만 비롯됩니다. 원시 웹훅 페이로드는 서버에 저장되지 않으며, 감사 로그에는 이벤트 ID, 유형, 관련 Stripe 객체 ID의 해시만 보관됩니다.