Skip to content

임베딩 검색을 요청자 소유 문서로 제한 - #217

Merged
i3months merged 1 commit into
devfrom
feat/embedding-search-user-scope
Aug 23, 2026
Merged

임베딩 검색을 요청자 소유 문서로 제한#217
i3months merged 1 commit into
devfrom
feat/embedding-search-user-scope

Conversation

@i3months

Copy link
Copy Markdown
Member

문제

#216 을 작업하며 함께 보고했던 건이다. POST /api/internal/embeddings/search 에는 userId 파라미터가 아예 없었다.

  • documentIds 가 비면 스펙상 "전체 검색" — 다른 사용자의 청크까지 대상
  • id 를 줘도 소유권을 확인하지 않는다 — 남의 문서 id 를 넣으면 그대로 조회
  • document_embeddings 에는 청크 원문이 저장되므로, 나오면 곧 남의 이력서 문장이다

실제 유출은 없었다. AI 호출부 3곳(questions/followup/feedback)이 모두 빈 목록을 사전에 걸러 (none) 을 반환한다. 확인했다.

문제는 방어가 전적으로 호출자에게 있었다는 점이다. 호출부가 하나 늘거나 가드를 빠뜨리면 남의 이력서 청크가 프롬프트로 들어간다. 내부 API 라 X-Internal-API-Key 뒤에 있지만, 그건 "AI 서버가 맞나"만 확인할 뿐 "누구 데이터를 볼 자격이 있나"는 묻지 않는다.

수정

Core 가 스코프를 확정한다 — 호출자가 무엇을 보내든 요청자 소유 문서를 벗어날 수 없다.

입력 동작
userId 필수 (@NotNull)
documentIds 있음 소유 문서와의 교집합만 — 요청한 id 를 그대로 믿지 않는다
documentIds 비음 그 사용자의 활성 문서 전체 ("비면 전체 사용자" 규약 폐기)
교집합이 빔 검색하지 않고 빈 결과 — 빈 목록을 그대로 넘기면 다시 전체 검색이 된다

AI 는 envelope.context.user_id 를 싣는다. Core 가 generate.questions/followup/feedback 발행 시 MessageContext 에 이미 userId 를 채우고 있고 AI 모델에도 필드가 있어서 — 메시지 계약 변경이 없다. 세 consumer 의 _process(envelope) 에서 검색까지 값을 흘리기만 하면 됐다.

user_id 를 못 얻는 경우(구버전 발행 등)는 검색을 건너뛰고 (none) 으로 폴백한다. RAG 는 보강용이라 없어도 생성은 진행된다.

테스트

Core (#197 인프라, 실제 pgvector):

  • serviceScopesSearchToRequestingUsersDocuments — 남의 문서 id 를 명시해도 결과에 없다. documentIds 를 비워도 전체 검색이 되지 않는다
  • serviceReturnsEmptyWhenUserOwnsNothing — 소유 문서가 없으면 빈 결과 (빈 목록 위임 금지)

AI: 기존 test_search_embeddings_uses_latest_core_contract 가 새 필수 인자를 바로 잡아냈다(계약 테스트가 제 역할을 했다). 요청 body 기대값에 userId 를 반영. 393 tests 통과.

배포 순서

deploy-app.yml 이 백엔드·AI 를 같은 워크플로에서 배포한다. 그 사이 짧은 창에서 구버전 AI 가 userId 없이 호출하면 400 → AI 는 예외를 잡아 (none) 으로 폴백한다. 면접·피드백 생성은 계속되고 RAG 보강만 잠시 빠진다 — 실패 경로가 이미 non-fatal 로 설계돼 있어 별도 조치가 필요 없다.

문서

docs/messaging.md §10.1 신설 — 스코프 규약, 이전 규약 폐기, user_id 부재 시 폴백을 명시.

POST /api/internal/embeddings/search 에는 userId 파라미터가 아예 없었다.
documentIds 가 비면 스펙상 "전체 검색" 이라 다른 사용자의 청크까지 대상이고,
id 를 줘도 소유권을 확인하지 않아 남의 문서 id 를 넣으면 그대로 조회됐다.

실제 유출은 없었다 — AI 호출부 3곳이 모두 빈 목록을 사전에 걸러 (none) 을
반환한다. 하지만 방어가 전적으로 호출자에게 있었다. 호출부가 하나 늘거나
가드를 빠뜨리면 남의 이력서 청크가 프롬프트로 들어간다.

Core 가 스코프를 확정한다:
- userId 필수(@NotNull)
- documentIds 를 주면 소유 문서와의 교집합만 — 요청한 id 를 그대로 믿지 않는다
- 비면 그 사용자의 활성 문서 전체 ("비면 전체 사용자" 규약 폐기)
- 교집합이 비면 검색하지 않고 빈 결과 (빈 목록을 넘기면 다시 전체 검색이 된다)

AI 는 envelope.context.user_id 를 싣는다 — Core 가 generate.questions/
followup/feedback 발행 시 이미 채우고 있어서 메시지 계약 변경이 없다.
user_id 를 못 얻으면 검색을 건너뛰고 (none) 으로 폴백한다.
@i3months
i3months merged commit 8ac5d7c into dev Aug 23, 2026
5 checks passed
@i3months
i3months deleted the feat/embedding-search-user-scope branch August 23, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant