보안
로그인, 세션, API 키, 결제, 잡 실행 환경에서 XenoCI가 비밀과 격리를 다루는 방식을 설명합니다
XenoCI는 비밀번호를 쓰지 않고, 키와 토큰은 해시로만 저장하며, 잡마다 새 VM을 씁니다
로그인과 세션
| 항목 | 방식 |
|---|---|
| 로그인 | Google 계정 또는 이메일 링크(6자리 코드 포함), 비밀번호 없음 |
| 이메일 링크 | 15분 동안 한 번만, 토큰은 URL 조각(#token=)에 실려 서버 로그와 Referer에 남지 않음, 코드는 5번 틀리면 무효 |
| 인가 코드 + PKCE(S256), 상태 값은 해시로 저장하고 10분 뒤 만료, 확인된 이메일만 기존 계정에 연결 | |
| 세션 | 14일, 쿠키 xci_console은 HttpOnly; Secure; SameSite=Lax, 서버에는 세션 ID의 SHA-256만 저장 |
| 요청 제한 | 로그인 메일은 이메일당 15분에 5번, IP당 20번 |
| 같은 출처 검사 | 쿠키로 하는 모든 쓰기 요청(POST, PUT, PATCH, DELETE)은 Origin 또는 Referer가 같은 출처여야 합니다, 아니면 403 insufficient_permission |
API 키
- 형식
xci_live_<43자>_<6자>, 뒤 6자는 CRC32 체크섬이라 데이터베이스를 읽기 전에 걸러냅니다 - 서버는 SHA-256 해시만 저장하고 발급 응답에서 한 번만 보여 줍니다
- 키는 워크스페이스 하나에 묶이고
run또는read권한만 가집니다, 결제와 관리 기능은 어떤 키로도 불가(console_only) - 서버 오류 메시지와 로그, CLI와 MCP 출력에서
xci_live_문자열을 가립니다 - 공개 유출 자동 폐기와 사용량 급증 알림은 키 유출 대응에 있습니다
Authorization헤더가 있으면 쿠키가 있어도 키 요청으로 처리하고, 잘못된 키는401입니다
잡 실행 환경
| 항목 | 방식 |
|---|---|
| 격리 | 잡마다 봉인된 베이스 이미지를 복제한 새 VM, 끝나면 파기하고 삭제를 확인 |
| 비밀값 | 잡 env는 VM에 표준 입력으로 전달, 명령줄과 로그에 남지 않음 |
| 소스 | 업로드는 잡 전용 1회용 토큰으로 맥 호스트가 받아 가고 잡이 끝나면 지움, git 소스는 VM 안에서 공개 저장소를 받음 |
| 결과물 | 잡 전용 토큰으로 올리고 15분짜리 서명 링크로 내려받음, 링크에 API 키가 들어가지 않음 |
| 요청 ID | 모든 응답에 X-Request-Id, 오류 봉투에 request_id |
| 캐시 | 모든 응답이 Cache-Control: private, no-store |
결제
- 결제는 PortOne 경유 원화 카드 결제이고 서버는 카드 정보를 받지 않습니다
- 결제 확인 때 서버가 PortOne에서 결제를 다시 읽어 금액, 통화, 상점을 주문과 대조합니다, 다르면
payment_amount_mismatch로 적립하지 않습니다 - 결제 웹훅은 Standard-Webhooks 서명을 검증하고, 같은 결제 ID는 한 주문에만 묶입니다(
payment_reference_reused) - 매일 전날 결제 목록을 PortOne과 대사합니다
감사 기록
조직 만들기, 역할 변경, 멤버 내보내기, 워크스페이스 보관, API 키 발급과 폐기, 유출 자동 폐기가 감사 이벤트로 남습니다, 지금은 콘솔이나 API로 조회하는 화면이 없고 문의하면 지원팀이 확인해 드립니다
책임 공유
- 키와 토큰을 저장소나 공개 채널에 두지 마세요
- 포크 PR 워크플로에 시크릿을 넘기지 마세요
- 서명 인증서와 App Store Connect 키는 API 제출의
env필드로 넘기고 스크립트에서 파일로 풀어 쓰세요, 서버에 보관하는 금고 기능은 아직 없고 CLI와 MCP에는 env 옵션이 없습니다 - 빌드 스크립트가 받는 외부 호스트를 최소로 두세요