FlawOpen

flawopen.com/Teardowns/cve-2026-71433-langgraph-store-namespace-boundary

● CVE-2026-71433 · CVSS 5.3 · 보통
보안 연구 · FlawOpen

LangGraph CVE-2026-71433: namespace 경계 오류

LangGraph의 Postgres·SQLite 범위 읽기가 namespace 경계를 넘어 인접 tenant 데이터를 노출할 수 있었습니다. 두 checkpoint 패키지를 3.1.1로 업데이트하세요.

CVSS 벡터CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
💡 알기 쉬운 설명 (ELI5)

도서관 직원에게 ‘팀’으로 시작하는 이름의 폴더를 찾아 달라고 했다고 생각해 보세요. 목록이 문자열의 앞부분만 검색하면 ‘팀2’도 함께 나올 수 있습니다. LangGraph는 namespace 라벨을 점으로 연결했지만, prefix 검색에서 그 점을 경계로 요구하지 않았습니다. 수정 후에는 정확한 namespace 또는 실제 다음 세그먼트를 요구하므로 서로 다른 tenant의 폴더가 섞이지 않습니다.

핵심 개념 및 용어

Namespace
LangGraph Store가 항목을 묶는 데 사용하는 라벨 튜플입니다. 애플리케이션은 이를 사용자 또는 tenant 경계로 사용할 수 있습니다.
Prefix scope
하나의 namespace와 하위 항목을 읽도록 지정하는 범위입니다. 올바른 매칭은 세그먼트 경계를 지킵니다. foo는 foo.child와 일치하지만 이웃 namespace인 foobar와는 일치하지 않습니다.
SQL `LIKE` 패턴
%는 임의의 문자열, _는 한 글자와 일치하는 텍스트 패턴입니다. namespace 라벨을 문자 그대로 처리하려면 이 문자를 이스케이프해야 합니다.
CWE-863
잘못된 권한 부여: Store가 호출자가 읽을 수 없는 namespace의 레코드를 반환했습니다.

프레임워크 매핑

  • 이 가이드에서 설명하는 약점의 CWE 참조: CWE-863

근본 원인 분석 (Root Cause)

3.1.1 이전 Postgres 및 SQLite checkpoint store는 namespace 튜플을 점으로 연결된 문자열로 변환하고 LIKE '<namespace>%'로 검색 범위를 제한했습니다. LIKE는 namespace 구분자를 알지 못하므로 foo 검색이 이웃 namespace인 foobar에도 일치할 수 있었습니다. 허용된 %와 _ 라벨도 와일드카드로 동작할 수 있었습니다. list_namespaces에도 prefix와 suffix 관련 유사한 오류가 있었습니다. 일반적인 범위 읽기만으로 충분하며 잘못된 입력은 필요하지 않았습니다. 이 문제는 애플리케이션이 namespace를 tenant 분리에 의존할 때 Postgres/SQLite Store의 search 및 namespace 목록 읽기 경로에만 영향을 줍니다. get, put, delete는 정확한 동등 비교를 사용해 영향이 없고, InMemoryStore도 namespace 요소를 비교하므로 영향이 없습니다. 수정은 정확한 일치 또는 하위 세그먼트 앞의 점을 요구하고, LIKE 메타문자를 이스케이프하며, namespace 목록에 세그먼트 인식 패턴을 사용합니다. SQLite 검색은 LIKE의 ASCII 대소문자 비구분 동작도 피합니다. 권고문에는 실제 악용 증거가 없다고 명시되어 있습니다. 영향받는 패키지는 3.1.1에서 수정되었습니다.

CVE-2026-71433 배포 환경별 도달성 매트릭스

배포 환경 및 namespace 형식문서에 확인된 동작점검 조치
PostgresStore / AsyncPostgresStore 또는 SqliteStore / AsyncSqliteStore가 3.1.1 미만이고 tenant 분리가 namespace에 의존함.범위가 지정된 search 또는 list_namespaces는 레이블이 텍스트 접두사를 공유하거나 (foo / foobar, 1 / 12) % / _를 포함하면 인접 namespace까지 일치할 수 있습니다. SQLite 검색은 ASCII 대소문자만 다른 이름도 동일하게 취급했습니다.해당 checkpoint 패키지를 3.1.1 이상으로 업그레이드하세요. 운영 backend에서 인접 접두사, 와일드카드, 접미사 및 대소문자 차이를 테스트하세요.
3.1.1 미만 SQLite Store에서 namespace 레이블이 ASCII 대소문자만 다름.취약한 하위 namespace 검색은 대소문자를 구분하지 않는 LIKE를 사용했지만 정확 일치 작업은 대소문자를 구분했습니다.업그레이드하고 레이블의 대소문자가 달라질 수 있다면 회귀 테스트를 추가하세요.
%와 _가 없는 고정 길이 UUID 레이블.권고문에 따르면 이 형식은 영향받지 않습니다. 고정 길이 레이블은 서로의 접두사가 될 수 없습니다.모든 namespace 세그먼트가 이 조건을 지키는지 확인하세요. 길이가 달라지거나 와일드카드가 들어갈 수 있으면 영향 범위로 간주하세요.
InMemoryStore.영향받지 않습니다. namespace 요소를 각각 비교합니다.이 CVE로 인한 업그레이드는 필요 없지만 애플리케이션 경계에서 tenant 소유권 검사는 유지하세요.
LangSmith 호스팅 배포.기본 LANGGRAPH_STORE_BACKEND=python은 AsyncPostgresStore를 사용하므로 취약한 패키지 버전이면 영향 범위입니다. 별도 grpc backend에는 동등한 수정이 적용됐습니다.설정된 backend와 실제 배포 버전을 확인하세요. 제품명만으로 판단하지 말고 호스팅 업데이트 또는 패키지 버전을 확인하세요.

단계별 공격 실행 흐름

단계 1

저장소 backend와 라벨 확인

배포에서 3.1.1 이전 langgraph-checkpoint-postgres 또는 langgraph-checkpoint-sqlite를 사용하고 namespace로 사용자나 tenant를 분리해야 합니다. 서로 prefix가 겹치는 라벨이나 %, _ 라벨이 경계 문제와 관련됩니다.

단계 2

일반적인 범위 읽기 실행

인증된 애플리케이션 요청이 foo 같은 namespace를 검색합니다. 특수 payload는 필요 없으며, 일반 namespace가 취약한 비교기에 전달됩니다.

단계 3

Backend가 라벨을 연결하고 패턴 비교

Store는 라벨을 점으로 연결하고 끝에 %가 붙은 LIKE prefix를 사용합니다. 구분자를 요구하지 않아 foo가 foobar와 일치할 수 있고, 라벨의 패턴 문자도 결과 범위를 넓힐 수 있습니다.

단계 4

인접 namespace의 항목 반환 가능

애플리케이션이 namespace를 tenant 경계로 사용하면 검색 또는 목록에서 다른 사용자의 저장 항목이 반환될 수 있습니다. 이는 읽기 기밀성 문제이며 SQL injection이나 쓰기 권한 문제가 아닙니다.

단계 5

업데이트 후 격리 재검증

해당되는 두 패키지를 3.1.1 이상으로 업데이트하세요. 분리된 테스트 tenant에서 인접 prefix, 문자 그대로의 %와 _, suffix, SQLite 대소문자 동작을 확인하세요.

소스 코드 비교: 취약한 구현 vs 보안 패치

취약한 조건은 결합된 namespace로 시작하는 모든 문자열을 하위 항목으로 처리합니다. 수정은 정확한 동등 비교를 추가하고 하위 세그먼트 앞에 실제 점을 요구하며 LIKE 패턴에서 역슬래시, %, _도 이스케이프합니다. Upstream commit은 list_namespaces의 prefix/suffix 패턴도 세그먼트 인식 매칭으로 바꾸고 SQLite 검색을 조정합니다. 전체 모듈이 아닌 소스 코드 발췌입니다.

소스 코드 발췌: libs/checkpoint-postgres/langgraph/store/postgres/base.py

취약한 구현
def _prepare_batch_search_queries(op):
    ns_condition = "TRUE"
    ns_param = None
    if op.namespace_prefix:
        ns_condition = "store.prefix LIKE %s"
        ns_param = (f"{_namespace_to_text(op.namespace_prefix)}%",)
    return ns_condition, ns_param
보안 강화 패치
def _escape_like_literal(text: str) -> str:
    return text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")

def _namespace_prefix_condition(namespace_prefix: tuple[str, ...]) -> tuple[str, tuple]:
    path = _namespace_to_text(namespace_prefix)
    condition = r"(store.prefix = %s OR store.prefix LIKE %s ESCAPE '\\')"
    return condition, (path, f"{_escape_like_literal(path)}.%")

if op.namespace_prefix:
    ns_condition, ns_param = _namespace_prefix_condition(op.namespace_prefix)

엔지니어링 및 시스템 보안 강화 체크리스트

출처