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메서드를 통해 이taintedGID를 생성하여 사용합니다.- 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)은 떨어지지만 장기적으로 훨씬 안전합니다.
- 가장 안전한 방법은 GID 대신