POSA_Copyrighter/docs/reviews/2026-07-11-remediation-checklist.md
changukyu 76a06f2ff1 docs: CLAUDE.md / PROJECT_MAP 신설, 7/11 체크리스트 재확인
세션 진입 문서가 없어 41.6k 라인 · 70모듈 구조를 매번 재발견하고 있었다.

- CLAUDE.md: 진입점 + 이 프로젝트에만 해당하는 규칙(폐쇄망 의존성 금지,
  app-*.js 로드 순서 계약, SCORER_VERSION 갱신 의무, 직렬화 라운드트립 불변식,
  LLM 은 실모델로 프로브, cp949 비ASCII 출력 주의)
- docs/PROJECT_MAP.md: 파일·라우트·스키마 위치, app-*.js 로드 순서, 알려진 부채
- README: verify 명령, app-*.js 구조, 인증 스로틀 동작 설명

7/11 리메디에이션 체크리스트를 코드와 대조해 갱신했다. 미체크 항목 중 6건은
이미 수정돼 있었고(naver 멀티페이지 쿼터, #10 Origin/Referer CSRF, env_file
BOM, daily-limit fail-open, __main__ 경로 앵커링, 그리고 Batch 8 전체가
Batch 6 과 중복) 각 줄에 확인 근거를 남겼다. 남은 미체크는 전부 근거가 기재된
명시적 연기다.
2026-08-08 09:49:55 +09:00

118 lines
9.6 KiB
Markdown

