신뢰성 주석 — 각 에이전트가 자기 발견을 직접 코드로 교차검증했습니다. 에이전트가 보고한 P0 오탐 5건(SQL injection·BullMQ 중복·graceful shutdown·workspace 우회·게이트 우회)은 근거 검토 후 강등/기각했습니다. 아래는 확증된 항목만 담았습니다.
| # | 분야 | 위치 | 문제 | 영향 |
|---|---|---|---|---|
| 1 | 인프라/보안 | elysia-server/Dockerfile:35 COPY .env* ./ · admin/Dockerfile |
.env 라이브 시크릿 49개(sk-ant-…, AWS AKIA…, Supabase service key, DB_PASSWORD)가 이미지 레이어에 영구 잔존. 런타임 env_file 주입과 별개로 Dockerfile이 또 복사 |
이미지 유출 = 전 시크릿 유출. Infisical SSOT 위반 |
| 2 | 보안 | nylas.service.ts:1295 |
Nylas webhook 서명검증 부재 — Paddle·Slack·Toss·SendGrid·AgentMail은 전부 HMAC 검증하나 Nylas만 누락 | open/click/reply 추적 이벤트 위조 주입 |
| 3 | 인프라 | .github/workflows/ci.yml:31-33 |
메인터 6명(최다 커밋 계정 포함) push/PR에 CI 전체 skip → lint·type·build·auth gate 무검증 | 정적 게이트가 유일 방어선인데 우회 |
| # | 분야 | 위치 | 문제 |
|---|---|---|---|
| 4 | 백엔드 | sequence-email-worker/idempotency.ts:54 | 멱등 marker Redis-only → Redis 장애 시 시퀀스 이메일 중복 발송 (DB UNIQUE 이중방어 없음) |
| 5 | DB | lead-search.service.ts:697,731 · email-replies.service.ts:158 | 사용자 라우트 OFFSET cap 없음 (leads 851k · email 208k row) → 깊은 페이지 full scan |
| 6 | DB | lead-engagement.service.ts:119 | emails 자기조인 COUNT(DISTINCT)를 기본 db(32MB)로 → 디스크 spill |
| 7 | 프론트 | lead-deals.ts:60~172(6곳) · leads.ts:428 | data as unknown as … 더블 캐스팅(OpenAPI never 우회) → shape 불일치 시 런타임 크래시 |
| 8 | 프론트 | SequenceEnrollmentsTable.tsx · CustomerTableReadonly.tsx | 수만 row 테이블 가상화 없이 전량 렌더 (자매 CustomerTable은 적용됨) |
| 9 | 프론트/보안 | public-routes.tsx:117~151 | /snitcher-test·/manus-test 등 테스트 페이지 인증 없는 public 프로덕션 배포 |
| 10 | 백엔드 | reply-alert.routes.ts:23 | 최근 커밋(4f2558feb) 추가 라우트가 app.ts 미마운트 → 전부 404, 기능 깨짐 |
| 심각도 | 위치 | 문제 |
|---|---|---|
| Crit/High | #1, #2 참조 | 시크릿 굽기 · Nylas 무서명 |
| Med | error-handler.ts:174~217 | 500 응답에 raw error.message 노출 (prod 게이팅 없음) |
| Med | rate-limit.plugin.ts:17~94 | rate limiter in-memory Map → 다중 replica에서 한도 ×N (brute-force·AI비용 abuse) |
| P1 | WorkspaceEmailAccountsSection · WorkspaceMembersSection | 삭제·소유권이전 등 파괴적 버튼이 hasPermission 가드 없이 렌더 (add엔 가드 있음) |
| P1 | dashboard-admin-routes.tsx:90 | admin 라우트 2개 명시적 RouteGuard 누락 (layout default-deny에만 의존) |
| P2 | webhook.routes.ts:78 · toss-webhook.routes.ts:56 | secret 미설정 시 검증 스킵 → prod env 강제 필요 |
| 양호 | JWT httpOnly 쿠키 · DOMPurify 21곳 · sql 파라미터화 · check:routes 게이트 | 확인됨 |
| 심각도 | 위치 | 문제 |
|---|---|---|
| P1 | linkedin-sdr-profile-view-budget.ts:35 | cap check-then-act 비원자 → race로 초과 |
| P1 | trial-feature-usage.service.ts:192 | 카운터 SELECT→UPDATE 트랜잭션 밖 → lost update |
| P2 | personalized-emails/rate-limit.service.ts:70 | Promise.all 부분 reject 미처리 → rate-limit 상태 불일치 |
| P2 | dsar.service.ts:115,308,336 | 부분실패 후 fulfilled 마킹 + 4단계 erasure 트랜잭션 부재 (GDPR) |
| P2 | queues.ts:1438,3529 | CSV export·blog enqueue dedup jobId 없음 → 중복 클릭 2건 처리 |
| P2 | 약 30개 파일 | 모델명 "gemini-3-flash-preview" 하드코딩 (SSOT 위반) |
| 심각도 | 위치 | 문제 |
|---|---|---|
| P0~P1 | #5,#6 + sequence-query.service.ts:305 · ui-events.service.ts:181 · buyer-search/analytics.service.ts:374 · activity-log.service.ts:334 | OFFSET · 기본 db COUNT(DISTINCT) 다수 산재 |
| P1 | visitor.service.ts:692 · assessment.service.ts:314 | 동적 정렬 OFFSET → 정렬키별 keyset 필요 |
| P1 | drizzle/0294,0332 | leads/emails trgm GIN fastupdate=on 미설정 (INSERT burst 권고 위반) |
| ⚠️ 마감 | 마이그 allowlist 0338·0339·0373 | 2026-06-30(내일) 만료 → 정리·연장 필요 |
| 양호 | UUID uuidv7() 100% · enum 분리 · 핵심 인덱스 설계 | 확인됨 |
| 심각도 | 위치 | 문제 |
|---|---|---|
| P0 | #7,#8,#9 참조 | 더블캐스팅 · 비가상화 · public 테스트페이지 |
| P1 | hooks 11곳 (leads/emails/sequences/dashboard) | keepPreviousData(금지) → keyset 무한스크롤 |
| P1 | SignatureEditorModal.tsx:62 · email-sidebar.tsx:243 · RepliedEmailsList.tsx:308 | 무가드 re-sync effect → 편집 중 입력 덮어씌움/데이터 손실 |
| P1 | RichSidePanel.tsx | 동적 리스트 key={idx} → 중간 삭제 시 입력값·포커스 corruption |
| P1 | client.ts:615 전역 | API 경계가 생성 스키마와 분리(generated/schema import 0) → BE 변경 시 컴파일 통과·런타임 drift |
| P1 | ImpersonationContext.tsx:31 · SSE 4파일 | 인증/SSE 응답 as any·무검증 단언 |
| 심각도 | 위치 | 문제 |
|---|---|---|
| Crit | #1,#3 참조 | 시크릿 굽기 · CI skip 우회 |
| P1 | Dockerfile:20 | bun install --frozen-lockfile || bun install 폴백이 무결성 게이트 무력화 |
| P1 | e2e 전반 | 셀렉터 SSOT(ports) 86% 스펙 우회 · networkidle .catch(()=>{}) ~60곳 무음통과 · waitForTimeout 15곳 · burn-in 전무 |
| P1 | 매출 영역 E2E | network-buyers(#9121)·이메일 발송 enqueue·billing tier-deny 분기 스펙 공백 |
| P1 | 소스 트리 | 200줄 초과 1598/4482 파일(36%), 1000줄+ 111개 · production any 86개(auth.macro·paddle 포함) |
| P2 | package.json · compose.db.yml | "eslint":"*" 미사용 와일드카드(Biome 사용 중) · 로컬 DB 이미지 untagged floating |
| P2 | 코드 부채 | @deprecated 53파일 미삭제 · TODO 68건 · console.log 302건 |
| 양호 | alpha/beta 컨테이너 | 핵심 서비스 전부 healthy(직접 점검), Exited(0)는 1회성 job |
에이전트가 P0로 올렸으나 코드 교차검증으로 강등/기각된 항목. 이 리포트의 정밀도를 위해 명시합니다.
excludeIds는 DB값만 보간, 사용자 입력 경로 없음 → 악용 불가worker.ts:362 중앙 핸들러가 일괄 종료iam-check.ts:278)가 멤버십 재검증, 비멤버 403/healthz·/readyz만 노출임팩트 대비 작업량이 작은 것부터: #1 시크릿 굽기 제거(.dockerignore) → #10 reply-alert 마운트(1줄) → DB allowlist 만료(내일 마감) → #2 Nylas 서명검증 → #5·#6 DB 쿼리. 묶어서 PR 착수 가능합니다.