AI 감사 결과 내 SaaS에서 발견된 두 개의 보이지 않는 장애와 보안 취약점
요약
AI 에이전트 스웜을 활용하여 SaaS 제품의 코드베이스와 프로덕션 환경을 전수 조사한 사후 분석 사례입니다. 모니터링 대시보드가 정상임에도 불구하고 발견된 OAuth 레이스 컨디션과 보안 취약점 등 실제 장애 사례를 다룹니다.
핵심 포인트
- AI 에이전트 스웜을 통한 다각도(보안, 정확성 등) 시스템 감사 수행
- 기존 모니터링 지표가 포착하지 못하는 실제 장애 및 보안 취약점 발견
- 에이전트 결과물에 대한 인간의 수동 검증 과정의 중요성 강조
- OAuth 콜백 과정에서 발생한 3.26초 단위의 레이스 컨디션 사례
대시보드는 초록색이었지만 제품은 다운되어 있었습니다.
완전히 다운된 것은 아니었습니다. 더 나쁜 상황이었죠. 제가 확인하지 않는 두 곳에서 장애가 발생하고 있었고, 제가 설정한 모든 체크 항목은 대시보드에 초록색(정상)을 표시하고 있었습니다. 이것은 제가 자신의 SaaS에 에이전트 스웜 (agent swarm)을 투입하여 38개의 결과물을 얻어낸 날에 대한 사후 분석 (postmortem)입니다. 그중 32개는 인간의 검증을 통과했으며, 여기에는 아무도 눈치채지 못한 두 번의 실제 장애와 프로덕션 (production)의 모든 예약된 작업 (scheduled job)에 존재하는 보안 취약점 (security hole)이 포함되어 있었습니다.
이해를 돕기 위한 배경 설명을 드리겠습니다. 저는 AI 에이전트 (AI-agent) 인력이 대부분의 노동을 수행하는 소규모 소셜 포스팅 SaaS를 혼자 운영하고 있습니다. 엔진은 고객의 목소리를 한 번 학습하고, 밤새 해당 산업을 모니터링하며, 플랫폼에 최적화된 게시물을 초안으로 작성합니다. 그러면 사람이 탭 한 번으로 승인하고, 현재는 Facebook과 Instagram에 게시되며, 더 많은 플랫폼이 출시될 예정입니다. 고객 1번은 엔진이 매일 밤 피드(feed)를 작성해 주는 고급 오디오 판매업체입니다. 실제 돈이 오가고, 실제 게시물이 올라가며, 실제 퍼머링크 (permalink)가 생성됩니다.
이것이 바로 제가 감사를 실시한 정확한 이유입니다.
멀쩡해 보이는 제품을 왜 감사하는가
에이전트 (agents)를 사용하여 빠른 속도로 제품을 구축하다 보면, 실제로 출시한 것과 출시했다고 믿는 것 사이의 간극에서 드리프트 (drift, 편차)가 쌓이게 됩니다. 저의 모니터링은 제가 측정하겠다고 생각한 것들을 측정했을 뿐이며, 이는 실제로 고장 나는 것들과는 매우 다른 집합입니다. 그래서 저는 전수 조사를 실시했습니다. 코드베이스 (codebase)와 프로덕션 (production) 상태에 대해 에이전트 스웜 (agent swarm)을 가동했으며, 8개의 검토 렌즈 (review lenses)를 사용했습니다. 각 렌즈는 별도의 브리프 (brief)를 가진 개별 에이전트이며, 다른 에이전트의 결론에 대해서는 알지 못합니다. 보안 (Security). 정확성 (Correctness). 테넌시 (Tenancy). 게시 경로 (The publish path). 크론 위생 (Cron hygiene). 기타 등등입니다.
스웜 (swarm)은 38개의 결과물을 제시했습니다. 저는 그 어떤 것을 믿기 전에 프로덕션 (production) 환경에서 모든 항목을 수동으로 직접 검증했습니다. 32개는 확인되었고, 6개는 노이즈로 판명되어 폐기되었습니다. 이 비율을 기억해 두십시오. 이것이 정직한 부분이기 때문입니다. 자신이 제기한 모든 것을 확인해 주는 감사는 아첨 기계일 뿐이며, 검증되지 않은 결과는 버그가 아니라 가설일 뿐입니다.
다음은 중요했던 세 가지 사항입니다.
장애 1: 3.26초의 OAuth 레이스 컨디션 (race condition)
이 발견 사항은 마치 미래에서 온 고객 지원 티켓처럼 보였습니다. 어떤 고객도 새로운 소셜 채널을 연결할 수 없었습니다. 모든 시도는 팝업 차단기와 똑같이 보이는 방식으로 실패했습니다. OAuth 창이 열리고, 고객이 플랫폼의 동의 화면에서 승인하며, 창이 닫히면, 그 후에는 아무 일도 일어나지 않았습니다. 목록에 새로운 채널이 나타나지 않았습니다. 에러도 없었습니다. 다시 시도해도 똑같았습니다.
그 메커니즘은 레이스 컨디션 (race condition) 특유의 추한 방식으로 작동하고 있었습니다. 연결 흐름은 OAuth 콜백 (callback)이 어떤 고객이 이 과정을 시작했는지 알 수 있도록 일회용 클레임 (claim)을 발행합니다. 그런데 백그라운드 작업 (background job)이 고객이 여전히 동의 화면을 읽고 있는 동안, 클레임이 생성된 지 3.26초 만에 이를 소비해 버렸습니다. 콜백이 도착했을 때 클레임은 이미 사라진 상태였고, 바인딩 (bind)은 조용히 실패했습니다. 예외도, 에러 레벨의 로그 라인도, 어디에서도 실패한 작업도 없었습니다. 그저 창이 닫혔고 채널 목록이 변하지 않았을 뿐입니다.
인간이 3.26초 이내에 OAuth 권한 부여를 승인할 수는 없습니다. 이는 실패율이 결정론적으로 100%였다는 것을 의미하며, 모든 개별 구성 요소가 성공을 보고했기 때문에 이 모든 상황이 보이지 않았다는 뜻입니다.
이 문제를 오진했을 때의 비용을 생각해 보십시오. 이는 클라이언트 측 문제로 나타납니다. 팝업 차단기, 제3자 쿠키, 잘못된 브라우저 같은 것들 말입니다. 실제 버그는 정확히 잘못된 시점에 정확히 지시받은 대로 수행 중인 백그라운드 작업에 있는데, 고객에게 크롬을 사용해 보라고 말하며 일주일간의 지원 사이클을 허비할 수도 있습니다.
해결책: 이제 클레임은 콜백 시점에 단 한 번, 원자적 (atomically)으로 소비됩니다. 그리고 과거에는 믿음에 의존했던 곳에 카운터가 생겼습니다. 바로 '시작된 연결 수' 대 '바인딩된 채널 수'입니다. 이 두 숫자가 어긋나면, 고객이 알아차리기 전에 제가 이메일을 받게 됩니다.
장애 2: 조용한 초안 기근 (silent draft famine)
두 번째 장애는 훨씬 더 조용하게 발생했으며, 저는 이것이 가능할 것이라고는 생각하지 못했습니다.
매일 밤 진행되는 연구 단계(research stage)는 엄선된 산업 출판물 세트를 읽어 들여, 작곡가(composer)가 초안을 작성할 수 있는 실제적이고 최신화된 자료를 제공합니다. 스웜(swarm)을 통해 확인한 결과, 해당 데이터 호출(fetches)이 클라우드 이그레스(cloud egress) 단계의 네트워크 수준에서 차단되고 있었습니다. 요란한 에러를 발생시키지도 않고, 그저 차단된 것이었습니다. 결과적으로 연구 단계는 아무것도 반환하지 않았고, 작곡가는 초안을 작성할 만한 가치가 있는 자료를 얻지 못했으며, 유료 계정으로의 초안 전달이 중단되었습니다.
그리고 이 발견이 별도의 섹션을 차지하게 만든 대목은 바로 이것입니다. 스케줄러(scheduler)는 그 시간 내내 계속해서 "리듬 충족(cadence satisfied)"이라고 보고하고 있었습니다. 리듬 체크(cadence check)가 잘못된 것을 측정하고 있었던 것입니다. 그것은 파이프라인(pipeline)이 실행되었는지 여부를 측정했을 뿐, 게시물이 실제로 게시(landed)되었는지 여부를 측정하지 않았습니다. 파이프라인은 아주 훌륭하게 작동했습니다. 예정된 일정에 따라 실행되었고, 모든 단계를 완료했으며, 출력물은 0개를 생성했습니다. 게시된 게시물(landed)은 0개에 머물러 있는 동안 모든 상태 표시(status surface)는 녹색을 유지했습니다.
이것이 제가 정의한 '보이지 않는 장애(invisible outage)'의 정의입니다. 모든 구성 요소는 성공하지만, 시스템은 아무것도 생산하지 않는 상태 말입니다.
해결책은 두 부분으로 나뉘었습니다. 기계적인 부분은 피드 호출(feed fetches)이 우리 인프라에서 실제로 해결(resolve)될 수 있도록 경로를 재설정하는 것이었습니다. 중요한 부분은 체크(check)가 믿고 있는 대상을 바꾸는 것이었습니다. 파이프라인이 작동하고 있음을 의미하는 유일한 숫자는 '게시됨(landed)'입니다. 즉, 사람이 승인하고 플랫폼이 라이브 상태임을 확인한 게시물입니다. 초안 작성(drafted), 예약(scheduled), 또는 API에 의해 수락됨(accepted-by-the-API)이 아닙니다. '게시됨(landed)'이어야 합니다. 이제 리듬 체크(cadence check)는 유료 계정의 게시물(landed) 수가 0일 때 경보를 울립니다. 비어 있는 피드 위에서 녹색 대시보드를 보여주는 것은 모니터링 시스템이 아니라, 자장가에 불과하기 때문입니다.
보안 취약점: 19개의 cron 경로와 위조 가능한 헤더 하나
스웜(swarm)의 보안 렌즈(security lens)는 제가 커피를 내려놓게 만든 바로 그 지점을 찾아냈습니다.
이 제품은 예약된 작업(scheduled jobs)을 기반으로 실행됩니다. 연구, 작성, 게시, 조정, 참여 등 총 19개의 cron 경로(cron routes)가 있습니다. 이 모든 경로는 호스팅 플랫폼의 cron 러너(cron runner)가 보내는
6개의 결과는 검증 과정에서 사라졌으며, 그 형태는 시사하는 바가 큽니다. 의도적이었던 중복처럼 보이는 코드, 수백 개의 행만 있는 테이블에서 수년간 스캔(scan) 방식이 괜찮은 '인덱스 누락 (missing index)', 그리고 실제 운영 환경의 현실과 부딪히자마자 해소되어 버린 패턴 매칭 버그 형태들이 그러했습니다.
그래서 제가 현재 적용하고 있는 운영 규칙은 다음과 같습니다: 스웜(swarm)의 출력물을 의견을 가진 정적 분석기 (static analyzer)처럼 취급하는 것입니다. 스웜은 지치지 않고, 모든 것을 읽으며, 동시에 8가지 관점을 유지하지만, 귀하의 시스템 이력은 알지 못합니다. 인간은 확인 게이트 (confirmation gate) 역할을 하며, 38개 중 32개가 확인되었다는 비율은 제가 신뢰할 수 있는 수치입니다. 만약 38개 중 38개가 모두 확인되었다면, 저는 스웜이 제가 듣고 싶어 하는 말만 해주는 것이 아닌지 의심했을 것입니다.
제가 대가를 치르고 배운 세 가지 교훈
- 부재(absence)에 경보를 울리십시오. 두 번의 장애 모두 부재로 인한 실패였습니다: 채널 바인딩 (channel bind)의 부재, 포스트 (post)가 안착하지 않은 부재. 에러가 발생할 때만 작동하는 모니터링은 모든 것이 성공하지만 아무 일도 일어나지 않는 실패 모드를 놓치게 됩니다. 제 경험상 그러한 모드는 요란한 충돌보다 훨씬 더 흔하며 훨씬 더 비용이 많이 듭니다. 존재해야 할 항목의 개수를 세고, 그 개수가 0일 때 경보를 울리십시오.
- 단언(assert)하기 전에 검증(verify)하십시오. 발행 API (publish API)로부터 받은 201 응답은 요청에 대한 영수증일 뿐, 포스트가 완료되었다는 증거는 아닙니다. 이제 엔진은 자체 데이터베이스를 믿기 전에 플랫폼의 실제 상태와 대조하여 조정(reconcile)합니다. 동일한 원칙이 한 단계 위에도 적용됩니다: 초록색 상태 페이지 (status page)는 귀하의 체크 항목들이 내리는 단언이며, 단언에는 검증이 필요합니다.
- '안착된 수(landed)가 0보다 크다'는 것만이 유일한 지표입니다. 모든 중간 단계의 수치는 결국 거짓을 말합니다. 생성된 초안, 완료된 작업, 수락된 요청: 이 모든 것들은 전체 출력 실패 상황 속에서도 건강한 상태를 유지했습니다. 살아남는 지표는 파이프의 맨 끝, 즉 귀하의 버그가 도달할 수 없는 타인의 시스템에서 측정되는 지표뿐입니다.
비용, 그리고 판결
전체 점검은 하루 만에 완료되었습니다. 수정 사항은 이후 며칠에 걸쳐 각각 확인된 하나의 결과에 범위가 맞춰진 작은 풀 리퀘스트 (pull requests) 더미로 반영되었습니다.
대안과 비교했을 때 저렴한 비용입니다. OAuth 레이스 컨디션 (race condition) 하나만으로도, 제가 실시간으로 새로운 고객을 온보딩 (onboarding)하려고 시도하는 바로 그 순간마다 모든 고객의 진입이 차단되었을 것이며, 해당 실패는 고객의 브라우저 잘못인 것처럼 위장했을 것입니다. 저는 에이전트 (agents)를 사용하여 이 제품을 구축했기에, 제가 놓친 것을 에이전트가 잡아낸다는 점에는 일종의 대칭성이 존재합니다. 하지만 대칭성이 핵심은 아닙니다. 핵심은 이제 대규모의 새로운 시각을 확보하는 비용이 저렴해졌으며, 자신의 운영 시스템 (production system)에 그 시각을 투입하지 않는 유일한 이유는 차라리 알고 싶지 않기 때문이라는 점입니다. 대시보드는 여전히 초록색입니다. 차이점은 이제 제가 그것을 믿을 수 있는 근거가 생겼다는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기