Gemini CLI가 MCP를 믿기 전에 막은 두 경로: OAuth SSRF와 작업공간 신뢰
원격 MCP 서버를 연결하는 일과 내려받은 저장소에서 코딩 에이전트를 실행하는 일에는 공통점이 있다. 둘 다 외부에서 건네받은 문자열이 네트워크 요청이나 프로세스 실행으로 바뀔 수 있다. 화면에 승인 버튼이 있다는 것만으로는 충분하지 않다. 승인 전에 어떤 설정을 읽었고, 그 설정이 어디로 요청을 보내며, 제한 모드가 실제 실행 경로까지 전달되는지 확인해야 한다.
Google이 9월 2일 공개한 Gemini CLI v0.59.0-preview.0은 이 경계에서 생기는 두 문제를 고쳤다. 하나는 MCP OAuth 메타데이터가 로컬·사설 네트워크로 요청을 돌릴 수 있었던 SSRF 경로다. 다른 하나는 제한된 작업공간에서도 저장소가 정의한 MCP 서버와 도구 설정이 허용 쪽으로 흘러갈 수 있었던 신뢰 판정 경로다. 둘을 함께 보면 에이전트 보안에서 "기본 거부"를 코드 어디에 놓아야 하는지 선명해진다.
요약: 연결 전 검증과 실행 전 필터가 따로 필요하다
첫 번째 수정인 PR #29081은 OAuth discovery, 동적 클라이언트 등록, 토큰 교환·갱신 전에 URL을 검사한다. 원격 endpoint에는 HTTPS를 요구하고, MCP 서버 origin과 resource_metadata의 origin을 대조한다. IP가 직접 적혀 있든 DNS 이름 뒤에 숨어 있든 사설망, loopback, link-local, 클라우드 instance metadata 주소를 거부한다. 로컬 MCP 서버에 연결할 때만 localhost 계열 HTTP를 허용한다.
두 번째 수정인 PR #29099는 환경의 제한 신호를 로컬 설정보다 먼저 평가한다. GEMINI_RESTRICTED_MODE=true 또는 GEMINI_CLI_TRUST_WORKSPACE=false이면 신뢰되지 않은 작업공간으로 판정한다. A2A 서버는 이 판정을 받은 뒤 저장소가 지정한 mcpServers, policyPaths, adminPolicyPaths, tools, telemetry를 설정 객체에서 제거한다.
둘은 같은 패치가 아니다. 네트워크 목적지를 검사하는 층과 저장소 설정의 실행 권한을 결정하는 층이 따로 있다. OAuth URL 검증만으로 악성 로컬 명령을 막을 수 없고, 작업공간 신뢰만으로 허용된 원격 MCP 서버가 돌려준 악성 metadata URL을 막을 수도 없다.
MCP OAuth에서 문자열이 내부 요청으로 바뀌는 과정
원격 MCP 서버가 인증을 요구하면 클라이언트는 WWW-Authenticate 헤더의 resource_metadata를 따라 OAuth 보호 자원 메타데이터를 찾을 수 있다. 여기서 얻은 authorization_servers와 registration endpoint, token endpoint를 다시 호출한다. 이 값들을 그대로 신뢰하면 공격자가 CLI를 대신해 127.0.0.1, 사설망 서비스, 169.254.169.254 같은 클라우드 metadata endpoint에 요청을 보내게 만들 수 있다.
PR #29081은 공통 validateOAuthEndpointUrl 관문을 넣었다. 프로토콜은 HTTP와 HTTPS만 허용하되 원격 대상은 HTTPS여야 한다. resource_metadata는 MCP 서버와 origin이 같아야 한다. hostname은 비동기 DNS 조회 후 반환된 모든 주소를 검사하며, 사설 IPv4·IPv6, loopback, link-local, benchmark, multicast와 broadcast 범위를 막는다. 동적 등록 POST와 authorization code 교환, refresh token 요청도 실제 fetch 직전에 같은 검증을 거친다.
OAuth discovery는 다음 endpoint를 계속 가리킨다. 최초 metadata뿐 아니라 등록과 token 요청 직전에도 목적지를 다시 검사해야 한다. 출처: github.com/google-gemini/gemini-cli/pull/29081.
여기서 중요한 것은 최초 URL만 검사하지 않았다는 점이다. OAuth는 discovery 결과가 다음 endpoint를 가리키는 연쇄 프로토콜이다. 입구 하나를 검사해도 registration이나 token URL이 별도 검증 없이 요청되면 우회 경로가 남는다. 반대로 로컬 개발까지 전부 막지는 않았다. 사용자가 처음부터 로컬 MCP 서버에 연결하는 경우에만 loopback HTTP 예외를 전달한다. 원격 서버가 로컬 주소로 방향을 바꾸는 경우에는 같은 예외를 주지 않는다.
작업공간 신뢰는 설정 로딩 뒤가 아니라 실행 전에 닫혀야 한다
저장소의 .gemini/settings.json은 편리하지만 단순한 문서가 아니다. mcpServers의 stdio transport는 프로세스를 시작할 수 있고, tools는 사용 가능한 도구를 바꾸며, telemetry는 데이터가 향하는 곳을 지정할 수 있다. 따라서 저장소를 열어 코드를 읽는 행위와 저장소가 제안한 실행 구성을 받아들이는 행위를 같은 신뢰 결정으로 묶으면 위험하다.
공식 Trusted Folders 문서는 신뢰되지 않은 폴더에서 workspace settings와 .env를 무시하고, MCP 서버 연결·custom command·자동 승인 등을 제한한다고 설명한다. PR #29099는 그 정책이 A2A 서버의 실제 설정 조립 과정에서도 유지되도록 고쳤다. 환경 변수가 "제한됨"을 말하면 folder trust 옵션의 permissive fallback보다 먼저 false를 반환한다. 판정값이 없을 때도 Config.isTrustedFolder()는 true를 추측하지 않고 false로 닫힌다.
신뢰되지 않은 작업공간에서는 저장소 설정을 읽었다는 사실이 실행 권한으로 이어지지 않도록 영향 있는 설정을 제거한다. 출처: github.com/google-gemini/gemini-cli/pull/29099.
그 뒤가 더 중요하다. trusted=false를 로그에만 남기지 않고 실행 영향을 가진 다섯 설정을 undefined로 바꾼다. 특히 저장소가 지정한 MCP 서버를 걸러내므로 A2A 서버 시작 과정에서 의도하지 않은 child process가 뜨는 경로를 차단한다. policy와 admin policy 경로, 도구, telemetry도 함께 제거해 "MCP만 막았지만 다른 설정이 권한을 다시 넓히는" 틈을 줄였다.
실전 적용: 네 개의 경계를 점검한다
첫째, 설치 버전을 고정한다. 이번 수정은 v0.59.0-preview.0 release에 포함됐지만 preview 채널이다. 릴리스 페이지와 실제 gemini --version을 대조하고, 팀 배포 전에 사용하는 transport와 인증 방식을 작은 테스트 환경에서 재현한다.
둘째, CI에서는 신뢰를 편의상 켜지 않는다. 공식 문서는 headless 환경에서 --skip-trust 또는 GEMINI_CLI_TRUST_WORKSPACE=true로 우회할 수 있다고 안내한다. 이것은 오류를 없애는 옵션이지 안전성을 추가하는 옵션이 아니다. 저장소 출처와 커밋을 별도로 검증한 job에서만 쓰고, 외부 pull request에는 restricted mode와 sandbox를 유지한다.
셋째, MCP 서버는 전송 방식별로 나눈다. stdio 서버는 로컬 프로세스 실행 경로이고, SSE·Streamable HTTP 서버는 원격 네트워크와 OAuth 경로다. 이름이 같은 MCP 서버라도 위험면은 다르다. command·args·env, endpoint origin, redirect와 token endpoint를 각각 검토한다.
검토표에는 최종 주소만 적지 말고 주소가 만들어진 출처도 남긴다. 사용자가 직접 고정한 URL인지, WWW-Authenticate가 준 metadata인지, discovery 문서가 준 token endpoint인지 구분해야 한다. 같은 HTTPS 주소라도 신뢰 경로가 다르면 변경을 승인할 사람과 회귀 테스트가 달라진다. 설정을 합친 뒤의 결과만 저장하면 어느 입력이 실행 권한을 넓혔는지 되짚기 어렵다.
넷째, 회귀 테스트를 "차단 결과"로 쓴다. 원격 MCP가 loopback registration URL을 주면 OAuthSecurityError가 나는지, DNS가 사설 IP를 반환하면 요청 전에 멈추는지, restricted mode에서 repository mcpServers와 tools가 제거되는지 확인한다. 단순히 인증 성공만 테스트하면 보안 관문이 빠져도 알아채기 어렵다.
범위와 주의점
이번 release는 두 수정이 포함됐다는 사실과 코드 변경, 단위 테스트를 공개한다. 그러나 실제 공격이 관찰됐는지, 영향받은 배포가 몇 개인지, CVE가 배정됐는지는 release와 두 PR에서 확인되지 않는다. 그래서 이를 "악용된 취약점"이나 "전체 MCP 보안 해결"로 부르면 근거보다 세다.
DNS 조회 뒤 연결 시점까지 주소가 바뀌는 모든 형태를 이 글만으로 완전히 배제할 수도 없다. PR은 비동기 DNS 해석 결과를 검사한다고 설명하고 테스트를 추가했지만, 이 검토에서는 별도 공격 환경을 만들어 독립 재현하지 않았다. 또한 Trusted Folders 기능은 공식 문서상 기본 비활성화다. 새 fail-closed 경로가 들어갔다고 해서 모든 사용자의 폴더 신뢰 기능이 자동으로 켜지는 것은 아니다.
그래도 설계 원칙은 분명하다. 외부 문자열을 네트워크 요청으로 바꾸기 직전에 목적지를 다시 검증하고, 저장소 설정을 실행 구성으로 바꾸기 직전에 신뢰 판정을 다시 적용해야 한다. 정책 문구보다 마지막 fetch와 child process 생성 앞의 검사가 더 강한 증거다.
참고 자료
- https://github.com/google-gemini/gemini-cli/releases/tag/v0.59.0-preview.0
- https://github.com/google-gemini/gemini-cli/pull/29081
- https://github.com/google-gemini/gemini-cli/pull/29099
- https://geminicli.com/docs/cli/trusted-folders/
- https://geminicli.com/docs/tools/mcp-server/
- https://www.rfc-editor.org/rfc/rfc9728.html#section-7.7
- https://www.rfc-editor.org/rfc/rfc8414.html


댓글
댓글 쓰기