~/web-app-dev.md
WEB_APP_DEV

GraphQL 인트로스펙션은 편의 기능이 아니라 API 노출 계약이다

10분 읽기·2026.07.11·출처 5·00
오늘의 질문

GraphQL 코드 생성이나 GraphiQL이 갑자기 __schema 오류를 낸다면, 이것은 장애일까 아니면 보안 기본값의 변화일까?

web-app-dev.md
GraphQL exposure contract

GraphQL 인트로스펙션은 편의 기능이 아니라 API 노출 계약이다

1. 왜 지금 봐야 하나

Supabase의 2026년 7월 개발자 업데이트에는 작지만 제품 개발자에게 꽤 직접적인 변화가 들어 있다. pg_graphql v1.6.2가 GraphQL schema introspection을 기본적으로 꺼 둔 상태로 제공된다는 내용이다. Supabase changelog를 보면 실제 breaking change는 pg_graphql 1.6.0부터 시작되며, 2026년 6월 29일 이후 새 Supabase 프로젝트는 1.6.0 이상을 사용하고 introspection이 기본 비활성화된다.

이 변화는 “GraphiQL의 자동완성이 안 된다” 정도의 UI 문제가 아니다. GraphQL 생태계에서 introspection은 개발 도구가 API 표면을 읽어 가는 표준 경로다. GraphiQL, GraphQL Playground, Apollo DevTools, Relay compiler, graphql-codegen 같은 도구는 __schema__type 쿼리로 타입, 필드, 관계를 확인한다.

따라서 새 프로젝트에서 이런 오류를 만날 수 있다.

zsh — 생존확인.sh
{ "errors": [{ "message": "Unknown field \"__schema\" on type Query" }] }

처음 보면 “GraphQL API가 망가졌나?” 싶지만 핵심은 다르다. Supabase 문서에 따르면 일반 데이터 쿼리와 mutation은 이 설정과 무관하게 계속 동작한다. 바뀐 것은 데이터 접근 자체가 아니라 API 표면을 자동으로 열람할 수 있는 권한의 기본값이다.

오늘의 핵심 문장: GraphQL introspection은 개발 편의 기능인 동시에, API 구조를 외부에 공개하는 노출 계약이다.

2. 핵심 개념

GraphQL에는 일반 데이터 필드 외에 자기 자신을 설명하는 메타 필드가 있다.

  • __schema: 전체 스키마의 타입, 쿼리, mutation, directive 등을 조회한다.
  • __type: 특정 타입의 필드와 구조를 조회한다.

개발 도구는 이 정보를 바탕으로 자동완성, 문서 탐색, 타입 생성, Relay artifact 생성 등을 수행한다. 그래서 GraphQL을 쓰는 팀은 종종 “introspection은 개발 환경에서 켜져 있는 것이 당연하다”고 느낀다.

하지만 보안 관점에서 보면 introspection은 API 탐색 지도다. 공격자가 실제 row 데이터를 읽지 못하더라도, 어떤 타입과 관계, mutation 이름이 있는지 알 수 있다. RLS와 권한이 제대로 되어 있더라도 API 표면이 넓게 드러나면 열거 공격과 취약 경로 탐색의 비용이 낮아진다.

pg_graphql의 새 기본값은 이 지점을 명확히 한다.

zsh — 생존확인.sh
comment on schema public is e'@graphql({"introspection": true})';

이 SQL을 실행해야 특정 schema의 introspection이 켜진다. 이미 inflect_names 같은 설정을 schema comment에 쓰고 있다면 JSON 키를 합쳐야 한다.

zsh — 생존확인.sh
comment on schema public is e'@graphql({"inflect_names": true, "introspection": true})';

반대로 명시적으로 끄려면 다음처럼 쓸 수 있다.

zsh — 생존확인.sh
comment on schema public is e'@graphql({"introspection": false})';

중요한 점은 이 설정이 schema 단위라는 것이다. 여러 schema가 search_path에 노출되어 있을 때 어떤 schema는 introspection을 켜고, 어떤 schema는 끌 수 있다. 문서에 따르면 적어도 하나의 노출 schema에서 introspection이 켜져 있으면 __schema__type 자체는 동작하지만, introspection이 꺼진 schema의 타입은 결과에서 필터링된다.

3. 최신 이슈와 연결

Supabase changelog는 영향을 받는 대상을 꽤 구체적으로 적고 있다.

  • 2026년 6월 29일 이후 만들어진 새 프로젝트
  • 기존 프로젝트 중 pg_graphql을 1.6.0 이상으로 올리는 경우
  • GraphiQL, GraphQL Playground, Apollo DevTools, Relay compiler, codegen처럼 introspection에 의존하는 도구

즉, 운영 API가 갑자기 응답하지 않는 장애라기보다 개발·빌드 파이프라인에서 먼저 체감될 가능성이 높다.

예를 들어 팀이 CI에서 다음 흐름을 돌리고 있었다고 하자.

zsh — 생존확인.sh
Supabase GraphQL endpoint -> introspection query -> schema.graphql 생성 -> TypeScript 타입 생성 -> 앱 빌드

새 프로젝트로 staging을 만들거나 Postgres 버전 업그레이드 과정에서 pg_graphql 버전이 올라가면, 실제 앱의 저장·조회 쿼리는 살아 있는데 codegen만 실패할 수 있다. 프론트엔드 입장에서는 “백엔드는 되는데 타입 생성이 깨지는” 애매한 상태가 된다.

