# task-2263: InsuRo 보안 취약점 수정 — CRITICAL 2건 + HIGH 6건

**팀**: dev1-team (헤르메스 팀장)
**작업 레벨**: Lv.3
**작업일**: 2026-04-28

---

## Situation
InsuRo 보안 전수조사(task-2262)에서 CRITICAL 2건, HIGH 6건의 보안 취약점이 발견되었다.
SSRF, JWT 미검증, 에러 메시지 노출, npm 취약점, subprocess 입력 검증 부재, 인증 없는 엔드포인트가 포함된다.

## Complication
CRITICAL 등급 취약점(SSRF, .env 시크릿)은 즉시 수정이 필요하다. 대상 파일 `main.py`가 5068줄 대형 파일이며, 수정 8건이 동일 파일에 집중되어 있다.

## Question
8건의 보안 취약점을 테스트 회귀 없이 안전하게 수정할 수 있는가?

## Answer
**8건 전체 수정 완료.** pytest 477건 전량 통과, npm build 성공.

---

## 수정 상세

### CRITICAL 1: SSRF 방지 — file_url 화이트리스트 검증
- `_validate_file_url()` 함수 추가 (main.py:170-182)
- HTTPS만 허용, 허용 도메인 3개(supabase.co, drive.google.com, storage.googleapis.com)
- 내부 IP(private/loopback/link-local) 차단
- `parse_premium_file` 함수 내 다운로드 전 검증 적용 (main.py:4149)

### CRITICAL 2: .env 시크릿 보호 강화
- `.env` 퍼미션 664→600 수정 완료 (원본 프로젝트)
- 시크릿 하드코딩: 전수 확인 결과 발견 없음
- 로그 시크릿 출력: 키 이름만 언급, 값 노출 없음
- `.gitignore`에 `.env` 등록 확인

### HIGH 1: JWT verify_signature 수정
- 다중 계정 탐지 미들웨어에서 JWKS 기반 서명 검증 추가
- 검증 실패 시 탐지 스킵 (미들웨어이므로 요청 차단하지 않음)

### HIGH 2: JWT verify_aud 수정
- `verify_jwt()`에서 `options={"verify_aud": False}` 제거
- `audience="authenticated"` 명시적 설정 (Supabase 표준값)

### HIGH 3: npm audit fix
- `jsdom@26.1.0` 업데이트로 3건 해결 (14→4 HIGH)
- 나머지 4건: esbuild/vite, serialize-javascript/vite-plugin-pwa 체인 — breaking change 필요
- `npm audit fix --force` 미사용 (안전 우선)

### HIGH 4: 에러 메시지 일반화 (11개소)
- main.py 5개소: ingest(L1535), 기여 기록(L1976), onboarding(L2776), newsletter-chat(L4544), premium-chat(L4692)
- mediscan_router.py:223, generation_queue.py:69+79, pipeline.py:138, image_generator.py:100
- 모두 `detail="일시적인 오류가 발생했습니다."` + `logger.exception()` 패턴

### HIGH 5: subprocess 입력 검증
- `_validate_file_path()` 함수 추가 (main.py:188-197)
- 정규표현식 화이트리스트 + 경로 순회(..) 방지
- Vision 배치 img_path 검증 적용 (L3743, L3762)
- tempfile 생성 경로는 시스템이 안전하게 생성하므로 별도 검증 제외

### HIGH 6: 파일 변환 엔드포인트 인증 + Rate Limit
- `/api/tools/convert/word-to-pdf`: `@limiter.limit("10/minute")` + `Depends(verify_jwt)` 추가
- `/api/tools/convert/pdf-to-word`: 동일 적용

---

## 수정 파일별 검증 상태

