00:00:00이번 달 보고서에 따르면 약 2만 개의 인디 앱을 조사한 결과, 9개 중 1개꼴로
00:00:04프런트엔드 코드에 Supabase 인증 정보가 노출되어 있었다고 합니다.
00:00:08서버 로그나 프라이빗 저장소에 있는 게 아닙니다.
00:00:11모든 방문자가 다운로드하는 자바스크립트 안에 그대로 들어있었죠.
00:00:14문제는 이 앱들이 해킹당한 게 아니라, 실수로 비밀 키를 포함해서 배포했다는 점입니다.
00:00:18분명히 말씀드리자면, 어떤 키들은 공개용으로 설계되었습니다.
00:00:21문제는 배포 과정입니다. 똑같은 실수로 브라우저에 절대로 노출되어서는 안 될
00:00:25비밀 키까지 함께 배포될 수 있기 때문이죠.
00:00:27저희는 유용한 영상을 계속해서 업로드하고 있습니다.
00:00:28구독하는 것 잊지 마세요.
00:00:35사람들이 흔히 혼란스러워하는 간단한 진실이 하나 있습니다.
00:00:38우리 모두 알고는 있지만, 바쁘게 작업하다 보면 놓치기 쉬운 부분이죠.
00:00:41환경 변수를 'public'으로 표시하면, 빌드 도구는 이를 브라우저용으로 간주하여
00:00:46클라이언트 번들에 포함시킵니다.
00:00:50Next.js는 NEXT_PUBLIC, Vite는 VITE_, SvelteKit은
00:00:56public 키워드를 사용하죠.
00:00:57'Next'는 안전 라벨이 아니라, 말 그대로 배송 라벨인 셈입니다.
00:01:01자, 이제 Supabase 관련 내용을 살펴봅시다.
00:01:04'anon'이나 'publishable' 키처럼 공개되어도 상관없는 키가 있는 반면,
00:01:10'service_role'이나 비밀 키처럼 절대 공개되면 안 되는 키도 있습니다.
00:01:12만약 비밀 키가 브라우저에 노출된다면, 어떤 일이 벌어질지 짐작이 가시죠?
00:01:17이런 경우에는 행 레벨 보안(RLS)도 소용이 없습니다.
00:01:20Supabase 문서에도 서비스 키는 RLS를 우회할 수 있다고 명시되어 있습니다.
00:01:24따라서 그 키가 프런트엔드에 있다면, 지금까지 설정한 보안 정책은 아무 의미가 없게 됩니다.
00:01:29제 샌드박스를 통해 어떻게 이런 일이 일어나는지 직접 보여드리겠습니다.
00:01:33먼저 Next.js로 간단한 CRUD 앱을 만들고 Supabase를 연결했습니다.
00:01:38여기가 제 .env 파일인데, 여기서 실수가 발생합니다.
00:01:41키를 'PUBLIC' 접두사가 붙은 변수에 넣었습니다.
00:01:45이 키는 공개용이므로 노출되는 것이 당연합니다.
00:01:48진짜 문제는 동일한 'NEXT_PUBLIC' 경로를 통해 실수로 비밀 키까지 배포될 수 있다는 것이죠.
00:01:53빌드를 위해 'npm run build'를 실행한 후 앱을 구동했습니다.
00:01:59크롬 브라우저 화면입니다.
00:02:00저희 CRUD 앱의 데이터베이스에 정보를 빠르게 추가해 보겠습니다.
00:02:05자, 이제 컴파일된 자바스크립트 번들을 열고 키를 검색해 보겠습니다.
00:02:10여기 있네요.
00:02:12URL과 키가 사용자가 브라우저로 내려받은 파일 안에
00:02:18그대로 박혀 있습니다.
00:02:19그리고 여기서 주목해야 할 점이 있습니다.
00:02:21아무도 이 앱을 해킹하지 않았다는 겁니다.
00:02:22제가 그냥 찾아낸 것뿐이죠.
00:02:24어떤 취약점을 공격한 것도 아닙니다.
00:02:25그저 앱이 이미 인터넷에 공개한 내용을 읽었을 뿐입니다.
00:02:29제 눈에 보인다면 누구나 볼 수 있다는 뜻입니다.
00:02:32개발자 도구를 열고 자바스크립트 파일을 확인해서 검색만 하면 됩니다.
00:02:35그게 전부예요.
00:02:36“아무도 안 보겠지”라고 생각하신다면 오산입니다. 인터넷에는 이런 정보를
00:02:41전문적으로 찾아내는 수많은 사람과 봇들이 널려 있습니다.
00:02:44이제 실제로 통하는 해결책을 알려드리겠습니다.
00:02:47브라우저는 오직 여러분의 API만 호출해야 합니다.
00:02:50API는 서버 사이드에서 실행됩니다.
00:02:52비밀 키가 있어야 할 곳은 바로 거기입니다.
00:02:54프라이빗한 작업은 API 경로(route)나 서버 함수로 옮기세요.
00:02:58클라이언트가 엔드포인트를 호출하면, 서버에서 Supabase를 호출하는 구조여야 합니다. 그다음 다시 빌드해서 번들을 확인해 보세요.
00:03:03번들에서 키가 사라졌다면 제대로 해결된 겁니다.
00:03:04하지만 여기서 만족하고 멈추지 마세요. 추가로 할 수 있는 것들이 더 있습니다.
00:03:09사용자가 접근하는 테이블에 행 레벨 보안(RLS)이 활성화되어 있는지 확인하고,
00:03:11설정한 정책이 의도한 대로 작동하는지 반드시 체크하세요.
00:03:16테스트에도 시간을 투자해야 합니다.
00:03:18이 부분은 종종 간과되곤 하죠.
00:03:19이제 문제가 재발하지 않도록 하는 방법입니다. 대부분 한 번은 고치지만,
00:03:21나중에 바쁠 때 다시 실수를 저지르곤 합니다.
00:03:26따라서 안전장치를 마련하세요. CI 단계에서 시크릿 스캐닝을 추가해
00:03:28키가 부적절한 곳에 노출될 경우 빌드가 실패하도록 만드세요.
00:03:34또한 PR 규칙을 정해 'NEXT_PUBLIC'이나 'Vite'가 포함된 코드는
00:03:36기본적으로 공개되는 것으로 간주하고 엄격히 검토해야 합니다.
00:03:41마지막으로, 키를 정기적으로 교체하세요.
00:03:42키가 노출된 것 같다는 의심이 조금이라도 든다면 즉시 새로 발급받으세요.
00:03:43며칠 더 지켜보는 것보다 훨씬 안전한 방법입니다.
00:03:47지금 바로 확인해 볼 수 있는 방법이 있습니다.
00:03:50실제 배포 방식 그대로 앱을 빌드해 보세요.
00:03:52빌드 결과물에서 'supabase', 'JWT', 'service_role' 등 토큰처럼 보이는 모든 것을 검색하세요.
00:03:55만약 비밀 키를 발견했다면 이미 유출된 것으로 간주해야 합니다.
00:04:01키를 교체하고 로직을 서버 사이드로 변경하세요.
00:04:05이 영상에서 딱 한 가지만 기억해야 한다면 이것입니다.
00:04:08“번들에 포함되었다면, 그것은 공개된 것입니다.”
00:04:11그럼 다음 영상에서 뵙겠습니다.
00:04:13We'll see you in another video.