이때 해결책은 무조건 production에서 introspection을 켜는 것이 아니다. Supabase와 pg_graphql 문서는 production에서는 introspection을 비활성화하는 방향을 권장한다. 개발자가 해야 할 일은 환경별 계약을 분리하는 것이다.

zsh — 생존확인.sh
local/dev/staging: 필요한 schema만 introspection 허용
production: 기본 비활성화, 필요 시 제한된 내부 네트워크나 별도 스냅샷으로 codegen

4. 개발자 관점 해석

웹앱 개발자는 GraphQL introspection을 “도구가 쓰는 자동 문서” 정도로만 보면 안 된다. 이것은 세 가지 계약이 겹치는 지점이다.

첫째, 개발 도구 계약이다. GraphiQL 자동완성, graphql-codegen, Relay compiler는 introspection이 된다는 전제 위에서 움직인다. 이 전제가 사라지면 개발 경험이 깨진다.

둘째, 보안 노출 계약이다. 데이터 row는 RLS가 막아도 타입 이름, 관계 이름, mutation 이름이 노출되면 공격자는 시스템의 모양을 더 쉽게 추정한다. 특히 admin, billing, invitation, internal_note 같은 이름이 API 표면에 드러나는 것만으로도 힌트가 된다.

셋째, schema 운영 계약이다. Supabase GraphQL은 Postgres schema, 권한, search path를 바탕으로 API를 만든다. 따라서 “GraphQL schema를 관리한다”는 말은 별도의 SDL 파일만 관리한다는 뜻이 아니라 Postgres 객체, 권한, comment directive까지 함께 관리한다는 뜻이다.

작은 팀에서는 보통 다음 실수가 나온다.

  • production에서 GraphiQL이 안 된다고 급하게 public 전체 introspection을 켠다.
  • codegen을 production endpoint에 직접 붙인다.
  • private schema를 search path에 넣어 놓고 “RLS가 있으니 괜찮다”고 생각한다.
  • schema comment를 덮어쓰면서 기존 inflect_namesmax_rows 설정을 지운다.

이번 변경은 이런 습관을 고치라는 신호에 가깝다. GraphQL endpoint를 제품 API로 운영한다면, introspection도 feature flag처럼 다뤄야 한다.

5. 내 프로젝트에 적용할 체크포인트

  1. 내가 introspection에 의존하는 도구를 쓰는지 확인한다

    • GraphiQL schema explorer
    • graphql-codegen
    • Relay compiler
    • Apollo DevTools
    • 직접 만든 __schema 기반 문서 생성기
  2. 실패 메시지를 데이터 쿼리 장애와 구분한다

    • Unknown field "__schema" on type Query는 일반 CRUD 실패와 다르다.
    • accountCollection 같은 실제 데이터 쿼리가 되는지 별도로 확인한다.
  3. schema 단위로 켤지 결정한다

    • public만 열어도 되는가?
    • 내부 schema가 search_path에 포함되어 있는가?
    • codegen에 필요한 schema와 숨겨야 할 schema가 섞여 있는가?
  4. schema comment를 migration으로 관리한다

    • 콘솔에서 한 번 실행하고 잊지 않는다.
    • comment on schema는 기존 comment를 덮어쓸 수 있으므로, 여러 옵션을 하나의 JSON으로 합친다.
  5. production codegen 경로를 재검토한다

    • production endpoint에서 매번 introspection을 수행해야 하는가?
    • staging에서 schema artifact를 생성해 저장할 수 있는가?
    • CI secret과 네트워크 제한을 둘 수 있는가?

6. 오늘 10분 액션

오늘 바로 할 수 있는 점검은 간단하다.

  1. GraphQL endpoint에 introspection query를 던져 본다.
zsh — 생존확인.sh
{
  __schema {
    queryType { name }
  }
}
  1. 실패하면 일반 데이터 쿼리도 함께 확인한다.
zsh — 생존확인.sh
{
  accountCollection(first: 1) {
    edges { node { id } }
  }
}
  1. 개발 환경에서만 introspection이 필요하다면 migration에 다음 형태를 추가한다.
zsh — 생존확인.sh
comment on schema public is e'@graphql({"introspection": true})';
  1. 이미 다른 directive가 있다면 합친다.
zsh — 생존확인.sh
comment on schema public is e'@graphql({"inflect_names": true, "max_rows": 100, "introspection": true})';
  1. production에서는 반대로 명시적 비활성화를 검토한다.
zsh — 생존확인.sh
comment on schema public is e'@graphql({"introspection": false})';

오늘의 목표는 “무조건 켜기”가 아니다. 어느 환경에서, 어떤 schema를, 어떤 도구 때문에 열어야 하는지 적는 것이다.

7. 더 볼 자료

중복 회피 메모

로컬 content/generated와 Supabase 최근 web_app_dev 글을 확인했다. 최근 글은 PostgREST 13 JWT kid 검증, self-hosted Supabase Auth URL, Hono 서버리스 어댑터, Turbopack 캐시, Next.js 인증 경계, Server Actions, npm trusted publishing, Supabase OAuth/RLS, Supabase 브랜칭과 RLS 테스트를 다뤘다. 이번 글은 Supabase와 API 보안을 다시 언급하지만 RLS 정책 작성·JWT 서명 키·OAuth 위임이 아니라 pg_graphql 1.6.x의 introspection 기본 비활성화를 GraphQL 개발 도구와 API 표면 노출 계약으로 해석하는 데 집중한다.

오늘 10분 액션+5%

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)