flawopen.com/Teardowns/cve-2026-104873-langgraph-sdk-auth-action-bypass
LangGraph CVE-2026-104873: 권한 우회
LangGraph SDK 0.1.45–0.4.3은 세 Python 데코레이터의 action 필터를 무시했습니다. 0.4.4로 업그레이드하세요.[1]
건물 안내 데스크는 택배만 받아야 하지만 안내 시스템은 모든 방문객을 그곳으로 보냅니다. 입장 여부만 확인하면 다른 데스크가 잠갔을 방에도 들어갈 수 있습니다. SDK는 리소스 핸들러 등록 때 actions=를 무시해 읽기, 수정, 삭제 요청도 전달할 수 있었습니다. 핸들러가 권한을 직접 확인하지 않을 때에만 위험합니다.[1]
핵심 개념 및 용어
CWE-863- 잘못된 권한 부여: 검사가 의도한 권한을 적용하지 못합니다.[1]
프레임워크 매핑
- 이 가이드에서 설명하는 약점의 CWE 참조: CWE-863
- OWASP API Security Top 10 — API5:2023 기능 수준 권한 부여 오류
근본 원인 분석 (Root Cause)
0.1.45–0.4.3은 actions=를 받으면서 핸들러를 리소스 와일드카드 *에 등록했습니다. LangGraph는 일반 대체 핸들러보다 먼저 선택할 수 있었습니다. 함수가 예정된 작업만 허가하면 다른 작업도 대체 검사 없이 전달될 수 있습니다. 앱이 생략된 검사에 의존하고 핸들러가 작업과 소유권을 확인하지 않을 때에만 타인 리소스 접근이 가능합니다. 0.4.4는 목록을 검증하고 작업별로 등록하며, 인수 생략 시 와일드카드를 유지합니다. 권고는 CWE-863, CVSS 4.0 7.6 High입니다. 설정에 따른 권한 결함이며 인증 없는 우회나 모든 배포의 문제는 아닙니다. 영향받는 데코레이터는 다음과 같습니다: @auth.on.threads / @auth.on.assistants / @auth.on.crons. 수정의 중요한 한계: SDK 변경은 선언된 작업으로 등록 범위를 좁히지만, 애플리케이션이 전역 기본 거부 핸들러를 설정하지 않으면 일치하지 않는 사용자 지정 인증 경로는 계속 허용됩니다. 0.4.4 변경 로그에 따르면 langgraph-api 0.10+는 시작 시 누락된 경로를 경고합니다. 이 경고는 탐지용이며 접근을 차단하지 않습니다.[1], [2], [5]
단계별 공격 실행 흐름
소스 코드 비교: 취약한 구현 vs 보안 패치
취약한 발췌는 이전 동작을 보여줍니다. _ResourceOn.__call__은 actions=와 무관하게 *를 사용했습니다. 수정은 목록을 검증하고 작업별로 등록합니다. 전체 모듈이 아닌 발췌입니다. SDK를 업그레이드하고 핸들러에서도 작업과 소유자를 확인하세요. 소스 코드 위치: libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]
소스 코드 발췌: 작업별 핸들러 등록
def __call__(self, fn=None, *, resources=None, actions=None):
if fn is not None:
_validate_handler(fn)
return typing.cast("_ActionHandler", _register_handler(
self.auth, self.resource, "*", fn
))
def decorator(handler):
_validate_handler(handler)
return typing.cast("_ActionHandler", _register_handler(
self.auth, self.resource, "*", handler
))
return decorator
def __call__(self, fn=None, *, resources=None, actions=None):
def decorator(handler):
_validate_handler(handler)
if resources is None:
resource_list = [self.resource]
elif isinstance(resources, str):
resource_list = [resources]
elif isinstance(resources, Sequence):
resource_list = list(resources)
else:
raise TypeError("resources must be a string or sequence of strings")
if resource_list != [self.resource]:
raise ValueError("use @auth.on(...) for other resources")
if actions is None:
action_list = ["*"]
elif isinstance(actions, str):
action_list = [actions]
elif isinstance(actions, Sequence):
action_list = list(actions)
else:
raise TypeError("actions must be a string or sequence of strings")
if not action_list:
raise ValueError("actions must not be empty")
if not all(isinstance(action, str) for action in action_list):
raise TypeError("actions must be strings")
valid_actions = {
value.action for value in vars(self).values()
if isinstance(value, _ResourceActionOn)
}
invalid_actions = (
sorted(set(action_list) - valid_actions) if actions is not None else []
)
if invalid_actions:
raise ValueError("invalid action for this resource")
if len(action_list) != len(set(action_list)):
raise ValueError("actions must not contain duplicates")
for action in action_list:
if (self.resource, action) in self.auth._handlers:
raise ValueError("handler already registered for action")
for action in action_list:
_register_handler(self.auth, self.resource, action, handler)
return handler
if fn is not None:
return decorator(typing.cast("_ActionHandler", fn))
return decorator
엔지니어링 및 시스템 보안 강화 체크리스트
- ✓SDK 0.1.45–0.4.3을 0.4.4 이상으로 업그레이드하세요.[1], [3]
- ✓
actions=를 쓰는 세 데코레이터를 검색하세요.[1], [2] - ✓작업 권한과 정확한 리소스 ID 접근을 확인하세요.[1]
- ✓선언 및 미선언 작업과 거부 경로를 테스트하세요.[2]
- ✓thread, assistant, cron의 사용자 간 접근을 시험하세요.[1]
- ✓시작 시 중복, 빈 값, 알 수 없는 목록을 거부하세요.[2]
- ✓로그인을 명시적 권한 부여로 혼동하지 마세요.
- ✓작업별 핸들러가 다루지 않는 모든 사용자 지정 인증 경로에 전역 기본 거부 핸들러를 설정하세요. langgraph-api 0.10+는 누락 경로를 경고하지만 시작 경고만으로 요청이 거부되지는 않습니다.[5], [1]