🔷 GraphQL7. 보안 & 거버넌스📖 개요

07-security-governance — 보안 & 거버넌스

이 챕터가 답하는 질문: GraphQL의 공격 면적은 무엇이고, 큰 조직은 그래프를 어떻게 관리하는가? 한 줄 답 (Pyramid Top): 공격 면적이 endpoint가 아니라 쿼리 자체이므로 depth·complexity·introspection·auth를 서버에서 강제해야 하고, 큰 그래프는 버전이 아니라 진화로 운영한다.


한 문장 답 (Pyramid Top)

REST는 endpoint 단위로 인증·rate limit·로깅을 끼울 수 있다 — path가 “이 사람이 뭘 하는지”를 알려준다. GraphQL은 endpoint가 하나이고, 그 안에 보내진 쿼리 한 줄이 전체 그래프의 어디든 탐색할 수 있다. 즉 GraphQL의 공격 면적은 쿼리 자체다 — depth, complexity, alias, introspection, batching이 모두 서버에서 강제돼야 한다. 한편 큰 조직의 거버넌스 문제는 “필드를 어떻게 늘리고 어떻게 줄이느냐” 인데, GraphQL은 버전이 없다 — 대신 @deprecated진화의 책임을 빌더에게 옮긴다. 이 챕터는 공격 면적(01)에서 시작해, depth/complexity(02), introspection(03), auth(04), rate limit & cost(05), linting & governance(06), versioning-없음(07)까지 7층으로 보안과 거버넌스를 분해한다.


챕터 지도 (Mermaid)


Why — 왜 이 챕터를 별도로 빼는가

GraphQL은 디폴트로 안전하지 않다. spec은 “쿼리를 검증하고 실행하라”고 적혀 있지만, 어떤 쿼리를 거절할 것인가는 한 줄도 적혀 있지 않다. 그래서 production에서 다음 5가지가 직접 손을 봐야 한다.

위험REST에서는GraphQL에서는어디서 다루나
거대 응답path별 응답 크기 알려져 있음{ user { posts { author { posts { ... } } } } } 한 줄로 폭발01, 02
스키마 노출OpenAPI spec을 명시 공개introspection이 디폴트 켜짐 — 공격자가 schema를 통째로 얻음03
권한URL path로 분리 가능endpoint 하나 — 필드 단위 권한 필요04
Rate limitreq/sec 기반 충분쿼리당 복잡도가 다름 — cost 기반 필요05
그래프 진화v1/v2/v3 endpoint 추가한 그래프 — 필드 추가/폐기로 진화06, 07

여기서 핵심은 공격 면적의 위치 이동이다. REST에서는 nginx 한 줄(limit_req_zone)로 끝나던 일이, GraphQL에서는 쿼리 파서가 끝난 직후, resolver가 실행되기 전의 좁은 창에서 모두 처리돼야 한다.


How — 어떻게 읽나

다음 7개 문서를 순서대로 읽으면 약 90분이 걸린다. 각 문서는 독립적으로 읽혀도 되지만, 누적적이다.

#파일읽는 데핵심 키워드
0101-attack-surface-of-graphql.mdx12분introspection · alias · batching · cyclic · OWASP cheat sheet
0202-query-depth-and-complexity-limit.mdx15분depth · complexity · cost · GitHub v4 · Shopify · graphql-query-complexity
0303-introspection-control.mdx12분NoIntrospection · persisted query · schema registry · trade-off
0404-authentication-and-authorization.mdx14분context · @auth directive · graphql-shield · Apollo · casbin · oso
0505-rate-limiting-and-cost-analysis.mdx14분token bucket · cost point · per-field · GitHub 5000/h
0606-schema-linting-and-governance.mdx12분graphql-eslint · Apollo Rover · 단수/복수 · deprecation reason
0707-versioning-non.mdx10분@deprecated · 추가는 안전 · 사용량 분석 · 진화 책임

의존성: 0201을, 0301을, 0401을, 0502를, 06~0704까지 가정한다.


What — 한 페이지 요약 (모든 문서의 핵심 한 줄)

