CloudFlare의 AI 코딩 OAuth 라이브러리 살펴보기

ko생성일: 2025. 6. 8.갱신일: 2025. 6. 12.

CloudFlare가 Anthropic의 Claude LLM으로 제작한 새로운 OAuth 프로바이더 라이브러리를 전문가의 시각에서 개괄적으로 분석한 글입니다. 보안, RFC 준수, 그리고 AI 활용의 장단점 등을 상세히 다룹니다.

오늘은 CloudFlare의 새로운 OAuth 제공자 라이브러리를 살펴보기로 했습니다. 이 라이브러리는 대부분 Anthropic의 Claude LLM의 도움으로 작성된 것으로 보입니다:

이 라이브러리(스키마 문서 포함)는 대부분 Claude의 도움으로 작성되었습니다. Claude의 결과물은 Cloudflare 엔지니어들이 철저히 검토했으며, 보안과 표준 준수에 세밀한 주의를 기울였습니다. 많은 개선이 이루어졌으며, 대부분은 Claude에게 재차 프롬프트를 주고 검토하면서 이루어졌습니다. Claude가 어떻게 프롬프트를 받았고 어떤 코드를 생성했는지는 커밋 내역을 확인해보세요.

강조하자면, 이건 ‘분위기로 코딩된(vibe coded)’ 것이 아닙니다. 모든 라인은 해당 RFC 경험이 있는 보안 전문가가 철저히 검토하고 표준 문서와 대조했습니다. 저는 제 회의론을 검증하려 했지만 오히려 제 자신이 틀렸음을 증명하게 됐죠.

저도 최근 LLM의 ‘에이전트식’ 코딩 방식을 많이 해왔고, OAuth 전문서 API Security in Action 저자이며, 몇 년간 IETF의 OAuth 작업 그룹에서 활동했고, 이전엔 주요 OAuth 제공자에서 테크 리드 및 보안 아키텍트로 일했습니다. (AI 박사 학위도 있지만, 그건 지금의 기계학습 열풍 전 이야기입니다) 그래서 이 결과물이 매우 궁금했고, 회의 중 틈틈이 살짝 들여다봤습니다. 참고로, 아직 깊이 리뷰하진 않았고 버그 몇 개만 제보했습니다.

처음에는 코드를 꽤 인상적으로 봤습니다. 코드가 한 파일에 모두 들어가 있는데(LMM 코딩 경험상 흔함), 구조도 나쁘지 않았고, LLM 특유의 쓸데없는 주석도 많지 않았으며, 실제 클래스와 상위 구조도 보였습니다.

테스트도 몇 개 있는데, OAuth 같은 중요한 인증 서비스에는 매우 부족합니다. 명세의 모든 MUST/MUST NOT은 물론, 생각할 수 있는 모든 오용 케이스에 대한 테스트가 최소인데, 그런 건 전혀 없고 기본 기능 테스트만 있습니다. (코드를 대충 훑어보니, 파라미터 검증 등에서 누락된 MUST 체크가 제법 많아 보임)

가장 눈에 띈 것은 제가 ‘YOLO CORS’라 부르는 관행, 즉 모든 Origin에 대해 사실상 동일 출처 정책을 거의 무효화해버리는 CORS 헤더 세팅이었습니다:

private addCorsHeaders(response: Response, request: Request): Response {
  const origin = request.headers.get('Origin');
  if (!origin) {
    return response;
  }
  const newResponse = new Response(response.body, response);
  newResponse.headers.set('Access-Control-Allow-Origin', origin);
  newResponse.headers.set('Access-Control-Allow-Methods', '*');
  // Include Authorization explicitly since it's not included in * for security reasons
  newResponse.headers.set('Access-Control-Allow-Headers', 'Authorization, *');
  newResponse.headers.set('Access-Control-Max-Age', '86400');
  return newResponse;
}

이런 식이 괜찮은 경우도 있지만(왜 이렇게 했는지는 아직 자세히 분석 안 함), 아주 수상해 보입니다. 이런 코딩은 거의 할 필요가 없습니다. 이 경우, 커밋 로그에서 이 방식은 LLM이 아니라 인간 엔지니어가 선택한 것임을 알 수 있습니다. 적어도 credentials를 허용하지 않았기 때문에 일반적 문제에는 해당되지 않을 수 있습니다.

