다른 회사 데이터가 보였다 — 14개 테이블에 보안이 없었던 이야기
등골이 서늘해진 순간
Echo Mail을 테스트하고 있었어요. A 계정으로 로그인했는데, 화면에 B 계정의 업체 목록과 알림 이력이 뜨는 거예요.
다시 말하면 — 다른 회사의 데이터가 내 화면에 보인 겁니다.
Echo Mail은 여러 회사가 같이 쓰는 서비스(SaaS)예요. 각 회사의 데이터는 철저하게 분리되어야 하는데, 그 분리가 안 되고 있었습니다. 비유하면 아파트인데 현관문이 전부 열려있는 상태.
원인을 찾아보니 — 14개 테이블에 잠금장치가 없었다
데이터베이스에는 "이 사용자는 이 데이터만 볼 수 있다"는 잠금장치(Row Level Security, 줄여서 RLS)를 걸 수 있어요. 아파트로 치면 각 집의 현관문 잠금장치인데, 14개 방(테이블) 전부에 잠금장치가 없었습니다.
그동안은 앱 코드에서 "A 회사 사용자에게는 A 회사 데이터만 보여줘"라고 필터링하고 있었어요. 근데 이 필터가 실수로 빠지는 순간, 전체 데이터가 그대로 노출되는 구조였습니다.
실제로 필터에 넘기는 회사 ID 값이 비어있을 때(undefined) 필터가 무시되면서 모든 데이터가 나오는 게 원인이었어요.
AI한테 보안 감사를 시키다
"보안 강화해줘"로는 일반적인 체크리스트만 나왔어요. 실제 증상을 알려줘야 했습니다:
당신은 데이터베이스 보안 전문가입니다.
[현재 상태]
- 여러 회사가 같이 쓰는 SaaS
- 앱 코드에서 회사별 데이터 필터링 중
- 데이터베이스에 잠금장치(RLS)가 하나도 없음
- 실제 증상: 필터 값이 비어있으면 다른 회사 데이터가 노출됨
[요청]
1. 보안이 필요한 테이블 14개 전부 식별해주세요
2. 각 테이블에 "내 회사 데이터만 보이는" 잠금장치를 설계해주세요
3. 신규 가입 시 자동으로 회사가 생성되는 장치도 만들어주세요
| 비교 | "보안 강화해줘" | 바꾼 프롬프트 |
|---|---|---|
| 역할 부여 | 없음 | "데이터베이스 보안 전문가" |
| 증상 설명 | 없음 | 다른 회사 데이터 노출 + 원인 |
| 범위 | 불명확 | 14개 테이블 전부 |
| 결과 | 일반 체크리스트 | 432줄 보안 분석 리포트 |
고치면 다음 문제가 터지는 4연쇄
여기서부터 삽질이 시작됐습니다. 한 문제를 고치면 다음 문제가 터지는 연쇄가 4번이나 반복됐어요.
1단계: 잠금장치 설치 → 신규 가입 불가14개 테이블에 잠금장치를 달았더니, 새로운 사용자가 가입하면 아무것도 안 보이는 문제가 생겼어요. 아파트에 현관문 잠금을 설치했는데, 새 입주자가 열쇠를 못 받은 셈.
가입할 때 자동으로 "회사 + 열쇠"를 만들어주는 장치가 필요했습니다:
잠금장치 설치 후 신규 가입자에게 회사가 생성되지 않습니다.
가입 시 자동으로 회사와 소속 정보를 만들어주는
데이터베이스 트리거를 만들어주세요.
2단계: 트리거 추가 → 기존 코드에서 데이터 안 나옴
트리거로 해결했더니, 이번에는 기존 코드에서 데이터가 하나도 안 나왔습니다. 잠금장치가 "누구세요?"라고 물어보는데, 기존 코드는 자기 신분증을 제시하는 방법을 몰랐던 거예요.
문제는 데이터베이스 접속 방식의 차이였습니다. 웹에서 접속하면 자동으로 신분증이 전달되는데, 기존 코드는 직접 연결이라 신분증이 빠져있었어요.
기존 코드에서 데이터가 안 나옵니다.
직접 연결 방식에서는 사용자 신분 정보가 전달되지 않습니다.
데이터베이스 쿼리 전에 "나는 이 사용자입니다"라는 정보를
설정하는 코드를 추가해주세요.
3단계: 신분증 설정 → 백그라운드 작업 멈춤
해결했더니, 이번에는 5분마다 돌아가는 메일 감시가 멈췄습니다. 백그라운드 작업은 특정 사용자가 아니라 "시스템"으로 돌아가는 건데, 잠금장치가 "시스템이라도 신분증 없이는 안 돼"라고 막은 거예요.
백그라운드 작업(메일 감시, 스케줄러)이 멈췄습니다.
시스템 관리자 역할에는 잠금장치를 우회하는
별도 정책을 추가해주세요.
4단계: 최종 해결
시스템 관리자 역할에 대한 우회 정책을 추가해서 드디어 전부 동작.
| 단계 | 뭘 고쳤나 | 뭐가 터졌나 |
|---|---|---|
| 1 | 14개 테이블에 잠금장치 | 신규 가입 불가 |
| 2 | 가입 시 자동 생성 트리거 | 기존 코드에서 데이터 안 나옴 |
| 3 | 쿼리 전 신분 정보 설정 | 백그라운드 작업 멈춤 |
| 4 | 시스템 관리자 우회 정책 | 드디어 해결 |
검증 — "진짜 안 보이나?"
고쳐놓고 "진짜 된 건가?" 확인이 필요했습니다. AI한테 테스트 시나리오를 만들게 했어요:
- A 회사 사용자가 B 회사 데이터를 조회 → 빈 결과 (이전에는 B 데이터가 보였음)
- 회사 ID가 비어있을 때 → 빈 결과 (이전에는 전체 데이터가 나왔음)
- 신규 가입 → 자동으로 회사 생성됨
- 메일 감시 스케줄러 → 정상 동작
배운 것 — 앱 코드만 믿으면 안 된다
이 삽질에서 가장 크게 배운 건:
앱에서 아무리 잘 필터링해도, 데이터베이스 자체에 잠금이 없으면 언젠가 새요. 앱 코드에 버그가 하나만 있어도 전체 데이터가 노출되는 구조는 위험합니다. 데이터베이스 잠금장치(RLS)는 "앱이 실수해도 데이터는 안전한" 마지막 방어선이에요.그리고 보안 작업은 연쇄 반응이 심합니다. 잠금 하나 걸면 다른 데서 터지고, 그거 고치면 또 다른 데서 터지고. "하나만 고치면 끝"이 아니라 "하나 고치면 3개 더 나온다"는 마음으로 시작해야 해요.
AI한테 시킬 때는 "보안 강화해줘"가 아니라, 실제로 어떤 증상이 있는지, 몇 개 테이블이 관련되는지, 어떤 역할(사용자/관리자/시스템)이 접근하는지를 알려줘야 쓸 수 있는 결과가 나옵니다.