# 2026-07-11 리뷰 리메디에이션 체크리스트
> 기준 리뷰: `docs/reviews/2026-07-11-project-review.md` (HEAD 89c1fe4)
> 신규 HIGH 9 + 유닛 추가 HIGH 3(통합/프론트) + MEDIUM ~28 + LOW ~14.
> 배치는 risk-per-effort 순. 각 배치 후 `pytest tests/rights_filter -q` 통과 확인.
>
> **2026-08-08 재확인**: 미체크로 남아 있던 항목을 코드와 대조했다. 6건은 이미 수정돼 있었고
> (체크리스트가 낡았던 것 — 각 항목에 확인 근거 기재), 보안 2건(#8 rate limit, SSRF
> DNS-rebinding TOCTOU)은 실제로 열려 있어 이번에 수정했다. 남은 미체크는 전부 **명시적 연기**
> 이며 각 줄에 근거가 있다.
## Batch 1 — 스코어링 파이프라인 복구 (최우선) ✅ 완료
직렬화 라운드트립 필드 손실 1개 패턴이 HIGH 3건을 만듦. 프로모터 3건은 배포 즉시 노출되므로 동일 배치.
- [x] H6 `store_serialization.py` — `_evidence_payload`에 `promoted`/`promotionReason` 추가, `_domain_evidence_from_ui`에서 복원
- [x] H7 `store_serialization.py` — `entity`/`category` 추가+복원
- [x] D1 `store_serialization.py` — 실제 `EvidenceSource` 영속화(`domainSource`), 키워드 휴리스틱은 레거시 폴백
- [x] M12 `search_result_promoter.py` — no-results 구조적 매칭, 마커 스캔에서 query 텍스트 제외
- [x] M13 `search_result_promoter.py` — 한국어 마커 추가, 단문 오탐 워드바운더리
- [x] M14 `risk_scoring.py` — promoted Naver 결과 query_signature 기준 dedup
- [x] 회귀 테스트 7건 (라운드트립 score>0, 2 distinct entities, skip 무점수, dedup, 프로모터)
## Batch 2 — 동시성 / _write_lock 규율 ✅ (M4/#12 제외)
무락 RMW 1개 패턴이 HIGH 3 + MEDIUM 5.
- [x] H1 `sqlite_store.py` — `record_decision` _write_lock
- [x] H2 `store_persistence.py` — `_apply_provider_usage_delta` 델타 병합(stale payload 되쓰기 금지)
- [x] H3 `store_enrichment.py` — `_sync_provider_payloads` 저장된 enabled 보존(`operatorDisabled` 플래그로 연결성/운영자 구분)
- [x] H4 `store_operations.py` — `manual_search` 네트워크 락 밖, RMW 재읽기 후 락 안
- [x] M1 `store_operations.py` — `collect_keyword_candidates` 후보 메모리 누적→락 안 clear+insert
- [x] M2 `sqlite_store.py` — `set_provider_enabled`/`emergency_disable` 락+트랜잭션
- [x] M3 `store_enrichment.py` — `_sync_submission_provider_state` 락
- [x] 회귀 테스트 2건 (operator-disable 재시드 생존, usage delta가 in-flight disable 안 되돌림)
- [ ] #12 `rerun_enrichment`/`run_auto_search` 락 — **연기**(메가함수 내 네트워크+RMW 교차, 락 유지 시 가용성 회귀; 연기된 HIGH#6 리팩터와 함께)
- [ ] M4 seed 락 협소화 — **연기**(동일 이유, 가용성 최적화)
## Batch 3 — 시드/저장소 견고성 ✅
- [x] H5 `sqlite_store.py` — 빈 analysis run 시 'failed' 밴드+감사+continue + 테스트
- [x] #13 `store_queue.py` — ensure_queue WHERE 키를 기존 행 id로 + 테스트
- [x] M6 `store_queue.py` — 큐 활성/레거시 이관 시 감사 이벤트(스팸 방지 조건부)
- [x] M5 `http_app.py` — 업로드 TOCTOU: 배타 생성(`open xb`) 재시도 루프
- [~] M8 `image_store.py` — **되돌림**: stem-충돌 접미어가 id를 형제-의존으로 만들어 판정된 submission 중복 부활 회귀(diff 리뷰 HIGH) → `id=path.stem` 복귀. 올바른 수정은 시드 단계 incumbent-id 보존 + 자산경로 마이그레이션 필요(연기)
- [ ] M7 `store_schema.py` — 제약 재구축 위반 행 coerce/격리 (미착수 — 마이그레이션 위험)
- [ ] M9 `store_queue.py` — 레거시 이관 파일 존재 검증 (연기 — 오이관 위험, 신중 설계)
## Batch 4 — 통합/아웃바운드 ✅
- [x] HIGH `cloud_vision_web_detection.py` — per-image error → HttpJsonError + 테스트 2건
- [x] HIGH `search_policy.py` — daily_limit 날짜 윈도우 리셋 + Lock
- [x] M `google_custom_search.py` — map 응답을 try 안으로
- [x] #9 `http_json.py` — 응답 크기 캡 + decode/read 예외 계약
- [x] #10 API 키 URL→헤더 — **Vision만** `X-goog-api-key`(검증됨+테스트). customsearch는 헤더 미검증(미지원 시 검색 조용히 실패)이라 `key=` 쿼리 유지(diff 리뷰 MEDIUM)
- [x] #15 `store_enrichment.py` — lastFailure 첫 실패 아이템 (3곳)
- [x] LOW `store_remote_fetch.py` — SSRF `not is_global`(CGNAT 등 특수대역 차단)
- [x] LOW `env_clients.py` — NAVER display 3종 클램프(1~100)
- [x] M `env_clients.py` — daily-limit malformed → 보수적 100(fail-open 제거) + 테스트 갱신
- [x] M `naver_search.py` — 멀티페이지 쿼터 실제 요청 수 (2026-08-08 확인: `_requested_calls` + `try_reserve(requested_calls=…)`/`release` 로 이미 구현돼 있었음. 항목이 낡았던 것)
- [x] M `store_remote_fetch.py` — SSRF DNS-rebinding TOCTOU (2026-08-08 수정: `_guarded_create_connection` 이 getaddrinfo 를 **1회만** 수행하고 그 답변의 모든 주소를 검증한 뒤 그중 하나로만 연결. `_Guarded*Connection`/`*Handler` 로 opener 에 배선 — 사전검사와 실제 다이얼의 해석이 갈라지지 않는다. 리바인딩 회귀 테스트 포함 14건)
- [ ] LOW: EUC-KR 스니핑, dead code 삭제(레이아웃 테스트가 고정 → 유지)
## Batch 5 — HTTP 보안 ✅ (M10/M11/#6)
- [x] M10 음수/비정수 Content-Length 거부 + 테스트
- [x] M11 핸들러 timeout=30
- [x] #6 앱셸 CSP/X-Frame-Options/nosniff + 테스트
- [x] #8(부분) 토큰 길이<32 경고 + static 부재 경고(`__main__`)
- [ ] #7 쿠키 분리+Secure (연기 — Secure는 localhost HTTP서 쿠키 미전송→인증 붕괴; HTTPS 전제 필요)
- [x] #8 rate limit (2026-08-08 수정: `server/auth_rate_limit.py` — **틀린 자격증명을 제시한** 시도만 클라이언트 주소별 고정윈도우 10회/60초로 제한, 초과 시 `429` + `Retry-After`. 성공하면 카운터 초기화. 토큰 미입력 요청(콘솔 최초 로드가 여러 건 발생시킴)은 세지 않으므로 정상 운영자가 잠기지 않는다. 테이블은 1024 클라이언트로 상한. 테스트 12건)
- [x] #10 Origin/Referer CSRF 폴백 (2026-08-08 확인: `_origin_ok()` 로 이미 구현돼 있었음 — 7/24 리뷰가 지적한 낡은 항목)
## Batch 6 — 프론트엔드 ✅
- [x] H8 이미지 뷰어 버튼 구현 + 케이스별 리셋
- [x] H9 dismiss 서버 영속화(신규 엔드포인트) + 테스트
- [x] HIGH '자동 항목 비활성' 선택 케이스 스코프
- [x] M16 deactivate 루프 멱등화(활성만, per-entry 실패 무시, finally refresh)
- [x] #17 setDecision in-flight 가드 + rerunSelected/applyCoverage null 가드
- [x] M18 addKnowledgeEntry 수집탭 초기화 부작용 제거
- [x] M19 executeSuggestedQueries refreshFromApi try/catch
- [x] M20 runCandidateCollection 선-클리어→성공 후로
- [x] M22 apiError 배너 applyBootstrap에서 clear
- [x] M23 renderCorrections derivedEntries 가드
- [x] #11(부분) riskClass 밴드 allowlist, case-image src safeUrl
- [ ] #11 잔여 sinks(similar-strip/face-crop safeUrl, innerHTML label) (미착수)
- [ ] #24 window.prompt → 인라인 (미착수 — 규모 큼)
- [ ] M21 8개 delegated mutation 공용 in-flight 가드 (미착수)
## Batch 7 — env/config/docs ✅
- [x] `env_file.py` UTF-8-sig BOM
- [x] `env_clients.py` daily-limit fail-open 제거
- [x] `__main__`/launcher 경로 ROOT chdir + python 3.13 가드 + static/token 경고
- [x] offline-install.md — Ollama/Playwright/lock 해시/egress 정정/`--only-binary`
- [x] .gitignore — .claude/settings.local.json, .impeccable/
- [ ] `env_clients.py` LLM assistant OLLAMA 게이팅 (연기 — env={} llm 테스트 다수 의존)
- [ ] package.json 정리 (미착수)
## Batch 8 — 에러핸들링/기타 ✅ (부분)
- [x] #14 `_sync_face_crops` 무음 예외 로깅 + preprocessing EXIF 폴백 경고(#19)
- [x] #16 skip 사유 reasons 오염 제거 + 테스트
- [x] #18 dedup 핫패스 타깃 쿼리(2곳)
- [ ] #14 잔여 무음 swallow(page scrape 등) (미착수)
- [ ] #20 ScoreResult.reasons tuple (연기 — 테스트 다수 `== []` 단언 충돌)
## Batch 7 — env/config/docs
- [x] `env_file.py:12` — UTF-8-sig BOM (2026-08-08 확인: `read_text(encoding="utf-8-sig")` 로 이미 적용됨)
- [x] daily-limit fail-open → 보수적 기본 (2026-08-08 확인: `_daily_limit()` 이 malformed 값에 warning + 100 적용. Batch 4 의 동일 항목과 중복 기재였음)
- [ ] `env_clients.py:239` — LLM assistant OLLAMA_* 게이팅 (연기 — env={} 로 LLM 을 기대하는 테스트 다수가 의존)
- [x] `__main__.py` — 경로 ROOT 앵커링 + static 부재 경고 (2026-08-08 확인: `6ab6da3` 에서 적용됨)
- [ ] offline-install.md — Ollama/Playwright/lock 해시
- [ ] python 3.13 가드, .gitignore, package.json 정리
## Batch 8 — 에러핸들링/기타 백로그
> **2026-08-08: 이 배치는 Batch 6 과 중복된 착수 전 계획 목록이었다.** #14·#16·#18·#19 는
> Batch 6 에서 이미 처리돼 [x] 로 기재돼 있고, #20 은 같은 곳에 연기 근거와 함께 남아 있다.
> 여기서 중복 추적하지 않는다 — 상태는 Batch 6 이 기준.
- [x] #14 무음 예외 로깅 → Batch 6 참조 (잔여 swallow 는 Batch 6 에 별도 미체크로 남음)
- [x] #16 skip 사유 reasons 오염 → Batch 6 참조
- [x] #19 EXIF/GPS PIL 폴백 경고 → Batch 6 참조 (테스트 실행 로그에서 실제 warning 발생 확인:
`preprocessing.py:52 external derivative fell back to original bytes…`)
- [x] #18 dedup 핫패스 타깃 쿼리 → Batch 6 참조
- [ ] #20 ScoreResult.reasons tuple → Batch 6 의 연기 항목과 동일 건