웹 개발 데일리 브리핑 — 2026-10-13
Soshy·

기준 시점: 2026-10-13 06:00 KST
조사 범위: 2026-10-12 06:00 ~ 2026-10-13 06:00 KST
추천 글 범위: 기준 시점 당시 최근 7일
1️⃣ 프론트엔드
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
2️⃣ 웹 플랫폼·브라우저
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
3️⃣ 백엔드·인프라
Cloudflare Workers, 런타임 업데이트 시 기존 요청의 유예 기간을 최대 20분으로 확대
- 발표일: 2026-10-12 — 정확한 게시 시각 미확인
- 적용 대상: Cloudflare Workers
- 원문: https://developers.cloudflare.com/changelog/post/2026-10-12-longer-runtime-update-grace-period/
Cloudflare가 Workers 런타임을 업데이트할 때 기존 요청과 연결이 종료되기까지의 유예 기간을 최대 30초에서 20분으로 확대했다.
Cloudflare Workers는 JavaScript와 TypeScript를 비롯한 애플리케이션 코드를 Cloudflare의 서버리스 실행 환경에서 처리한다.
이 환경에서는 개발자가 직접 코드를 배포하지 않더라도 Cloudflare가 런타임 자체를 업데이트할 수 있다. 보안 수정이나 실행 엔진 개선 등이 이루어지면 새로운 런타임이 준비되고, 기존 런타임에서 실행 중이던 프로세스는 점차 교체된다.
문제는 이러한 교체 과정에서 처리 시간이 긴 요청이 영향을 받을 수 있다는 점이다.
일반적인 API 요청은 수십 밀리초에서 수 초 안에 완료되기 때문에 큰 문제가 없을 수 있다. 그러나 모든 요청이 짧게 끝나는 것은 아니다.
예를 들어 LLM이 생성하는 응답을 HTTP 스트림으로 전달하거나, 장시간 데이터를 전송하거나, WebSocket을 통해 실시간 연결을 유지하는 애플리케이션에서는 하나의 연결이 상당 시간 지속될 수 있다.
기존에는 런타임 업데이트가 발생한 뒤 최대 30초의 유예 기간이 제공됐다.
해당 시간이 지나도록 작업을 완료하지 못한 요청이나 연결은 종료될 수 있었다.
이번 변경으로 유예 기간이 최대 20분으로 확대됐다.
이는 기존보다 긴 작업이 런타임 업데이트 과정에서 중단되지 않고 완료될 기회를 제공한다.
런타임 교체 과정의 동작
Cloudflare는 새로운 런타임이 준비되면 새로 들어오는 HTTP 요청을 업데이트된 런타임으로 전달한다.
이전 런타임에서 실행 중인 프로세스는 새로운 HTTP 요청을 받지 않고, 이미 처리 중이던 작업을 완료하는 데 집중한다.
이 과정을 Draining이라고 한다.
기존 프로세스에 연결된 요청과 연결은 유예 기간 동안 계속 처리될 수 있다.
다만 최대 20분이 지나면 아직 완료되지 않은 요청이나 열려 있는 연결은 종료된다.
유예 기간은 전체 애플리케이션에 일괄적으로 적용되는 하나의 타이머가 아니라 이전 런타임을 실행하고 있는 각 프로세스에 적용된다.
이러한 구조는 장시간 실행되는 요청을 유지하면서 새로운 요청은 최신 런타임으로 처리하려는 방식이다.
WebSocket과 스트리밍 응답의 제한사항
유예 기간이 최대 20분으로 늘어났다고 해서 WebSocket 연결이 최소 20분 동안 반드시 유지된다는 의미는 아니다.
Cloudflare는 WebSocket이 유예 기간이 끝나기 전에도 종료될 수 있다고 명시했다.
이는 WebSocket Hibernation API를 사용하는 연결에도 해당한다.
따라서 장시간 연결을 유지하는 애플리케이션에서는 연결이 끊어졌을 때 자동으로 재연결하고, 필요한 상태를 복구하는 구조가 여전히 필요하다.
예를 들어 실시간 협업 서비스에서 WebSocket 연결이 종료됐다면 사용자는 재연결 이후 서버에 마지막으로 정상 수신한 이벤트의 위치를 전달할 수 있어야 한다.
서버 역시 재연결된 클라이언트가 누락된 데이터를 다시 가져올 수 있도록 상태와 이벤트를 관리해야 한다.
AI 응답 스트리밍에서도 마찬가지다.
응답을 생성하는 도중 연결이 종료되면 요청을 무조건 다시 보내기보다 이미 처리된 작업과 중복 실행될 수 있는 작업을 구분해야 한다.
특히 결제, 데이터 변경, 외부 API 호출처럼 부수효과가 있는 작업은 재시도 과정에서 동일한 작업이 중복 실행되지 않도록 설계해야 한다.
이번 업데이트는 기존 요청이 중단될 가능성을 줄이는 변경이지, 장시간 연결의 무중단 실행을 보장하는 기능은 아니다.
Cloudflare는 WebSocket 재연결, 안전한 요청 재시도, 프로세스 재시작 이후 저장된 상태 복구를 계속 구현할 것을 권장한다.
Cloudflare Queues, EU·US·FedRAMP 관할권 제한 지원 — 메시지 저장과 처리를 지정된 지역으로 제한
- 발표일: 2026-10-12 — 정확한 게시 시각 미확인
- 적용 대상: Cloudflare Queues
- 원문: https://developers.cloudflare.com/changelog/post/2026-09-18-queues-jurisdictions/
Cloudflare가 Queues에 관할권(Jurisdiction) 제한 기능을 추가했다.
Cloudflare Queues는 애플리케이션에서 발생한 작업을 비동기적으로 처리하기 위한 메시지 큐다.
예를 들어 사용자가 파일을 업로드하면 HTTP 요청에서는 업로드 완료만 처리하고, 이미지 변환이나 분석 작업은 Queue에 메시지로 등록할 수 있다.
이렇게 하면 시간이 오래 걸리는 작업을 사용자 요청과 분리할 수 있고, 작업 처리량이 증가해도 Consumer의 실행 규모를 조정하는 방식으로 대응할 수 있다.
하지만 글로벌 클라우드 환경에서는 메시지가 저장되거나 처리되는 위치가 중요할 수 있다.
메시지에는 사용자 정보, 주문 정보, 업무 데이터 또는 규제 대상 정보가 포함될 수 있기 때문이다.
특히 기업 고객이 데이터의 저장 및 처리 위치를 특정 지역으로 제한해야 한다면, 애플리케이션 서버뿐 아니라 비동기 메시지 시스템에도 동일한 조건이 적용돼야 한다.
이번 기능은 메시지가 저장되고 처리되는 지역을 Queue 생성 시점에 지정할 수 있도록 한다.
지원하는 관할권은 다음 세 가지다.
| 관할권 | 의미 |
|---|---|
eu | 유럽연합 |
us | 미국 |
fedramp | FedRAMP Moderate 데이터센터 |
Queue를 생성할 때 Cloudflare CLI의 --jurisdiction 옵션을 사용하거나 Create Queue API에 jurisdiction 값을 전달할 수 있다.
예를 들어 eu를 지정하면 해당 Queue에 게시된 메시지는 유럽연합 관할권 안에서 저장되고 처리되도록 제한된다.
기존 글로벌 메시지 처리와의 차이
일반적인 글로벌 메시지 시스템에서는 사용자가 어느 지역에서 요청했는지와 메시지가 실제로 저장되는 위치가 반드시 일치하지 않을 수 있다.
서버리스 실행 환경은 요청을 가까운 지역에서 처리할 수 있지만, 그 과정에서 생성한 메시지가 다른 지역에 저장될 수도 있다.
이는 성능 문제와 별개로 데이터 거주성(Data Residency) 요구사항과 충돌할 수 있다.
이번 변경은 Queue 자체에 지역 제한을 적용해 비동기 처리 과정까지 데이터 거주성 정책의 적용 범위를 확대한다.
다만 특정 관할권을 선택했다는 사실만으로 GDPR이나 FedRAMP의 모든 요구사항을 자동으로 충족하는 것은 아니다.
실제 규제 준수 여부는 메시지에 포함된 데이터의 종류, 접근 권한, 로그와 백업, 연결된 다른 서비스의 데이터 처리 위치 등에 따라 달라진다.
생성 이후 관할권을 변경할 수 없다
가장 중요한 제한은 Queue를 생성한 이후 관할권을 변경할 수 없다는 점이다.
처음에 us로 생성한 Queue를 나중에 eu로 변경하는 기능은 제공되지 않는다.
따라서 애플리케이션의 고객별 데이터 정책과 배포 지역을 고려해 Queue를 생성해야 한다.
이미 운영 중인 Queue의 관할권을 변경해야 한다면 새로운 Queue를 생성하고 Producer와 Consumer의 연결을 변경하는 별도 이전 작업이 필요할 수 있다.
이 과정에서는 기존 메시지 처리, 중복 작업 방지, 실패한 메시지 재처리 등을 함께 고려해야 한다.
이번 변경은 Queue API의 일반적인 메시지 전송 방식을 변경한 것이 아니라 메시지 저장 및 처리 위치에 대한 운영 정책을 Queue 수준에서 설정할 수 있도록 확장한 기능이다.
Cloudflare Agents SDK, 웹 검색·문서 수집 도구 추가 — 검색 결과와 실제 문서 내용을 연결하는 에이전트 구성 지원
- 발표일: 2026-10-12 — 정확한 게시 시각 미확인
- 적용 대상: Cloudflare Agents SDK
web_search: Agents SDK 0.27.0 이상web_fetch: Agents SDK 0.28.0 이상- 웹 검색 원문: https://developers.cloudflare.com/changelog/post/2026-10-12-web-search/
- 문서 수집 원문: https://developers.cloudflare.com/changelog/post/2026-10-11-web-fetch/
Cloudflare가 Agents SDK에 웹 검색과 문서 수집을 위한 두 가지 도구를 추가했다.
두 기능은 서로 다른 역할을 수행하지만, 검색한 문서를 실제로 읽는 하나의 작업 흐름으로 연결되므로 함께 정리한다.
AI 에이전트가 외부 정보를 이용해 답변하려면 일반적으로 두 단계가 필요하다.
먼저 질문과 관련된 문서를 찾아야 한다.
그다음 검색 결과의 제목이나 짧은 설명만 사용하는 대신 실제 문서를 읽고 내용을 확인해야 한다.
검색 결과에 포함된 짧은 설명만으로는 문서의 제한사항이나 구체적인 구현 내용을 정확하게 이해하기 어렵기 때문이다.
이번 업데이트에서는 이러한 두 단계를 web_search와 web_fetch라는 독립적인 도구로 제공한다.
web_search: 공개 웹 검색
web_search는 에이전트가 입력한 검색어를 바탕으로 공개 웹에서 관련 문서를 찾는다.
Cloudflare Web Search API를 통해 검색을 수행하며, 해당 API에는 ceramic.ai, Exa, Linkup이 사용된다.
모델은 검색어와 최대 결과 수를 전달한다.
도구는 검색 결과의 제목, URL, 짧은 설명을 번호가 있는 목록으로 반환한다.
중요한 점은 검색 결과 자체가 문서의 전체 본문은 아니라는 사실이다.
검색 결과는 관련 문서를 발견하기 위한 정보이며, 실제 내용을 검증하려면 별도의 문서 조회가 필요하다.
Agents SDK는 Pi Durable의 확장 레지스트리에 webSearchTool을 등록하는 방식으로 이 기능을 제공한다.
또한 Cloudflare는 모델을 호출하지 않는 일반 애플리케이션에서도 검색 기능을 사용할 수 있도록 createAIWebSearch()와 createHTTPWebSearch()를 제공한다.
따라서 모든 웹 검색 작업에 AI 에이전트를 실행해야 하는 것은 아니다.
web_fetch: 웹페이지와 문서를 읽을 수 있는 형태로 변환
web_fetch는 에이전트가 특정 URL의 내용을 읽도록 하는 도구다.
지원 대상에는 일반적인 웹페이지뿐 아니라 PDF와 지원되는 Office 문서도 포함된다.
Cloudflare Workers AI의 Markdown Conversion 기능을 이용해 문서 내용을 모델이 처리하기 쉬운 텍스트 형태로 변환한다.
웹사이트가 Markdown for Agents를 지원한다면 해당 사이트에서 Markdown을 직접 요청한다.
JSON 응답은 모델이 이해하기 쉬운 형태로 정리한다.
도구는 문서 내용과 함께 다음 정보를 제공한다.
- 리다이렉트 이후 최종 URL
- HTTP 응답 상태
- 콘텐츠 유형
- 변환된 문서 내용
- 긴 문서를 이어서 읽기 위한 Offset
특히 긴 문서를 한 번에 모델에 전달하지 않고 여러 구간으로 나누어 반환할 수 있다는 점이 중요하다.
대규모 문서를 한 번에 입력하면 모델의 Context Window를 빠르게 소비하거나 필요한 부분을 찾기 어려워질 수 있다.
Offset을 이용하면 에이전트가 특정 구간을 읽은 뒤 다음 구간을 계속 요청할 수 있다.
외부 URL 조회의 제어 범위
에이전트가 임의의 URL을 조회할 수 있도록 허용하면 보안과 운영 측면에서 여러 문제가 발생할 수 있다.
예를 들어 신뢰할 수 없는 콘텐츠에 포함된 URL을 그대로 따라가거나, 접근해서는 안 되는 호스트에 요청할 가능성을 고려해야 한다.
Cloudflare는 이를 제한하기 위해 다음과 같은 설정을 제공한다.
- 접근을 허용할 호스트
- 최대 응답 크기
- 요청 타임아웃
- 리다이렉트 처리 방식
이러한 제한은 외부 문서 조회 작업의 범위를 제어하기 위한 기능이다.
다만 도구가 접근할 수 있는 호스트를 제한한다고 해서 외부 문서의 내용이 신뢰할 수 있는 지시로 바뀌는 것은 아니다.
에이전트가 가져온 웹페이지에는 잘못된 정보나 악의적인 지시가 포함될 수 있다.
따라서 외부 문서의 내용과 애플리케이션이 신뢰하는 시스템 지시를 구분하는 구조는 별도로 필요하다.
검색과 수집을 연결하는 방식
두 도구를 함께 등록하면 Agents SDK는 모델에 검색 결과를 읽을 때 web_fetch를 사용하도록 안내한다.
전체 흐름은 다음과 같다.
- 사용자가 특정 기술에 관한 질문을 전달한다.
- 에이전트가
web_search로 관련 문서를 찾는다. - 검색 결과에서 필요한 URL을 선택한다.
web_fetch로 해당 문서의 본문을 가져온다.- 읽은 내용을 바탕으로 사용자에게 답변한다.
이 구조는 검색과 실제 내용 확인을 분리한다.
특히 기술 문서, 릴리스 노트, 연구 자료처럼 제목만으로 변경 사항을 판단하기 어려운 정보에 적용할 수 있다.
web_fetch는 Pi Durable뿐 아니라 AI SDK, TanStack AI를 사용하는 Harness와도 연결할 수 있다.
모델을 호출하지 않고 문서만 조회해야 한다면 fetchWeb(), windowWebFetchPage(), renderWebFetchPage()를 직접 사용할 수도 있다.
이번 발표는 새로운 검색 모델 자체를 공개한 것이 아니라 Cloudflare Workers 기반 에이전트에서 웹 검색과 문서 조회를 표준화된 도구로 구성할 수 있도록 한 SDK 확장이다.
Cloudflare Agents SDK, OpenCode 2.0 실행 지원 — Durable Objects에 코딩 에이전트의 세션과 실행 상태 저장
- 발표일: 2026-10-12 — 정확한 게시 시각 미확인
- 안정화 단계: Experimental
- 적용 대상: Agents SDK 0.28.0 이상, OpenCode 2.0
- 원문: https://developers.cloudflare.com/changelog/post/2026-10-12-opencode-harness/
Cloudflare가 Agents SDK에서 OpenCode 2.0을 실행할 수 있는 실험적 OpenCodeHarness를 추가했다.
OpenCode는 모델과 도구를 연결해 개발 작업을 수행하는 코딩 에이전트다.
일반적인 코딩 에이전트는 실행 중인 프로세스에 대화 기록과 작업 상태를 저장하거나 로컬 파일시스템을 이용해 상태를 관리할 수 있다.
하지만 서버리스 환경에서는 실행 프로세스가 언제든 종료되거나 다른 실행 환경에서 다시 시작될 수 있다.
따라서 로컬 프로세스에만 의존해 세션을 유지하면 작업 중간에 프로세스가 종료됐을 때 이전 상태를 복구하기 어렵다.
이번 통합은 OpenCode를 Cloudflare Durable Objects 안에서 실행하면서 세션과 수명주기를 관리하는 구조를 제공한다.
Durable Objects와 세션 저장
Durable Objects는 특정 객체 ID에 연결된 상태와 실행을 관리하는 Cloudflare의 플랫폼 기능이다.
같은 객체에 대한 요청을 특정 실행 단위로 연결할 수 있으며, 지속 저장소를 이용해 프로세스가 종료된 이후에도 필요한 상태를 유지할 수 있다.
OpenCodeHarness는 이 구조를 이용해 OpenCode 세션을 SQLite에 저장한다.
에이전트가 사용자와 대화하거나 여러 도구를 실행하는 과정에서 생성된 세션 정보가 단순한 프로세스 메모리에만 의존하지 않도록 구성하는 방식이다.
Cloudflare는 Lifecycle 작업을 통해 실행 환경이 종료된 뒤에도 중단된 작업을 다시 이어갈 수 있는 구조를 제공한다.
여기서 중요한 차이는 프로세스를 계속 실행하는 것과 작업 상태를 복구할 수 있도록 만드는 것이다.
서버리스 환경에서 동일한 프로세스를 무한히 유지하는 것이 아니라 실행이 중단되더라도 필요한 상태를 저장하고 이후 다시 처리하는 방식이다.
Workers AI와 외부 도구 연결
이번 통합은 agents/models/opencode Provider를 제공한다.
이를 통해 OpenCode가 Cloudflare Workers AI의 모델을 사용할 수 있다.
Cloudflare가 공개한 예제에서는 Workers AI의 AI Binding으로 모델을 연결하고, OpenCode의 build 에이전트를 실행한다.
OpenCode 플러그인을 이용하면 다음 기능도 추가할 수 있다.
- 애플리케이션 전용 커스텀 도구
- 원격 MCP 서버
- 재사용 가능한 Skills
- 지속 저장소를 이용하는 도구
공식 예제 프로젝트에서는 브라우저 UI와 WebSocket 스트리밍, 영속적인 메모 도구를 결합하는 방식도 제공한다.
이는 에이전트가 단일 HTTP 요청 안에서 답변을 생성하고 종료하는 구조뿐 아니라 여러 요청에 걸쳐 상태를 유지하는 애플리케이션을 만들 수 있도록 한 것이다.
현재는 실험적 기능
OpenCodeHarness는 아직 Experimental 단계다.
따라서 기존 Agents SDK의 모든 안정적인 기능과 동일한 호환성 보장을 제공한다고 가정해서는 안 된다.
또한 Durable Objects가 제공하는 세션 저장과 작업 복구 기능은 코딩 에이전트의 실행 결과가 항상 올바르다는 사실을 보장하지 않는다.
실행 중인 도구의 접근 권한과 외부 API 호출에 따른 부수효과 역시 별도로 관리해야 한다.
이번 변경은 새로운 코딩 모델 출시가 아니라 OpenCode의 실행 상태와 수명주기를 Cloudflare의 서버리스 인프라에 연결하는 통합 기능이다.
4️⃣ 개발 도구 및 보안
GitHub, 커밋을 실제로 Push한 사용자 기록 지원 — Author·Committer 위조와 인증된 Push 사용자 구분
- 발표일: 2026-10-12 — 정확한 게시 시각 미확인
- 안정화 단계: Limited Public Preview
- 적용 대상: GitHub Enterprise 고객 중 Preview 참여 조직
- 원문: https://github.blog/changelog/2026-10-12-push-attribution-public-preview/
GitHub가 각 커밋을 실제로 Push한 사용자의 인증된 신원을 별도로 기록하는 Push Attribution 기능을 공개했다.
Git에는 커밋을 만든 사람을 나타내는 Author와 커밋을 기록한 사람을 나타내는 Committer 정보가 존재한다.
문제는 이 정보가 Git 클라이언트에서 지정하는 문자열이라는 점이다.
사용자는 Git 설정을 통해 이름과 이메일 주소를 지정할 수 있으며, 해당 값을 이용해 커밋을 생성할 수 있다.
따라서 커밋의 Author나 Committer에 특정 관리자의 이메일 주소가 표시된다고 해서 실제로 그 관리자가 해당 코드를 Push했다고 단정할 수 없다.
GitHub는 Push 요청을 처리할 때 실제 사용자를 인증하지만, 기존 커밋 메타데이터는 이러한 인증 정보와 독립적으로 관리됐다.
이번 기능은 이 차이를 해결하기 위해 GitHub가 인증한 Push 사용자의 신원을 커밋마다 별도로 기록한다.
인증된 Pusher 신원 기록
새로운 Push Attribution은 다음 세 가지 정보를 구분한다.
- Author: 커밋의 작성자로 기록된 사용자
- Committer: 커밋을 기록한 사용자
- Pusher: 실제 Push 요청을 수행하며 GitHub의 인증을 통과한 사용자
Author와 Committer는 커밋에 포함된 메타데이터다.
반면 Pusher는 GitHub가 Push 과정에서 확인한 인증된 사용자 정보다.
GitHub는 이 Pusher 정보를 지속적으로 기록하고 UI와 API에서 확인할 수 있도록 했다.
따라서 커밋의 작성자 정보가 특정 개발자를 가리키더라도 실제 Push를 수행한 사용자가 다른 사람이라면 두 정보를 구분해 확인할 수 있다.
이 기능은 코드 검토와 보안 사고 조사에서 활용될 수 있다.
예를 들어 공격자가 권한이 낮은 계정을 탈취한 뒤 관리자의 이름과 이메일 주소로 커밋을 위조했다고 가정해 보자.
커밋 메타데이터만 확인하면 정상적인 관리자가 작업한 것처럼 보일 수 있다.
하지만 인증된 Pusher 정보가 별도로 기록된다면 실제로 해당 Push를 수행한 계정을 확인할 수 있다.
Committer와 Pusher의 일치 여부 강제
GitHub는 새로운 Repository Ruleset 조건인 Committer must match pusher도 추가했다.
이 규칙을 활성화하면 커밋의 Committer 이메일 주소가 실제 Push를 수행한 사용자의 확인된 이메일 주소와 일치해야 한다.
조건을 만족하지 못하면 GitHub가 해당 Push를 차단한다.
기존에는 Author나 Committer 정보만으로 커밋을 신뢰하는 정책을 구성하기 어려웠지만, 새 규칙을 이용하면 인증된 Push 사용자와 커밋 메타데이터 사이의 관계를 정책으로 강제할 수 있다.
다만 이 규칙은 정상적인 개발 과정에도 영향을 줄 수 있다.
Git에서는 다른 사람이 작성한 패치를 적용하거나 여러 개발자의 커밋을 Push하는 일이 발생할 수 있기 때문이다.
특히 커밋을 실제로 생성한 사용자와 Push를 수행하는 사용자가 다른 협업 방식에서는 규칙을 적용하기 전에 기존 작업 흐름과의 호환성을 검토해야 한다.
또한 Pusher가 인증됐다는 사실이 커밋 내용 자체의 안전성을 보장하는 것은 아니다.
악의적인 코드를 작성한 사용자가 자신의 계정으로 정상적으로 Push하는 상황까지 방지하는 기능은 아니기 때문이다.
이번 기능은 커밋의 내용 검증과 별개로 코드 변경을 저장소에 전달한 주체의 신뢰성을 강화한다.
현재는 모든 GitHub 사용자에게 제공되는 정식 기능이 아니다.
GitHub Enterprise 고객을 대상으로 한 Limited Public Preview이며, 참여하려면 GitHub 담당 팀에 접근 권한을 요청해야 한다.
Cloudflare WAF, TanStack Start XSS와 Atlassian 파일 접근 취약점에 대한 신규 차단 규칙 추가
- 발표일: 2026-10-12 — 정확한 게시 시각 미확인
- 적용 대상: Cloudflare Managed Ruleset
- Cloudflare 원문: https://developers.cloudflare.com/changelog/post/2026-10-12-waf-release/
- TanStack 공식 보안 공지: https://tanstack.com/blog/tanstack-start-security-update-cve-2026-102989
Cloudflare가 TanStack Start와 Atlassian 제품의 기존 보안 취약점을 완화하기 위한 새로운 WAF 탐지 규칙을 공개했다.
이번 발표는 두 취약점이 처음 발견됐다는 소식이 아니다.
TanStack Start의 취약점은 9월 30일에 이미 공개됐으며, 이번 조사 기간에 새로 공개된 변경은 Cloudflare Managed Ruleset에 해당 공격을 탐지하고 차단하는 규칙이 추가됐다는 점이다.
TanStack Start의 Reflected XSS 취약점
첫 번째 대상은 CVE-2026-102989다.
TanStack Start에서 발생하는 Reflected Cross-Site Scripting(XSS) 취약점이다.
Reflected XSS는 공격자가 조작한 입력이 웹페이지의 응답에 안전하지 않은 형태로 포함되면서 공격자의 JavaScript가 실행될 수 있는 취약점이다.
일반적인 XSS와 마찬가지로 사용자가 공격자가 만든 URL을 열었을 때 해당 사이트의 Origin에서 스크립트가 실행될 수 있다.
이번 TanStack Start 취약점은 Server Function 응답 처리 과정과 관련된다.
공격자는 특수하게 구성한 URL을 이용해 영향을 받는 애플리케이션이 공격자가 제어하는 HTML을 자신의 Origin에서 반환하도록 만들 수 있다.
피해자의 브라우저가 이 응답을 신뢰하고 JavaScript를 실행하면 사용자의 세션을 이용한 비정상적인 동작이나 데이터 접근이 발생할 수 있다.
공식 보안 공지에 따르면 다음 패키지의 특정 버전이 영향을 받는다.
| 패키지 | 영향을 받는 버전 | 수정 버전 |
|---|---|---|
@tanstack/react-start | 1.143.12 이상, 1.168.60 미만 | 1.168.60 |
@tanstack/solid-start | 1.143.12 이상, 1.168.57 미만 | 1.168.57 |
@tanstack/vue-start | 1.143.12 이상, 1.168.56 미만 | 1.168.56 |
@tanstack/start-server-core | 1.143.12 이상, 1.169.39 미만 | 1.169.39 |
Cloudflare는 이번에 TanStack - Reflected XSS - CVE:CVE-2026-102989라는 새로운 탐지 규칙을 추가했다.
규칙의 기본 동작은 Block이다.
즉 해당 취약점을 악용하는 것으로 판단되는 요청을 차단하도록 구성됐다.
하지만 WAF 규칙이 추가됐다고 해서 취약한 애플리케이션이 완전히 수정된 것은 아니다.
WAF는 요청의 특성을 분석해 알려진 공격 패턴을 차단하는 보완적인 방어 계층이다.
새로운 우회 방법이나 다른 요청 형태를 이용하는 공격이 존재할 수 있으므로 영향을 받는 패키지를 수정 버전으로 업데이트하는 것이 근본적인 대응이다.
Atlassian 제품의 파일 접근 취약점
두 번째 대상은 CVE-2026-21589다.
Atlassian 제품에 영향을 주는 임의 파일 접근(Arbitrary File Access) 취약점이다.
공격자가 권한 없이 파일에 접근할 수 있다면 애플리케이션의 설정 파일이나 민감한 정보가 노출될 가능성이 있다.
Cloudflare는 이를 탐지하기 위한 Atlassian - Arbitrary File Access - CVE:CVE-2026-21589 규칙을 추가했다.
기본 동작 역시 Block이다.
다만 Cloudflare의 이번 공지는 WAF 탐지 규칙에 초점을 맞추고 있으며, Atlassian 제품별 영향을 받는 버전과 수정 버전을 상세하게 제공하지 않는다.
따라서 해당 제품을 사용하는 조직은 Cloudflare의 규칙만 확인하기보다 Atlassian의 공식 보안 권고를 통해 사용 중인 제품과 버전의 영향을 별도로 확인해야 한다.
기존 규칙 통합도 함께 진행
이번 WAF 업데이트에는 기존 Template Injection Beta 규칙을 원래 규칙으로 통합하는 변경도 포함됐다.
해당 Beta 규칙은 비활성화된 상태를 유지하며 기존 규칙에 병합된다.
이는 새로운 취약점 탐지 규칙과는 별개의 Ruleset 정리 작업이다.
이번 발표에서 중요한 변화는 이미 공개된 웹 애플리케이션 취약점에 대한 Cloudflare의 방어 범위가 확대됐다는 점이다.
취약점 자체의 발표일과 WAF 규칙이 추가된 날짜를 구분해야 하며, WAF 사용 여부와 관계없이 영향을 받는 프레임워크 및 서버 제품의 보안 업데이트가 필요하다.
📚 추천 글
Chrome vs. Safari vs. Firefox: AI Debugging MCPs Tested
- 저자: Chizaram Ken / LogRocket
- 게시일: 2026-10-12
- 원문: https://blog.logrocket.com/mcps-chrome-safari-firefox/
Chrome, Safari, Firefox의 브라우저 디버깅용 MCP 서버가 실제 React 애플리케이션의 오류를 얼마나 정확하게 분석하는지 비교한 실험이다.
단순히 세 브라우저의 개발자 도구 기능을 나열하지 않고, 의도적으로 오류를 삽입한 React 애플리케이션을 만들고 동일한 증상을 전달해 에이전트가 원인을 찾아가는 과정을 기록했다.
특히 React의 불필요한 리렌더링 문제에서 브라우저별 디버깅 도구가 어떤 정보를 제공하는지 살펴본 부분이 흥미롭다.
네 가지 오류를 삽입한 React 애플리케이션
저자는 Vite 8, React 19.3, TypeScript를 이용해 작은 대시보드를 만들었다.
애플리케이션은 프로젝트 카드 8개, 메모 입력창, 대규모 활동 테이블로 구성된다.
처음에는 300개의 행으로 테스트했지만 성능 문제가 충분히 드러나지 않아 테이블을 1,500개 행으로 늘렸다.
실험을 위해 네 가지 오류를 의도적으로 만들었다.
첫 번째는 API 요청의 Waterfall 문제다.
프로젝트 8개의 통계를 가져오는 작업에서 await를 반복문 안에 배치해 요청을 순차적으로 실행하도록 했다.
병렬 실행이 가능한 요청이 순차적으로 처리되면서 약 350ms면 완료할 수 있는 작업에 3초가 걸리는 상황이다.
두 번째는 레이아웃 오류다.
매우 긴 프로젝트 이름에 공백이 없어 텍스트가 줄바꿈되지 않고, 카드가 줄어들지 못하면서 화면 밖으로 넘치는 문제다.
세 번째는 콘솔 오류다.
여덟 개의 API 응답 가운데 하나만 다른 데이터 구조를 반환하지만, 화면에는 일반적인 컴포넌트 오류로 나타나도록 했다.
네 번째가 가장 복잡한 React 리렌더링 문제다.
메모 입력창에 문자를 입력하면 활동 테이블의 많은 행이 다시 렌더링되면서 화면이 느려진다.
이 오류의 실제 원인은 React Context에 서로 다른 성격의 상태를 함께 저장한 구조다.
애플리케이션의 Context에는 사용자 정보, 테마, 메모 초안, 메모 수정 함수가 들어 있다.
활동 테이블의 각 행은 이 Context에서 테마 정보만 사용한다.
하지만 메모를 입력할 때마다 같은 Context의 값이 변경된다.
React Context를 구독하는 컴포넌트는 자신이 사용하지 않는 값 때문에도 Context 변경의 영향을 받을 수 있다.
테이블 행에는 이미 React.memo가 적용돼 있지만, Context 업데이트는 React.memo로 막을 수 없다.
또한 각 행은 렌더링 과정에서 11개의 Intl Formatter를 생성한다.
1,500개의 행이 모두 다시 렌더링되면 키 입력 한 번에 약 16,500개의 Formatter가 만들어진다.
저자는 이 현상을 정확하게 설명할 수 있는지에 주목했다.
실험 환경을 통제하는 방식
실험에는 Claude Code 2.1.97과 Opus 4.6을 사용했다.
다만 에이전트가 자유롭게 프로젝트 파일을 읽거나 Shell 명령을 실행하면 브라우저 디버깅 도구의 차이를 평가하기 어렵다.
따라서 저자는 파일 접근과 Shell 도구를 비활성화하고 브라우저 MCP 도구만 사용할 수 있도록 제한했다.
에이전트에게는 문제의 증상만 한 문장으로 전달했다.
예를 들어 리렌더링 문제에서는 메모 입력창에서 타이핑하면 화면이 느리다는 정보만 제공했다.
각 실행이 끝나면 에이전트는 원인과 근거, 도구를 이용해 확인하지 못한 정보를 설명해야 했다.
저자는 실험 전에 각 오류에 대한 정답 기준도 작성했다.
다만 모든 조합을 충분히 반복하지는 못했다.
대부분의 테스트는 한두 번만 실행됐기 때문에 결과는 통계적으로 안정적인 Benchmark보다 구체적인 디버깅 사례를 비교한 실험 보고서에 가깝다.
일반적인 브라우저 오류에서는 차이가 크지 않았다
네트워크 요청, 레이아웃, 콘솔 오류는 세 브라우저 모두 원인을 찾아냈다.
실험에서 기록한 대표적인 소요 시간은 다음과 같다.
| 오류 | Chrome | Firefox | Safari |
|---|---|---|---|
| API 요청 Waterfall | 123초 | 59초 | 108초 |
| 레이아웃 깨짐 | 104초 | 78초 | 86초 |
| 콘솔 오류 | 68초 | 67초 | 82초 |
세 브라우저 모두 문제를 발견할 수 있었기 때문에 저자는 단순한 시간 차이만으로 우열을 판단하지 않는다.
실험 횟수가 적고 같은 모델이 매번 동일하게 행동한다는 보장도 없기 때문이다.
오히려 중요한 것은 에이전트가 어떤 정보에 접근할 수 있었는지였다.
Firefox는 디버거를 이용해 실제 소스코드를 확인했다.
Safari는 초기 네트워크 기록이 비어 있다는 사실을 발견하고 페이지를 다시 불러와 요청을 수집했다.
Chrome은 1,500개 행이 포함된 페이지의 스냅샷을 요청했다가 56만 자가 넘는 결과를 받아 처리에 어려움을 겪었다.
이는 에이전트에게 많은 정보를 제공하는 것이 항상 유리하지는 않다는 점을 보여준다.
React 리렌더링 오류에서는 원인 분석의 정확도가 달랐다
가장 복잡한 React 오류에서는 브라우저별 차이가 크게 나타났다.
첫 번째 비교 결과는 다음과 같다.
| 브라우저 | 분석 결과 | 소요 시간 | 사용 토큰 |
|---|---|---|---|
| Firefox | 정확한 원인 발견 | 76초 | 225,095 |
| Chrome | 관련 영역은 발견했지만 세부 원인 오류 | 398초 | 500,693 |
| Safari | 첫 실행에서 분석 완료 실패 | - | - |
Firefox는 개발자 도구의 소스 조회 기능을 이용해 AppContext.tsx, ActivityTable.tsx, NotesBox.tsx를 읽었다.
이후 메모 상태와 테마가 같은 Context에 들어 있고, 해당 Context를 읽는 테이블 행이 업데이트된다는 사실을 확인했다.
또한 테이블 행에 이미 React.memo가 적용돼 있어도 Context 변경으로 인한 렌더링을 막을 수 없다는 점까지 설명했다.
반면 Chrome은 React의 내부 Fiber Tree를 탐색하며 관련 영역을 발견했지만, 테이블에 memo가 누락됐다고 판단했다.
실제 코드에는 이미 memo가 있었으므로 해당 조언을 그대로 적용해도 문제가 해결되지 않았을 것이다.
저자는 이 차이를 소스코드에 접근해 원인을 검증했는지, 런타임 내부 상태를 관찰한 뒤 추정했는지의 차이로 해석한다.
React DevTools MCP를 추가한 경우
이후 Chrome에 react-devtools-cdt-mcp를 연결해 같은 오류를 다시 분석했다.
이 도구는 React 컴포넌트 트리, Owner Stack, Profiler 정보 등에 접근할 수 있도록 한다.
비교 결과는 다음과 같았다.
| 환경 | 결과 | 소요 시간 |
|---|---|---|
| Chrome 기본 MCP | 세부 원인 오류 | 398초 |
| Chrome + React DevTools MCP | 정확한 원인 발견 | 144초 |
| Firefox 기본 개발자 MCP | 정확한 원인 발견 | 76초 |
React DevTools를 추가한 Chrome은 어떤 컴포넌트가 Context를 구독하는지 확인하고, React.memo가 Context 업데이트를 막지 못한다는 사실도 정확하게 설명했다.
그러나 도구가 추가됐다고 입력 데이터의 양이 줄어드는 것은 아니었다.
Profiler가 반환한 보고서는 약 210만 자에 달했다.
저자는 브라우저의 기능이 많아질수록 모델에 전달되는 도구 설명과 실행 결과도 커질 수 있다는 점을 지적한다.
이 실험에는 명확한 한계가 있다.
각 조건의 실행 횟수가 매우 적고, 특정 모델과 특정 MCP 서버 버전을 사용했으며, 테스트 애플리케이션도 하나뿐이다.
따라서 Firefox가 모든 React 오류에서 Chrome보다 우수하다고 결론 내릴 수는 없다.
그럼에도 AI 디버깅의 정확도가 모델 자체뿐 아니라 도구가 제공하는 소스 접근 범위와 결과 데이터의 크기에 크게 영향을 받는다는 점을 구체적으로 보여준다.
Filter, reshape, and redact logs before export with Transformers
- 저자: Rob Taylor, Sahidya Devadoss / Cloudflare
- 게시일: 2026-10-12
- 원문: https://blog.cloudflare.com/transformers-on-logpush/
Cloudflare가 초당 수억 건 규모의 로그를 처리하는 Logpush 파이프라인 안에 SQL 기반 데이터 변환 기능을 통합한 과정을 설명한 기술 글이다.
Logpush Transformers는 이전에 정식 출시된 기능이며, 이번 글은 해당 기능의 신규 출시 공지가 아니다.
대신 기존 로그 전달 구조에 데이터 변환을 추가하면서 어떤 아키텍처를 선택했고 어떤 방식은 포기했는지를 자세히 설명한다.
로그를 외부로 전송하기 전에 가공해야 하는 이유
일반적인 웹 서비스의 로그 파이프라인은 여러 단계를 거친다.
애플리케이션이나 CDN이 요청 로그를 생성하면 이를 외부 저장소나 분석 서비스로 전송한다.
이후 로그 수집 시스템에서 불필요한 데이터를 제거하거나 필드 이름을 통일하고, 필요한 데이터를 검색할 수 있도록 인덱싱한다.
문제는 이 과정에서 나중에 버릴 데이터까지 전송하고 저장하는 비용이 발생한다는 점이다.
예를 들어 서비스의 Health Check 요청이나 내부 모니터링 요청은 로그 분석에서 제외하고 싶을 수 있다.
또한 IP 주소와 같은 개인정보를 외부 분석 서비스에 전달하기 전에 일부 마스킹해야 할 수도 있다.
여러 CDN을 함께 사용하는 조직이라면 각 공급업체의 로그 필드 이름을 공통 형식으로 변환하는 작업도 필요하다.
기존에는 이러한 처리를 위해 별도의 로그 처리 서비스를 운영하는 경우가 많았다.
하지만 변환 서비스가 하나 추가되면 운영해야 하는 서버, 오류 처리, 확장 정책, 모니터링 대상도 증가한다.
Cloudflare가 선택한 방식은 Logpush가 로그를 전송하기 전에 SQL을 실행하도록 만드는 것이다.
SQL 기반 변환 모델
Transformers는 특정 로그 데이터셋을 대상으로 SQL을 작성하는 구조다.
예를 들어 HTTP 요청 로그에서 다음 작업을 수행할 수 있다.
- 특정 호스트나 경로의 로그 제외
- 사용하지 않는 필드 제거
- 필드 이름 변경
- IP 주소의 일부 마스킹
- 새로운 계산 필드 추가
- 여러 필드를 중첩된 객체로 구성
SQL의 WHERE 절은 어떤 로그를 전송할지 결정한다.
SELECT 절은 실제로 전송할 로그의 필드와 구조를 정의한다.
변환이 끝난 결과는 NDJSON 형식으로 전송된다.
따라서 Transform 자체가 새로운 로그 파일 형식을 도입하는 것은 아니다.
중요한 변화는 로그를 외부에 전달하기 전에 데이터의 내용과 구조를 변경할 수 있다는 것이다.
초당 1억 건 이상의 로그를 처리하는 파이프라인
Cloudflare의 전체 데이터 파이프라인은 초당 10억 건 이상의 이벤트를 수집한다.
그중 Logpush 전달 서비스인 Bufstream은 초당 1억 1,000만 건 이상의 로그 레코드를 고객의 목적지로 전송한다고 설명한다.
이는 Cloudflare가 공개한 자체 운영 수치다.
Bufstream은 수백 개의 Kubernetes Pod에서 실행된다.
각 Pod는 Cloudflare의 분산 메시지 브로커인 Buftee에서 처리할 데이터 구간을 가져온다.
이 구조는 Logpush 작업의 규모에 따라 처리량을 분산할 수 있도록 설계됐다.
여기에 SQL 변환을 추가하려면 기존 처리량을 크게 떨어뜨리지 않으면서 새로운 장애 요인도 최소화해야 했다.
처음에는 WebAssembly와 Workers를 검토했다
Cloudflare의 초기 설계는 사용자가 정의한 변환 코드를 WebAssembly로 실행하고, 이를 Workers 환경에서 처리하는 방식이었다.
이 구조는 격리된 실행 환경을 제공하고 변환 코드를 별도로 배포할 수 있다는 장점이 있다.
그러나 로그를 전달하는 과정에서 Workers를 호출하려면 추가적인 네트워크 요청이 발생한다.
로그 전송 파이프라인은 매우 많은 데이터를 지속적으로 처리해야 한다.
이때 외부 서비스 호출이 추가되면 네트워크 지연, 타임아웃, 재시도가 전체 처리량에 영향을 줄 수 있다.
특히 로그 변환 서비스가 잠시 응답하지 않으면 정상적인 로그 전달 작업까지 중단될 가능성이 있다.
Cloudflare는 이러한 이유로 원격 실행 방식 대신 로그 전달 프로세스 안에서 변환 코드를 실행하는 구조를 선택했다.
Apache DataFusion과 Apache Arrow를 선택한 이유
최종 구현에는 Rust 기반 SQL 엔진인 Apache DataFusion이 사용됐다.
DataFusion은 Apache Arrow의 컬럼형 데이터 구조를 활용해 SQL을 실행한다.
일반적인 행 기반 처리에서는 각 로그 레코드를 하나씩 읽고 필드를 검사해야 한다.
반면 컬럼형 처리에서는 동일한 필드의 값들이 연속된 데이터 구조로 표현되므로 필터링이나 계산 작업을 효율적으로 수행할 수 있다.
Cloudflare의 로그 전달 서비스는 Go로 작성돼 있다.
따라서 Go 기반 서비스와 Rust 기반 SQL 엔진을 연결하는 과정이 필요했다.
Cloudflare는 이 경계에서 Arrow IPC 형식을 사용한다.
Go는 로그 데이터를 Arrow RecordBatch로 구성한 뒤 Rust로 전달한다.
Rust의 DataFusion은 해당 데이터를 읽어 SQL을 실행하고 변환된 결과를 반환한다.
이 과정에서는 Go와 Rust의 메모리 소유권도 구분한다.
Go가 전달한 입력 버퍼는 Rust 호출이 실행되는 동안에만 참조한다.
반면 Rust가 생성한 출력 버퍼는 Go가 자신의 메모리로 복사한 뒤 Rust 쪽 할당을 명시적으로 해제한다.
이러한 처리는 서로 다른 언어의 런타임 사이에서 메모리 수명주기가 충돌하는 문제를 줄이기 위한 설계다.
SQL 실행 계획을 재사용하는 방식
사용자가 Transformer를 저장하면 Cloudflare는 해당 SQL을 R2에 저장한다.
Bufstream은 SQL을 가져온 뒤 DataFusion의 실행 계획으로 변환한다.
중요한 점은 로그 배치마다 SQL을 새로 분석하지 않는다는 것이다.
같은 작업 구간에 대해서는 준비된 실행 계획을 여러 배치에서 재사용한다.
이렇게 하면 반복적인 SQL 파싱과 계획 생성 비용을 줄일 수 있다.
Cloudflare의 자체 Benchmark에서는 5,000개 행으로 구성된 합성 HTTP 로그 배치를 처리하는 데 약 3ms가 걸렸다.
이를 단순 환산하면 초당 약 160만 개 행을 처리하는 수준이다.
다만 이는 하나의 Go Goroutine에서 수행한 특정 배치와 SQL에 대한 자체 측정이다.
실제 모든 Transformer 작업이 같은 처리량을 달성한다는 의미는 아니다.
실제 처리량은 SQL의 복잡도, 필터 조건, 로그 크기, 데이터 유형, 동시 작업 수에 따라 달라질 수 있다.
변환에 실패하면 원본 로그를 전송하지 않는다
이 글에서 가장 중요한 설계 선택 중 하나는 Fail Closed 정책이다.
Transformer 실행에 실패했다고 해서 변환하지 않은 원본 로그를 그대로 외부로 전송하지 않는다.
이유는 개인정보와 데이터 계약 때문이다.
예를 들어 Transformer가 IP 주소를 마스킹하도록 설정돼 있다고 가정해 보자.
변환에 실패했을 때 원본 로그를 전송하면 외부 서비스에 전달돼서는 안 되는 전체 IP 주소가 노출될 수 있다.
또는 downstream 시스템이 특정 구조의 로그만 처리하도록 설계돼 있다면 변환되지 않은 로그가 들어오면서 파이프라인이 실패할 수 있다.
Cloudflare는 이러한 상황을 방지하기 위해 변환 실패 시 해당 배치를 전송하지 않는다.
또한 작업의 전달 위치를 앞으로 이동시키지 않고 재시도한다.
반복적으로 실패하는 작업은 재시도 간격을 늘려 다른 정상 작업의 처리량에 영향을 주지 않도록 한다.
장기간 실패하는 작업은 자동으로 비활성화될 수 있다.
이러한 설계는 로그 전달의 가용성을 일부 희생하더라도 설정된 데이터 변환과 개인정보 보호 정책을 우선한다는 선택이다.
Transformers는 Free, Pro, Business, Enterprise 요금제에서 제공된다.
이 글은 새로운 SQL 기능을 소개하는 데 그치지 않고 고처리량 데이터 파이프라인에서 네트워크 호출, 언어 간 메모리 관리, 실행 계획 캐싱, 장애 처리 정책을 어떻게 선택했는지를 실제 구현으로 설명한다.
Build a Secure TypeScript MCP Client with Cross App Access (XAA)
- 저자: Akanksha Bhasin / Okta Developer
- 게시일: 2026-10-09
- 원문: https://developer.okta.com/blog/2026/10/09/typescript-mcp-sdk-cross-app-access
- 예제 코드: https://github.com/oktadev/okta-xaa-typescript-mcp-sdk-example
기업용 웹 애플리케이션과 AI 에이전트가 외부 시스템에 접근할 때 서비스 계정 하나에 모든 권한을 부여하지 않고 실제 사용자의 신원을 다른 시스템까지 전달하는 방법을 설명한 글이다.
OAuth 2.0, OpenID Connect, MCP를 결합한 Cross App Access(XAA) 흐름을 TypeScript 예제로 구현한다.
특히 여러 인증 서버가 관여하는 상황에서 각 토큰이 누구를 위한 것인지 구분하는 부분이 상세하다.
다른 애플리케이션에 접근할 때 발생하는 인증 문제
기업에서 사용하는 애플리케이션은 서로 독립적으로 운영되는 경우가 많다.
예를 들어 인사 관리 시스템에서 신규 직원의 온보딩 상태를 보여주려면 다른 업체가 제공하는 업무 관리 시스템의 데이터를 가져와야 할 수 있다.
이때 두 시스템은 서로 다른 도메인과 인증 서버를 사용한다.
일반적인 OAuth 연동에서는 사용자가 다른 애플리케이션에 접근할 때 별도의 동의 화면을 거쳐야 할 수 있다.
다른 방법으로는 조직이 하나의 서비스 계정을 만들어 여러 사용자의 요청을 대신 처리할 수도 있다.
하지만 서비스 계정에 지나치게 넓은 권한을 부여하면 실제 어떤 사용자가 데이터를 요청했는지 구분하기 어려워진다.
AI 에이전트에서는 이러한 문제가 더 복잡해진다.
하나의 사용자 요청이 문서 검색, 일정 조회, 업무 등록, 메시지 전송 등 여러 서비스의 도구 호출로 이어질 수 있기 때문이다.
저자가 설명하는 XAA는 기업의 Identity Provider가 사용자의 신원을 확인하고, 관리자 정책에 따라 다른 애플리케이션에 해당 사용자의 권한을 위임하는 구조다.
네 단계로 구성되는 인증 흐름
예제는 TypeScript와 Express를 사용해 직원 온보딩 대시보드를 구현한다.
웹 애플리케이션은 사용자를 로그인시킨 뒤 별도의 MCP 서버에서 업무 목록을 가져온다.
전체 인증 흐름은 네 단계다.
첫 번째 단계는 OpenID Connect 로그인이다.
사용자는 기업의 Identity Provider에서 로그인한다.
애플리케이션은 Authorization Code Flow와 PKCE를 이용해 사용자를 인증하고 ID Token을 발급받는다.
PKCE는 인증 과정에서 사용되는 Authorization Code가 탈취되더라도 공격자가 이를 임의로 교환하기 어렵게 만드는 보안 절차다.
두 번째 단계는 ID Token을 ID-JAG로 교환하는 과정이다.
ID-JAG는 Identity Assertion JWT Authorization Grant의 약자다.
애플리케이션은 OAuth 2.0 Token Exchange(RFC 8693)를 이용해 사용자의 ID Token을 새로운 위임 토큰으로 교환한다.
세 번째 단계에서는 ID-JAG를 대상 리소스의 Authorization Server에 전달한다.
이 과정에서는 JWT Bearer Grant(RFC 7523)를 사용한다.
Authorization Server는 ID-JAG와 사전에 설정된 권한 정책을 확인한 뒤 특정 리소스에 접근할 수 있는 Access Token을 발급한다.
네 번째 단계에서 애플리케이션은 해당 Access Token을 Bearer Token으로 사용해 실제 MCP 서버에 접근한다.
이렇게 하면 MCP 서버는 단순히 어떤 애플리케이션에서 요청이 왔는지만 확인하는 것이 아니라 어떤 사용자를 대신해 어떤 범위의 권한으로 요청했는지 확인할 수 있다.
ID-JAG에 포함되는 정보
ID-JAG는 서명된 JWT다.
예제에서 중요한 필드는 다음과 같다.
| 필드 | 의미 |
|---|---|
iss | ID-JAG를 발급한 Identity Provider |
sub | 요청의 주체인 사용자 |
aud | ID-JAG를 검증할 Authorization Server |
resource | 실제 접근하려는 MCP 서버 |
client_id | 리소스 Authorization Server에 등록된 애플리케이션 ID |
scope | 요청한 접근 권한 |
exp | 토큰 만료 시각 |
jti | 토큰의 고유 식별자 |
이 가운데 저자가 가장 강조하는 것은 aud와 resource의 차이다.
일반적인 Access Token에서는 aud가 실제 API 서버를 가리키는 경우가 많다.
그러나 ID-JAG의 aud는 최종 MCP 서버가 아니라 ID-JAG를 검증할 Authorization Server를 가리킨다.
반면 resource는 실제로 접근하려는 MCP 서버를 나타낸다.
Authorization Server는 Access Token을 발급할 때 이 resource 값을 Access Token의 aud로 사용한다.
따라서 두 값을 혼동하면 토큰 발급 과정이 실패하거나, 토큰을 발급받았더라도 실제 MCP 서버가 요청을 거부할 수 있다.
ID-JAG는 짧은 수명을 가진다.
예제에서는 약 5분의 만료 시간이 적용되며, 고유한 jti를 통해 토큰 재사용 공격을 제한한다.
또한 JWT Header에 oauth-id-jag+jwt라는 토큰 유형을 명시한다.
이를 통해 일반적인 ID Token을 ID-JAG처럼 사용하는 혼동을 방지할 수 있다.
TypeScript MCP SDK의 인증 처리
예제에서는 @modelcontextprotocol/client의 Cross App Access 기능을 사용한다.
핵심 구성요소는 CrossAppAccessProvider다.
이 Provider는 단순히 Access Token을 저장하는 객체가 아니다.
MCP 서버에 요청했을 때 인증이 필요하다면 서버가 반환한 401 응답을 확인하고, 보호된 리소스의 메타데이터를 조회해 Authorization Server를 발견할 수 있다.
그다음 애플리케이션이 제공한 콜백을 통해 ID-JAG를 가져오고, 이를 Access Token으로 교환한 뒤 요청을 다시 실행한다.
즉 애플리케이션이 인증 서버의 URL과 토큰 교환 과정을 매번 직접 구현하지 않도록 한다.
저자는 이 과정에서 발생할 수 있는 실제 오류도 설명한다.
예를 들어 Authorization Server가 요구하는 Client Authentication 방식과 SDK의 기본값이 다르면 인증에 실패할 수 있다.
또한 필요한 Scope가 Access Token에 포함되지 않으면 MCP 연결 자체는 성공했더라도 실제 데이터를 읽는 작업은 실패할 수 있다.
따라서 토큰이 발급됐다는 사실만으로 필요한 접근 권한까지 정상적으로 부여됐다고 판단해서는 안 된다.
서버에 토큰을 보관하는 이유
예제는 사용자의 ID Token을 서버 측 세션에 저장한다.
브라우저에는 실제 MCP 서버에 접근하기 위한 장기적인 인증 정보나 애플리케이션의 Client Secret을 직접 노출하지 않는다.
웹 애플리케이션은 필요한 경우 서버에서 인증 토큰을 갱신하거나 새로 교환한다.
저자는 운영 환경에서 Session Cookie에 HttpOnly, Secure, 적절한 SameSite 설정을 적용해야 한다고 설명한다.
또한 예제 코드에서 사용하는 메모리 기반 세션 저장소는 학습용으로만 적합하다고 명시한다.
서버가 재시작되면 세션이 사라질 수 있고 여러 서버 인스턴스 사이에서 세션을 공유할 수 없기 때문이다.
운영 환경에서는 별도의 세션 저장소가 필요하다.
토큰을 로그에 그대로 기록하거나 사용자 화면에 노출하지 않는 것도 중요하다.
예제에는 인증 흐름을 이해하기 위한 토큰 확인 화면이 있지만, 이는 학습과 로컬 디버깅을 위한 것이다.
아직 안정화되지 않은 SDK
이 글에서 사용하는 MCP TypeScript SDK는 2.0.0-alpha.2다.
정식 안정 버전이 아니라 사전 공개 버전이므로 이후 API가 변경될 수 있다.
또한 XAA가 활용하는 Identity Assertion Authorization Grant 규격 역시 IETF에서 발전 중인 문서다.
따라서 현재 예제의 API와 인증 흐름을 모든 MCP 서버에서 즉시 사용할 수 있다고 가정해서는 안 된다.
실제로 사용하려는 Identity Provider와 Authorization Server가 해당 위임 방식을 지원하는지도 확인해야 한다.
이 글은 단순한 MCP 연결 튜토리얼보다 서로 다른 인증 체계를 가진 애플리케이션 사이에서 사용자 신원과 접근 권한을 안전하게 전달하는 설계에 초점을 맞춘다.
특히 기업용 SaaS와 AI 에이전트에서 인증과 인가의 책임을 어디에 둘 것인지 이해하는 데 참고할 만하다.