헤더 얘기하면, 응답에 표준 보안 헤더가 뚜렷하게 부족합니다. 많은 헤더가 API에는 해당되지 않지만, 일부는 적용되며, 이게 종종 문제의 원인이 되기도 합니다. 예를 들어, API에 XSS가 생길 수 있음을 책에서 보여주는데, JSON만 올바로 반환한다고 브라우저가 그렇게 해석한다는 보장은 없습니다. Cloudflare Workers가 일부 헤더를 자동 추가하는지 모르겠지만, 적어도 X-Content-Type-Options: nosniff 및 HTTP Strict Transport Security는 토큰 보호를 위해 필요합니다.

이상한 선택들도 있는데, 실무적으로 OAuth 명세에 익숙하지 않은 사람들이 참여한 것 같다는 인상도 듭니다. 예를 들어, 이 커밋은 public client 지원을 추가하면서, 이미 폐기된 "implicit" grant(OAuth 2.1에서 제거됨)를 구현합니다. public client 지원에 굳이 필요 없는 동작인데, PKCE 구현도 있고 CORS도 느슨한데 말입니다. 커밋 메시지에 따르면, public client 지원에 필요한 점을 몰라서 Claude에 물었더니 implicit grant를 제안했다고 합니다. 이 implicit grant는 기능 플래그로 숨겨져 있지만, 토큰 발급 시점이 아닌 단순 요청 파싱용 헬퍼에서만 체크됩니다.

또 다른 힌트는 Basic Auth 구현을 잘못했다는 점입니다. 흔한 오류지만(LMM도 그랬다니), 일반 Basic 인증과 달리 OAuth는 모든 것을 URL-encoding해야 합니다(문자셋 문제 때문). 게다가 secret에 콜론이 들어갈 경우(명세상 허용) 보조 버그도 있습니다. 이 구현에서는 client ID/secret을 항상 직접 생성하므로 당장 큰 문제는 없겠지만, 자세히 보진 않았습니다.

더 심각한 버그로, 토큰 ID 생성 코드가 안전하지 않은데 편향된 랜덤 생성 문제입니다. 이는 랜덤 문자열로 토큰 만들 때 흔한 실수인데, 처음 커밋부터 등장했습니다. 쉽게 뚫린다고 보기엔 충분히 엔트로피가 크지만, “보안 전문가들이 AI 코드 한 줄 한 줄 검토했다”는 주장엔 신뢰가 안 갑니다. 만약 정말 다 검토하고도 이걸 놓쳤다면 너무 LLM을 믿은 셈이죠 (실제론 첫날 한 개발자 단독으로 메인에 21개 커밋이 직행해서 코드 리뷰 자체가 없었던 듯합니다).

토큰 저장소 암호화 구현도 대충 봤는데, 설계가 꽤 인상적입니다. 커밋 메시지를 보면 설계는 사람이 했고, 실제 구현은 Claude가 많이 했다는 걸 알 수 있습니다. 이 커밋 메시지에서 Claude와 엔지니어가 어떻게 상호작용했는지 일부 발췌합니다:

Claude에게 props를 암호화해 저장하라고 함.

프롬프트: GrantToken 레코드의 props를 암호화해서 저장하고 싶다. 복호화에는 유효한 토큰이 필요해야 한다. 토큰은 시점별로 여러 개가 유효할 수 있으니 props를 여러 번 암호화하긴 싫다. 그래서 props는 대칭키로 한 번만 암호화하고, 각 토큰별로 그 키를 래핑해서 저장하고 싶다. 암호화엔 WebCrypto를 써라.

Claude가 잘못된 시도로 시작했기에 추가 디자인 조건을 더함:

프롬프트: 한 가지 더, listUserGrants() 헬퍼는 이제 props를 반환할 수 없다. 복호화할 토큰이 없으니까. 괜찮다. props는 인증된 API 요청에서만 앱에 전달하면 된다. 어차피 listUserGrants()는 grant의 좁은 형태만 돌려주면 충분: id, clientId, userId, scope, metadata, createdAt만 반환하면 된다. token ID나 code challenge 정보는 필요 없다.

Claude가 멋진 코드를 만들어냈지만 하나 큰 결함이 있었음.

프롬프트: 토큰별로 키 래핑할 때 보안상 결함이 있음. 토큰 hash로 키를 만들고 있는데, 그 hash는 토큰 ID 만드는 데도 사용. 따라서, 저장된 토큰 ID만으로 keys가 쉽게 언랩될 수 있다. 이를 방지하려면 wrapping용 키오더리브를 token ID로 변환 못하도록 해야함.