문서한 줄 결론
01공격 면적은 endpoint가 아니라 쿼리 자체 — introspection·alias·batching·cyclic 4가지가 4대 공격 패턴이다.
02depth limit < complexity limit < cost-based — 정밀도와 구현 비용의 trade-off이며, 셋 다 서버에서 강제돼야 한다.
03production introspection 끄기 vs 켜기는 정책 결정이다 — persisted query를 쓰면 introspection이 필요 없어진다.
04인증은 context에 user 주입, 권한은 resolver 단위 또는 @auth directive — directive는 schema에 보이고, middleware는 코드에 숨는다.
05REST의 token bucket은 그대로 못 쓰고, 쿼리당 cost로 환산해야 한다 — GitHub v4의 5000 point/h가 사실상 표준 예시.
06큰 그래프는 컨벤션으로 산다 — 단수/복수, ID! vs Int, mutation 명명, deprecation reason은 graphql-eslint·rover로 강제.
07GraphQL은 버전이 없다@deprecated로 진화하며, 필드 추가는 항상 안전·제거는 사용량 분석 후.

What-if — 이 챕터를 건너뛰면

  • 01(공격 면적)을 모르면: nginx로만 막아 두고 안심하다가 query { __schema { types { fields { type { fields { ... } } } } } } 한 줄로 DB가 죽는다.
  • 02(depth/complexity)를 안 깔면: 악의 없는 클라이언트가 재귀 쿼리 한 번에 timeout을 부른다 — 사용자는 “GraphQL 느려요”라고만 말한다.
  • 03(introspection)을 모르면: production에 introspection을 켠 채 배포해서 공격자가 schema 전체를 합법적으로 다운로드해 간다.
  • 04(auth)를 모르면: middleware 한 줄에 if (!ctx.user) throw만 박아 두고 — 내부 필드까지 권한 검사 없이 노출된다.
  • 05(rate limit)을 모르면: REST식 100 req/min만 켜 두고 — 한 요청 안의 100개 필드에 당한다.
  • 06(governance)을 모르면: 팀이 늘면 getUser / findUser / userById같은 그래프에 공존하기 시작한다.
  • 07(versioning)을 모르면: REST 습관으로 /v2/graphql을 만들고 — 그 순간 GraphQL의 살아 있는 그래프 모델이 깨진다.

Insight — 한 단락 이야기

“GraphQL은 spec이 정의하지 않은 곳에서 무너진다”

2018년 Black Hat USA에서 발표된 GraphQL 보안 세션의 첫 슬라이드는 이랬다 — “우리는 모든 공격을 spec 안에서 정당하게 했다.” 그들이 보여준 공격(introspection 덤프, alias amplification, cyclic query, batch flooding)은 어느 것도 spec 위반이 아니었다. 모두 합법적인 쿼리였다. 그 의미는 분명하다 — spec이 정의하지 않은 곳이 보안의 위치다. depth limit도 complexity limit도 introspection 끄기도 spec에 없다. spec은 “쿼리가 들어오면 검증하고 실행하라”고만 적혀 있고, *“검증의 한계는 네가 정해라”*고 말한다. 추상화가 자유로울수록, 그 자유를 서버 쪽에서 닫는 책임이 무겁다 — 이 챕터가 하는 일은 그 닫기를 7층으로 분해하는 것.


Mermaid 4색 규약


공격 면적 다이어그램

5개의 공격 패턴(danger)이 모두 endpoint 하나로 들어오고, Validator → AuthZ → Rate limit의 세 관문(result)을 통과해야 resolver에 닿는다. 이 세 관문이 spec 외부의 정책이라는 점이 이 챕터의 출발점이다.


한 단락 요약

GraphQL의 공격 면적은 쿼리 자체이며(01), 그 면적을 서버에서 닫는 도구가 depth/complexity(02)와 introspection 제어(03)다. 다음 층은 권한(04)과 cost 기반 rate limit(05)으로, 둘 다 필드 단위가 기본 단위가 된다. 마지막은 거버넌스 — 컨벤션과 linting(06)으로 큰 그래프를 컨벤션으로 운영하고, 버전 없이 진화(07)하는 정책으로 마감한다. 이 챕터를 끝내면 “GraphQL은 안전한가요?” 라는 질문 대신 “이 쿼리의 cost는 얼마이고, 누가 어느 필드에 권한이 있는가?” 라는 질문을 던지게 된다. 다음 챕터(08-theory-and-alternatives)는 이 모든 결정의 이론적 배경과 대안 접근들을 다룬다.