| 파일 | 변경 내용 | grep 검증 | 상태 |
|------|-----------|-----------|------|
| server/main.py:170-182 | `_validate_file_url()` 추가 | grep "_validate_file_url" 2건 OK | verified |
| server/main.py:188-197 | `_validate_file_path()` 추가 | grep "_validate_file_path" 3건 OK | verified |
| server/main.py:225-242 | JWT 서명 검증 추가 | grep "verify_signature" 0건 OK (제거됨) | verified |
| server/main.py:728 | `audience="authenticated"` | grep 'audience="authenticated"' 1건 OK | verified |
| server/main.py:1535,1976,2776,4544,4692 | 에러 메시지 일반화 | grep "일시적인 오류" 5건 OK | verified |
| server/main.py:3383-3385 | word-to-pdf 인증+rate limit | grep "limiter.limit" 확인 OK | verified |
| server/main.py:3489-3491 | pdf-to-word 인증+rate limit | grep "limiter.limit" 확인 OK | verified |
| server/main.py:4149 | SSRF 검증 적용 | grep "_validate_file_url" 확인 OK | verified |
| server/mediscan_router.py:223 | 에러 메시지 일반화 | grep "분석 중 오류" 확인 OK | verified |
| server/generation_queue.py:69,79 | 에러 메시지 일반화 | grep "처리 중 오류" 2건 OK | verified |
| server/pipeline.py:138 | 에러 메시지 일반화 | grep "처리 중 오류" 1건 OK | verified |
| server/image_generator.py:100 | 에러 메시지 일반화 | grep "이미지 생성 중 오류" 1건 OK | verified |
| server/tests/test_convert.py | JWT override 추가 | grep "dependency_overrides" OK | verified |
| server/tests/test_pipeline_failures.py | assertion 업데이트 | grep "처리 중 오류" 3건 OK | verified |
| package.json / package-lock.json | npm audit fix | npm audit HIGH 14→4 | verified |

---

## 발견 이슈 및 해결

1. **test_convert.py 테스트 실패**: 파일 변환 엔드포인트에 인증 추가 후 기존 테스트가 401 반환. → JWT mock override 추가로 해결.
2. **test_pipeline_failures.py 3건 실패**: 에러 메시지 일반화 후 에러 내용 검증 assertion 불일치. → assertion을 일반 메시지 검증으로 수정.
3. **test_smart_parse_hybrid.py 3건 실패**: subprocess 경로 검증이 tempfile mock과 충돌. → tempfile 경로(시스템 생성)는 검증 제외로 해결.
4. **npm audit HIGH 잔여 4건**: esbuild/vite, serialize-javascript/vite-plugin-pwa 체인의 breaking change 필요. `--force` 미사용.
5. **.env 퍼미션 664**: 원본 프로젝트 .env가 664로 설정됨 → 즉시 600으로 수정.

---

## L1 스모크테스트 결과

- 서버 재시작: 성공 (포트 8765에서 기동)
- API 응답 확인 (curl 증거: `/home/jay/workspace/memory/reports/evidence/task-2263-l1.txt`):
  - `GET /api/status` → 200 `{"status":"ok"}`
  - `POST /api/tools/convert/word-to-pdf` (인증 없음) → 401 (기대값)
  - `POST /api/tools/convert/pdf-to-word` (인증 없음) → 401 (기대값)
- SSRF 검증 함수 테스트: 9/9 케이스 ALL PASS
  - 허용 도메인 3건 → True
  - HTTP 거부 → False
  - 미허용 도메인 → False
  - loopback/link-local/private IP → False
- 스크린샷: 해당없음 (백엔드 보안 수정, 프론트 변경 없음)

---

## 테스트 결과

- **pytest**: 477 passed, 0 failed (68.83s)
- **npm build**: 성공 (11.94s)
- **npm audit**: HIGH 4건 잔여 (14→4, breaking change 필요 항목만 잔존)
- **py_compile**: main.py + 4개 파일 모두 성공

---

## Gemini PR 리뷰 결과
- **PR**: https://github.com/JonghyukJeon/InsuRo/pull/54
- HIGH 1건 (수용): `endswith` 도메인 우회 → `hostname == d or hostname.endswith("." + d)`로 수정
- MEDIUM 1건 (수용): 정규식 하이픈 시작 허용 → `^[a-zA-Z0-9_/.]`로 시작 문자 제한
- MEDIUM 1건 (보류): PyJWKClient 인스턴스 캐싱 → 기존 verify_jwt도 동일 패턴, 별도 성능 작업으로 분리
- **미수정 HIGH 0건** → PASS

## 머지 판단
- **머지 완료**: PR #54 merge 완료
- **브랜치**: task/task-2263-dev1 (삭제됨)
- **빌드 결과**: npm run build 성공 (12.28s) — merge 후 main에서 확인

---

## 모델 사용 기록
- 불칸(백엔드): sonnet — main.py 보안 수정 8건 전체 구현
- 아르고스(테스터): sonnet — .env 보안 검증
- 헤르메스(팀장): opus — 설계/분배/검토/통합/테스트 수정/Gemini 대응

---

## 3문서 상태
- plan.md: status → completed
- context-notes.md: 3 Step Why A-B-C 기록 완료, status → completed
- checklist.md: 14/14 항목 체크 완료, status → completed

## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회

