Summary
E2E 테스트가 Supabase service role key를 직접 쓰지 않도록 seed와 cleanup 경로를 좁힙니다.
현재 CI는 이 키를 PR 코드가 실행되는 환경에 그대로 주입하고 있어, 저장소 쓰기 권한이 개발 DB 전권으로 이어집니다.
#209의 Codex 리뷰에서 확인된 문제이며, 해당 PR에서는 근거를 남기고 수용한 뒤 이 이슈로 분리했습니다.
Why
해결하려는 문제가 무엇인가요?
PR 코드가 service role key와 같은 환경에서 실행됩니다.
.github/workflows/ci-e2e-test.yml의 테스트 단계는 SUPABASE_SERVICE_ROLE_KEY를 환경변수로 주입합니다. pull_request 이벤트는 제안된 병합 커밋의 코드를 실행하므로, 그 단계에서 도는 playwright.config.ts, e2e/**, next start가 띄우는 서버 코드는 모두 PR이 가져온 코드입니다. 리뷰보다 실행이 먼저입니다.
저장소 쓰기 권한이 개발 DB 전권으로 이어집니다.
조직에 브랜치를 만들 수 있는 사람은 키를 외부로 보내는 코드를 PR에 넣을 수 있습니다. fork PR을 배제한 설정은 이 경로를 막지 못합니다. 공격자가 fork가 아니라 같은 저장소의 브랜치를 쓰기 때문입니다. 이 키는 RLS를 우회하므로 유출되면 개발 Supabase 프로젝트 전체에 제한 없이 접근됩니다.
지금 당장의 위험은 낮지만 조건이 바뀌면 달라집니다.
현재 개발 DB에는 실제 고객 데이터가 없고(user 행 4개) 조직에서 이 DB를 쓰는 사람이 사실상 한 명이라 실질 피해가 작습니다. 그래서 #209에서는 수용했습니다. 그러나 참여 인원이 늘거나 개발 DB에 의미 있는 데이터가 쌓이면 같은 구조가 그대로 위험이 됩니다. 조건이 바뀌기 전에 처리해야 합니다.
Goal
완료되면 무엇이 달라지나요?
이번 작업의 목표
- CI 환경에 service role key가 존재하지 않습니다.
- 테스트가 쓰는 자격증명은 E2E 테스트 계정과 그에 딸린 데이터만 만들고 지울 수 있는 범위로 제한됩니다.
- 자격증명이 유출되어도 피해가 그 범위를 넘지 않습니다.
- 로컬 실행과 CI 실행이 같은 경로를 씁니다.
이번 작업에서 해결하지 않는 범위 (Non-goal)
- 개발 DB와 분리된 별도 테스트 전용 Supabase 프로젝트 구성은 포함하지 않습니다. 권한 상승 경로 자체를 없애지 못해 이 이슈의 목표와 다릅니다.
- GitHub Environment 승인 게이트 도입은 하지 않습니다. 매 PR 수동 승인이 생겨 자동 게이트라는 목적과 충돌합니다.
- 테스트 케이스 추가 구현은 포함하지 않습니다.
Approach
어떻게 해결할 계획인가요?
전체 구현 방향
seed와 cleanup을 테스트 프로세스에서 떼어내 서버 쪽 진입점 뒤로 옮깁니다. 테스트는 그 진입점을 호출하고, 호출에 필요한 자격증명만 CI에 둡니다. service role key는 서버 쪽에만 남습니다.
주요 변경 사항 (확인 필요)
- seed와 cleanup을 수행하는 진입점을 만듭니다. Supabase Edge Function과 Next.js API route 중 어느 쪽이 맞는지 판단이 필요합니다.
- 진입점은 E2E 테스트 계정 규칙(
@nbread-e2e.test 이메일 등)에 해당하는 대상만 다루도록 제한합니다.
- 호출자 인증용 시크릿을 새로 만들고 GitHub Secrets에 등록합니다.
e2e/fixtures/seed.ts가 admin 클라이언트 대신 이 진입점을 호출하도록 바꿉니다.
- 워크플로우에서
SUPABASE_SERVICE_ROLE_KEY 주입을 제거합니다.
검토한 대안과 선택 이유
#209에서 검토한 대안은 다음과 같습니다.
- GitHub Environment 필수 승인자: 실행 전에 사람이 승인하므로 노출은 막습니다. 그러나 매 PR마다 수동 승인이 필요해 자동 게이트 도입 이유가 사라집니다.
- 개발 DB와 분리된 테스트 전용 프로젝트: 유출 시 잃을 데이터가 줄지만, 저장소 쓰기 권한이 그 프로젝트 전권으로 이어지는 구조는 그대로입니다.
- 현 상태 유지: 조건이 바뀌면 그대로 위험이 됩니다.
권한 자체를 좁히는 방식만이 구조를 바꿉니다. 설계서 v2 12장의 "테스트 계정 관리 방식 개선"과도 같은 방향입니다.
Tasks
Constraints
반드시 유지해야 하는 동작
- 기존 E2E 테스트 16건은 그대로 통과해야 합니다.
- 테스트 실패 후에도 만든 데이터가 되돌려져야 합니다.
- 로컬 실행 방식이 지나치게 번거로워지면 안 됩니다.
변경하면 안 되는 사항
- service role key는 브라우저로 전달되는 코드에 포함되면 안 됩니다.
- Secret과
.env 파일은 커밋하지 않습니다.
- 운영 Supabase에는 어떤 경우에도 테스트 데이터가 생성되지 않아야 합니다.
기술적 제약사항
- Supabase Admin API(
auth.admin.createUser, deleteUser)는 secret key를 요구합니다. 키를 없애는 것이 아니라 서버 쪽으로 옮기는 것이 목표입니다.
- 진입점이 새로운 공격면이 되지 않도록 호출자 인증과 대상 범위 제한이 함께 필요합니다.
Definition of Done
확인 필요
Summary
E2E 테스트가 Supabase service role key를 직접 쓰지 않도록 seed와 cleanup 경로를 좁힙니다.
현재 CI는 이 키를 PR 코드가 실행되는 환경에 그대로 주입하고 있어, 저장소 쓰기 권한이 개발 DB 전권으로 이어집니다.
#209의 Codex 리뷰에서 확인된 문제이며, 해당 PR에서는 근거를 남기고 수용한 뒤 이 이슈로 분리했습니다.
Why
해결하려는 문제가 무엇인가요?
PR 코드가 service role key와 같은 환경에서 실행됩니다.
.github/workflows/ci-e2e-test.yml의 테스트 단계는SUPABASE_SERVICE_ROLE_KEY를 환경변수로 주입합니다.pull_request이벤트는 제안된 병합 커밋의 코드를 실행하므로, 그 단계에서 도는playwright.config.ts,e2e/**,next start가 띄우는 서버 코드는 모두 PR이 가져온 코드입니다. 리뷰보다 실행이 먼저입니다.저장소 쓰기 권한이 개발 DB 전권으로 이어집니다.
조직에 브랜치를 만들 수 있는 사람은 키를 외부로 보내는 코드를 PR에 넣을 수 있습니다. fork PR을 배제한 설정은 이 경로를 막지 못합니다. 공격자가 fork가 아니라 같은 저장소의 브랜치를 쓰기 때문입니다. 이 키는 RLS를 우회하므로 유출되면 개발 Supabase 프로젝트 전체에 제한 없이 접근됩니다.
지금 당장의 위험은 낮지만 조건이 바뀌면 달라집니다.
현재 개발 DB에는 실제 고객 데이터가 없고(
user행 4개) 조직에서 이 DB를 쓰는 사람이 사실상 한 명이라 실질 피해가 작습니다. 그래서 #209에서는 수용했습니다. 그러나 참여 인원이 늘거나 개발 DB에 의미 있는 데이터가 쌓이면 같은 구조가 그대로 위험이 됩니다. 조건이 바뀌기 전에 처리해야 합니다.Goal
완료되면 무엇이 달라지나요?
이번 작업의 목표
이번 작업에서 해결하지 않는 범위 (Non-goal)
Approach
어떻게 해결할 계획인가요?
전체 구현 방향
seed와 cleanup을 테스트 프로세스에서 떼어내 서버 쪽 진입점 뒤로 옮깁니다. 테스트는 그 진입점을 호출하고, 호출에 필요한 자격증명만 CI에 둡니다. service role key는 서버 쪽에만 남습니다.
주요 변경 사항 (확인 필요)
@nbread-e2e.test이메일 등)에 해당하는 대상만 다루도록 제한합니다.e2e/fixtures/seed.ts가admin클라이언트 대신 이 진입점을 호출하도록 바꿉니다.SUPABASE_SERVICE_ROLE_KEY주입을 제거합니다.검토한 대안과 선택 이유
#209에서 검토한 대안은 다음과 같습니다.권한 자체를 좁히는 방식만이 구조를 바꿉니다. 설계서 v2 12장의 "테스트 계정 관리 방식 개선"과도 같은 방향입니다.
Tasks
e2e/fixtures/seed.ts가 진입점을 호출하도록 변경SUPABASE_SERVICE_ROLE_KEY주입 제거e2e/README.md의 실행 준비 절 갱신Constraints
반드시 유지해야 하는 동작
변경하면 안 되는 사항
.env파일은 커밋하지 않습니다.기술적 제약사항
auth.admin.createUser,deleteUser)는 secret key를 요구합니다. 키를 없애는 것이 아니라 서버 쪽으로 옮기는 것이 목표입니다.Definition of Done
npm run lint통과npm run build통과확인 필요
app/api이관([refactor-63/api] supabase client → app/api 마이그레이션 #170) 흐름과 맞습니다. 어느 쪽이 맞는지 판단이 필요합니다.