Claude는 PBKDF2로 10만번 반복하는 방향을 제안.

프롬프트: PBKDF2는 비밀번호처럼 입력값이 약할 때 중요하지만, 우리 입력값은 고엔트로피이니 과도하게 무겁다. 대신, static HMAC 키를 가진 SHA-256 HMAC을 쓰자(실질적으론 salt 역할).

Claude는 이후 "OAUTH_PROVIDER_WRAPPING_KEY_HMAC_v1" 문자열을 HMAC 키로 사용한 코드 제안.

프롬프트: 성능 위해 32바이트 hex 배열을 HMAC key로 명확하게 쓰자. 임의로 뽑은 32바이트는 다음과 같다: 22 7e 26 86 8d f1 e1 6d 80 70 ea 17 97 5b 47 a6 82 18 fa 87 28 ae de 85 b5 1d 4a d9 96 ca ca 43

(참고: 여기서 하드코딩한 키여도 실질적으론 HKDF-Extract의 고정 랜덤 salt라 괜찮습니다. 보안적으로, 키 파생 용도가 서로 독립적이길 바라는데, 이 설계는 합리적입니다. 같은 방식으로 token ID 생성에도 다른 salt를 써서 적용할 수도 있겠네요.)

이 전체 상호작용에서 눈에 띄는 점은, LLM을 활용할 때 반드시 사전에 자신의 지식이 부족하면 위험하다는 것. Claude가 중간에 낸 "큰 결함"은 경험없는 개발자는 아예 캐치하지 못했을 수 있습니다. 그리고 PBKDF2 제안도 괴상하지만, 많은 개발자들이 그럴싸하게 받아들였을지도 모릅니다. LLM은 결코 진정으로 ‘추론’하지 못합니다.

마무리 소회

첫 시도 치고 나쁘진 않은데, 실제 사용하진 않길 권합니다. OAuth 제공자를 올바르고 안전하게 구현하는 건 매우 어렵고, 이 프로젝트에 들어간 노력보다 훨씬 더 많은 시간과 정성이 필요합니다. 제 경험상, ForgeRock 시절 OAuth 구현에 수백 개의 보안 버그가 쌓였고, 자동화 테스트만 수십만건, 위협모델링, 최고 수준의 SAST/DAST, 매우 꼼꼼한 전문가 코드 리뷰가 반복됐습니다. LLM이 뚝딱 만들어 낼 수 있다고 생각한다면, 정말 말도 안 된다고 봅니다.

이 프로젝트의 커밋 히스토리는 매우 흥미롭습니다. 엔지니어들은 주요 설계 방향을 잘 잡았고, LLM을 잘 제어하며 코드를 뽑아냈습니다(이렇게 코딩하는 데엔 LLM이 꽤 괜찮음). 하지만 엉뚱한 짓도 여러 번 해서, 어떤 건 엔지니어가 잡았지만, 어떤 건 그냥 넘어간 것 같습니다. 아마 아직도 깔려 있는 문제도 있겠죠. 이게 전적으로 사람만 했을 때보다 못한가? 라고 하면 꼭 그렇진 않습니다. Stack Overflow 인기 답변에도 이런 실수 흔한데, Claude도 그걸 학습했을 겁니다. 하지만, 정말 꼼꼼한 엔지니어라면 더 잘했을 사람들이 분명 있습니다. 이런 코드는 정말 세심한 주의가 필요합니다. 디테일이 곧 전부입니다. README가 뭐라고 써놨든, 실제로 분위기로 코딩한 흔적이 보이고, 사람도 자주 그런 코드 씁니다. LLM이든 아니든, 우리는 정말 신경써야 합니다.

제가 LLM 프로젝트 경험에서 느낀 점, 그리고 본 프로젝트 리뷰에서 얻은 교훈은 이겁니다. LLM이 내놓은 코드가 제대로 된 건지 판단하려면, 내가 그 코드가 어떤 모습을 갖춰야 하는지 머릿속으로 분명히 알고 있어야 합니다. 진짜 좋은지 평가하려면, 스스로 비슷한 걸 한 번 만들어본 경험이 필요하고, “시스템 2” 사고로 받아들여야지, 그냥 눈 앞의 결과물이 괜찮다고 생각해선 안 됩니다. 그냥 대충 만들어도 되는 부분이라면 LLM이 뭐든 상관없지만, "내 인증 시스템" 같은 중요한 건, 내 손으로 직접 꼼꼼히 하고 싶습니다.