필터와 벽의 차이점
데이터에 접근할 수 있는 AI 에이전트를 구축하고 있습니다. 보안을 위해 두 가지 선택지가 있습니다. 데이터를 필터링하거나, 벽을 세우는 것입니다.
필터링은 쿼리가 특정 행만 반환한다는 의미입니다. 벽을 세우는 것은 에이전트가 숨겨진 행에 아예 접근할 수 없음을 의미합니다.
무언가 잘못되기 전까지는 이 둘이 똑같아 보입니다.
최근에 120만 단어를 지식 베이스로 변환하는 시스템을 구축했습니다. 데이터 관리를 위해 Supabase를 사용했습니다. AI 에이전트가 공개된 콘텐츠만 볼 수 있도록 하고 싶었습니다.
데이터를 필터링하기 위해 표준 Postgres 뷰를 사용했습니다:
CREATE VIEW public_seeds AS
SELECT * FROM moments
WHERE visibility = 'public'
AND is_canonical = true;
올바르게 보이지만, 여기에는 치명적인 결함이 있습니다. 기본적으로 Postgres 뷰는 호출하는 사람이 아니라 소유자(owner) 권한으로 실행됩니다. 뷰 소유자는 종종 모든 권한을 가지고 있습니다. 이는 행 수준 보안(RLS) 정책이 뷰에 적용되지 않음을 의미합니다.
당신은 벽을 세운 것이 아니라, 필터를 만든 것입니다.
필터가 실패해도 AI 에이전트는 알아차리지 못합니다. 사람은 오류나 잘못된 데이터를 발견하지만, 에이전트는 그저 전달받은 데이터를 처리할 뿐입니다. 필터가 실패하면 에이전트는 경고 없이 개인 데이터를 사용하기 시작할 것입니다.
Postgres 15는 security_invoker 옵션으로 이 문제를 해결했습니다.
security_invoker를 true로 설정하면 뷰는 호출하는 역할(role)로 실행됩니다. 이를 통해 뷰가 RLS 정책을 따르도록 강제할 수 있습니다. 이제 뷰는 구조적 관문이 됩니다.
올바른 방법:
CREATE VIEW public_seeds
WITH (security_invoker = true)
AS
SELECT * FROM moments
WHERE visibility = 'public'
AND is_canonical = true;
이제 벽은 구조적입니다. 개발자가 잘못된 쿼리나 조인을 작성하더라도 RLS 정책이 테이블을 보호합니다.
"이런 일은 일어나지 말아야 한다"는 모든 것이 완벽하게 작동한다는 전제에 의존합니다. "이런 일은 일어날 수 없다"는 당신의 아키텍처에 의존합니다.
AI를 위해 구축할 때는 '일어날 수 없는 일'을 위해 설계해야 합니다.
설정 시 확인해야 할 세 가지 사항:
- 에이전트에 서비스 역할(service role)을 사용하지 않도록 하세요. 서비스 역할은 RLS를 완전히 우회합니다.
- Postgres 버전을 확인하세요.
security_invoker를 사용하려면 버전 15 이상이 필요합니다. - 기존 뷰를 점검하세요. 오래된 뷰는 새로운 RLS 정책을 자동으로 따르지 않습니다.
Optional learning community: https://t.me/GyaanSetuAi