Skip to content

적대적 코드 리뷰: src/kma 정확성·보안·성능 수정 + CI 추가 - #25

Merged
digitie merged 1 commit into
mainfrom
review/adversarial-src-kma
Aug 29, 2026
Merged

digitie merged 1 commit into
mainfrom
review/adversarial-src-kma

Conversation

@digitie

@digitie digitie commented Aug 29, 2026

Copy link
Copy Markdown
Owner

개요

src/kma의 손으로 작성한 로직 파일(src/kma/apihub_endpoints.py, tools/update_apihub_endpoints.py가 생성하는 13,589줄짜리 자동생성 470개 endpoint wrapper 파일은 범위에서 제외)을 4명의 독립된 전문 리뷰어 서브에이전트(정확성/동시성, 보안, API 설계, 성능/신뢰성)가 적대적으로 리뷰했습니다. 각 발견은 별도 검증 에이전트가 실제 코드를 다시 읽고 반박을 시도한 뒤 확정했습니다. (방법론: digitie/python-mois-api#9, digitie/python-krforest-api#10)

  • 리뷰어 4명 원시 발견 34건, 확정 후 수정 적용, 4건은 반박되어 기각

가장 중요한 수정 — pagination 데이터 손실/중복

  • pagination.py: iter_pages()가 응답 body가 자체 보고하는 pageNo를 그대로 신뢰해 다음 페이지를 계산했습니다. 어떤 operation이 pageNo를 제대로 echo하지 않거나(또는 항상 고정값을 돌려주면), iter_pages()는 같은 페이지에서 멈춘 채 max_pages번까지 같은 페이지를 반복 재요청하면서 호출자에게는 서로 다른 후속 페이지인 것처럼 반환했습니다. → 서버가 실제로 요청받은 페이지 번호를 로컬에서 직접 추적하도록 수정.
  • pagination.py: pageNo/numOfRows/totalCount가 "10.0"처럼 소수점 형태 숫자로 오면 int() 파싱이 실패해 조용히 기본값(sentinel)으로 대체됐고, 이 값이 has_next_page()를 False로 뒤집어 더 가져올 페이지가 있는데도 아무 표시 없이 순회가 중단됐습니다. → 소수점 형태 숫자 파싱을 허용. max_pages에 도달했는데 실제로 더 가져올 페이지가 남아있으면 이제 PaginationLimitWarning을 발생시킵니다(이전엔 완전히 무음). 비동기 aiter_pages() 신규 추가, ApiHubClient의 iter_pages/aiter_pages에도 동일 수정 적용.

그 외 주요 수정

  • client.py: header: null(또는 Mapping이 아닌 header)인 JSON envelope가 typed KmaParseError 대신 처리되지 않은 AttributeError로 크래시하던 문제
  • apihub.py: ApiHubResponse.text가 실제 응답 Content-Type의 charset이 아니라 httpx의 기본 추정 charset으로 디코딩되던 문제
  • apihub.py: open_api()/aopen_api()가 결과 코드를 확인하지 않고 오류 응답을 성공으로 그대로 반환하던 문제
  • apihub.py: base_url을 검증 없이 받아들이던 문제 → apihub.kma.go.kr 호스트 allowlist 추가

검증

  • python -m pytest -q -m "not integration": 149 passed, 12 deselected
  • 실제 서버 대상 live 검증: KMA_RUN_LIVE=1 python -m pytest -m integration (이 체크아웃의 .env/.env.local에 있는 data.go.kr·APIHub 키 사용) → 9 passed, 3 skipped(서비스키가 구독하지 않은 특정 data.go.kr operation, 회귀 아님)
  • python -m ruff check .: 통과
  • python -m mypy src/kma: 통과 (strict)

추가: CI 워크플로

.github/workflows/ci.yml 신규 추가 — lint/typecheck/test(Python 3.10/3.11/3.12/3.13, not integration 마커 제외) 3개 job.


🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 5 [email protected]

… performance fixes

4 independent reviewer subagents (correctness/concurrency, security, API design,
performance/reliability) audited the hand-written src/kma files (apihub_endpoints.py,
a 13,589-line auto-generated wrapper file, was excluded from scope); every finding
was adversarially re-verified by a separate skeptic agent (reading the actual code +
reproducing the failure) before being applied. 34 raw findings, 4 refuted.

Highlights:
- pagination.py: iter_pages() trusted the response body's self-reported pageNo to
  compute the next page instead of the page it actually requested, so an operation
  that doesn't echo pageNo correctly (or always returns a fixed value) made iter_pages
  stall on one page and silently re-fetch/duplicate it up to max_pages times, returned
  to the caller as if they were distinct subsequent pages -- now increments a
  locally-tracked page number instead
- pagination.py: pageNo/numOfRows/totalCount that arrive as decimal-formatted numbers
  ("10.0") failed int() parsing and silently fell back to a sentinel default, which
  could flip has_next_page() to False and truncate a real result set with zero
  indication -- now tolerant of decimal-formatted numeric fields; added an explicit
  PaginationLimitWarning when max_pages is hit while more pages were genuinely
  available (previously fully silent); added an async aiter_pages() counterpart, and
  the same fixes applied to ApiHubClient's iter_pages/aiter_pages
- client.py: a JSON envelope with header: null (or any non-mapping header) crashed
  parsing with a raw, unhandled AttributeError instead of the library's typed
  KmaParseError
- apihub.py: ApiHubResponse.text decoded via httpx's default charset guess instead of
  the response's actual declared Content-Type charset; open_api()/aopen_api() returned
  error responses as success without checking the embedded result code; base_url was
  accepted without validation (added an apihub.kma.go.kr host allowlist)

Also adds .github/workflows/ci.yml (lint/typecheck/test on Python 3.10-3.13).

Verified against real servers: `KMA_RUN_LIVE=1 pytest -m integration` with the
data.go.kr and APIHub keys present in this checkout's .env/.env.local -- 9 passed,
3 skipped (service key not subscribed to those specific data.go.kr operations, not a
regression).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XF9V2q4mAmhmXn6t5G9Hfe
@digitie
digitie merged commit 32dcbb5 into main Aug 29, 2026
6 checks passed
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