임베딩 검색을 요청자 소유 문서로 제한 - #217
Merged
Merged
Conversation
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) 으로 폴백한다.
This was referenced Aug 23, 2026
Merged
i3months
added a commit
to i3months/stackup
that referenced
this pull request
Aug 28, 2026
지금까지 자료 삭제는 soft delete 만 했다. S3 의 업로드 원본 PDF·분석 마크다운과 document_embeddings 의 청크가 그대로 남았다. 이력서 PDF 에는 이름·연락처·주소가 들어 있고(docs/security.md §5.2) 임베딩 청크에는 그 원문 조각이 들어 있다. Team-StackUp#216 으로 검색에서 빠지고 Team-StackUp#217 로 스코프가 닫혀 읽히지는 않게 됐지만, 보관 자체가 남아 있었다. 행은 계속 soft delete 로 남긴다 — session_contexts 가 analyzed_documents 를 FK 로 참조해서 행을 지우면 제약 위반이다. 남길 이유가 있는 건 참조 무결성뿐이고 내용물은 아니다. - 임베딩 청크: 삭제와 같은 트랜잭션에서 DELETE - 스토리지 객체: ObjectPurgeEvent → AFTER_COMMIT 리스너 커밋 전에 지우면 롤백된 삭제로 객체만 사라져 되돌릴 수 없다 - 스토리지 삭제 실패는 사용자 요청을 실패시키지 않는다. 이미 커밋돼 행이 도달 불가이고 예외를 던져도 삭제를 되돌릴 수 없다. 키를 ERROR 로 남겨 수동 회수만 가능하게 한다 — 조용히 삼키면 파기됐다고 착각하게 된다 이력서·레포·자소서 세 경로 모두 적용(AnalyzedDocumentCascadeListener 가 이미 세 이벤트를 다룬다). 웹 이력서는 업로드 원본이 없어 filePath 가 null 이고, 분석 전에 지운 자료는 documentPath 가 없다 — 빈 키는 이벤트에서 걸러진다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
문제
#216 을 작업하며 함께 보고했던 건이다.
POST /api/internal/embeddings/search에는userId파라미터가 아예 없었다.documentIds가 비면 스펙상 "전체 검색" — 다른 사용자의 청크까지 대상document_embeddings에는 청크 원문이 저장되므로, 나오면 곧 남의 이력서 문장이다실제 유출은 없었다. AI 호출부 3곳(
questions/followup/feedback)이 모두 빈 목록을 사전에 걸러(none)을 반환한다. 확인했다.문제는 방어가 전적으로 호출자에게 있었다는 점이다. 호출부가 하나 늘거나 가드를 빠뜨리면 남의 이력서 청크가 프롬프트로 들어간다. 내부 API 라
X-Internal-API-Key뒤에 있지만, 그건 "AI 서버가 맞나"만 확인할 뿐 "누구 데이터를 볼 자격이 있나"는 묻지 않는다.수정
Core 가 스코프를 확정한다 — 호출자가 무엇을 보내든 요청자 소유 문서를 벗어날 수 없다.
userId@NotNull)documentIds있음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부재 시 폴백을 명시.