Rails Global ID 사용 시 LLM과의 위험성 및 안전한 활용 방안

Making Rails Global IDs safer

작성자
발행일
2025년 12월 10일

핵심 요약

  • 1 Rails Global ID(GID)는 LLM과 함께 사용될 경우, 인증 및 유효성 검사 부족으로 정보 유출 및 데이터 유출과 같은 심각한 보안 위험을 초래할 수 있습니다.
  • 2 ActiveRecord::Base.find의 유연한 ID 파싱(`id.scan(/\d+/)`) 특성 때문에 LLM이 UUID 같은 문자열에서 잘못된 숫자 ID를 추출하여 의도치 않은 레코드를 조회할 수 있습니다.
  • 3 이러한 위험을 완화하기 위해 커스텀 GID 로케이터를 통한 엄격한 ID 유효성 검사 및 권한 부여 계층 추가, 또는 `SignedGlobalID`나 `signed_id` 사용이 권장됩니다.

도입

LLM(대규모 언어 모델) 기반 애플리케이션 개발이 활발해지면서, 시스템 내 객체를 참조하는 도구 호출 방식에 대한 관심이 높아지고 있습니다. 특히 Rails 애플리케이션에서는 `Global ID(GID)` 시스템이 객체 직렬화 및 역직렬화에 유용하게 사용되지만, LLM과 결합될 경우 예상치 못한 보안 취약점을 야기할 수 있습니다. 본 글은 Rails GID의 기본 동작과 LLM과의 상호작용에서 발생할 수 있는 위험을 분석하고, 이를 해결하기 위한 안전한 접근 방안을 제시합니다.

Rails Global ID는 gid://app_name/ModelName/ID 형식으로 애플리케이션 내 특정 모델을 참조하는 문자열 핸들입니다. 주로 ActiveJob 직렬화에 활용되어, 큐에서 역직렬화될 때 해당 모델 객체를 자동으로 찾아줍니다. LLM은 이러한 텍스트 기반 패턴 인식에 능숙하여 GID를 도구 호출의 인자로 활용할 수 있습니다. 예를 들어, LLM이 미결 인보이스 목록을 GID로 받고 특정 인보이스의 상세 정보를 GID로 요청하는 시나리오가 가능합니다.

그러나 GID는 LLM과의 사용 시 다음과 같은 치명적인 문제점을 내포합니다:

GID의 위험성

  • 생성자 불확실성 및 신뢰성: GID는 애플리케이션 코드에 의해 생성되었음을 보장하지 않으며, LLM이 임의로 생성하거나 환각(hallucination)할 수 있습니다.

  • 권한 부여 계층 우회: GlobalID::Locator.locate는 기본적으로 권한 검사 없이 단순히 기본 키(primary key)로 객체를 조회합니다. 이는 current_user.credit_statements.find(id)와 같이 사용자 범위 내에서 객체를 조회하는 일반적인 Rails 패턴과 달리, 시스템 수준의 기능이므로 정보 노출 위험이 있습니다.

  • ActiveRecord::Base.find의 유연한 ID 파싱: GID는 내부적으로 ActiveRecord::Base.find를 사용합니다. 이 메서드는 id.scan(/\d+/)를 통해 문자열에서 숫자 시퀀스를 추출하여 ID로 사용합니다. 만약 LLM이 gid://moneymaker/Invoice/22ecb3fd-5e25-462c-ad2b-cafed9435d16와 같이 UUID를 포함한 GID를 생성하면, ActiveRecord는 여기서 “22”를 추출하여 ID 22인 인보이스를 찾아내어 잘못된 레코드가 조회될 수 있습니다. 이는 다른 사용자의 데이터에 접근하거나 수정하는 심각한 보안 문제를 야기할 수 있습니다.

안전한 GID 활용 방안

이러한 위험을 해결하기 위해 다음과 같은 방법을 고려할 수 있습니다:

  • 커스텀 LLMSafeLocator 사용:
    • GlobalID::Locator::BaseLocator를 상속받아 LLMSafeLocator를 구현합니다.
    • locate 메서드 내에서 gid.model_id가 순수한 숫자(^\d+$)인지 정규식으로 검사하여 ID 파싱의 “마법”을 제거합니다.
    • authorize_access! 메서드를 추가하여 조회된 모델이 현재 사용자(Current.user)에게 속하는지 검사하는 권한 부여 계층을 강제합니다.
    • GlobalID::Locator.use :tainted, LLMSafeLocator.new를 통해 별도의 app ID(tainted)로 로케이터를 등록하고, 애플리케이션 코드에서는 to_tainted_global_id 메서드를 통해 이 tainted GID를 생성하여 사용합니다.
    • LLM에게는 gid://tainted/Invoice/12와 같이 tainted 앱 ID를 사용하는 GID만 주고받도록 지시하고, 입력받은 GID의 app 속성을 검증합니다.
  • SignedGlobalID 또는 signed_id 사용:
    • 가장 안전한 방법은 GID 대신 SignedGlobalID 또는 signed_id를 사용하는 것입니다. 이는 GID가 변조되지 않았음을 암호화 방식으로 보장합니다.
    • invoice.signed_id(purpose: "llm")로 서명된 ID를 생성하고, current_user.invoices.find_signed(signed_invoice_id, purpose: "llm")를 통해 안전하게 조회합니다. 이 방법은 다형성(polymorphism)은 떨어지지만 장기적으로 훨씬 안전합니다.

결론

Rails Global ID는 ActiveJob 직렬화와 같은 시스템 내부 용도로는 매우 유용하지만, LLM과 같은 외부의 신뢰할 수 없는 입력과 함께 사용할 때는 심각한 보안 문제를 야기할 수 있습니다. 특히 GID의 기본 동작이 권한 검사를 수행하지 않고, `ActiveRecord::Base.find`의 유연한 ID 파싱이 잘못된 레코드 조회를 유발할 수 있음을 인지해야 합니다. 따라서 GID를 LLM과 연동할 때는 반드시 커스텀 로케이터를 통한 엄격한 유효성 검사 및 권한 부여 계층을 추가하거나, `SignedGlobalID` 또는 `signed_id`와 같은 더욱 안전한 대안을 사용하는 것이 필수적입니다. LLM 입력을 신뢰할 수 없는 사용자 입력으로 간주하고, 이에 상응하는 보안 조치를 적용해야 합니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!