harden(security): 배포 전 보안·정확성 하드닝 (6커밋)#40
Merged
Conversation
배포 전 보안 점검의 블로커#1 + 코어 DoS/정보노출 하드닝. - http: assert_public_url(SSRF 가드) — getaddrinfo로 해석한 IP가 private/loopback/ link-local/reserved/multicast/unspecified면 거부(메타데이터 169.254.169.254·localhost·내부망 차단). - feeds_fetch: get_text(guard_ssrf=True, max_bytes=16MB) — 사용자 임의 URL의 SSRF 차단 + 스트리밍 크기 컷오프(메모리 DoS 방지). 가드는 get_text 내부로 두어 단위테스트 무네트워크 유지. - http: NetworkError가 _safe_url로 scheme://host만 노출(telegram /bot<token>/·serviceKey 누출 방지). - http: Retry.max_delay(기본 30s) + _retry_delay 상한 — 거대 Retry-After 자기-DoS 차단. - http: UpstreamError가 text payload를 2KB로 절단(dict는 보존). - server: FastMCP(mask_error_details=True) — 미처리 예외 원문이 클라이언트로 누출되지 않게. 882 passed(+15: SSRF 차단/통과·토큰 마스킹·payload 절단·retry 캡·max_bytes 컷오프·feeds e2e) · ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
배포 전 점검의 OAuth 하드닝. - oauth: state를 생성했으면(authorize_url_for_login 호출) exchange_code에 state 필수 — state를 떼고 code만 넘기는 token-fixation/CSRF 우회 차단(이전엔 둘 다 있을 때만 비교). - oauth: 손상된 credentials.json을 .bak으로 비키고 빈 상태로 폴백(JSONDecodeError가 모든 토큰 읽기를 막던 문제 제거). - oauth: _post_token이 access_token 없는 응답을 KeyError 대신 명확한 RuntimeError로 거부. - cli(auth): REFRESH_TOKEN을 기본 마스킹(앞4…뒤4), 전체 값은 --show-token opt-in 뒤로 — 터미널 스크롤백·CI 로그 평문 잔존 방지. - 참고: 인-프로세스 lost-update는 update()에 await가 없어 원자적이라 락 불필요(크로스프로세스는 filelock 의존성 필요 → 범위 외). 885 passed(+3: state 필수/생략·토큰응답 검증·손상파일 복구) · ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
배포 전 점검의 블로커#2(.claude가 PyPI sdist로 누출) + 공급망 하드닝. - .gitignore: `.claude/`·`*.bak`를 레포 .gitignore에 명시(전역 ignore 의존 제거 — hatchling sdist 선택이 레포 .gitignore를 따르므로 누출 차단). - pyproject: [tool.hatch.build.targets.sdist] exclude로 .claude·.github·dist·docs/local· .env*·credentials.json·*.bak를 sdist에서 제외(이중 방어). 빌드 후 sdist에 민감파일 0건 검증. - release.yml: (1) 태그 push 시 v<tag>와 __version__ 일치 게이트, (2) `rm -rf dist build` 후 빌드, (3) sdist에 .claude/.env/.git/credential 포함 시 publish 실패 가드. - stale dist/ 아티팩트(구 패키지명 arcsolve_mcp) 로컬 제거. - 의존성 상한 캡은 두지 않음: fastmcp 3.x가 최신 httpx/pydantic를 요구해 캡이 fastmcp를 깨진 2.x로 강등시킴(검증). 호환성은 uv.lock 고정으로 관리. 885 passed · ruff clean · sdist 민감파일 0건. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
배포 전 점검의 서비스 레벨 하드닝.
- transport: data.go.kr 6종(airkorea·egen·ev_charger·airport·parking·tago_transit)의
BASE_URL을 http→https로 전환(apis.data.go.kr TLS 응답 확인). API 키가 쿼리에 실리므로
평문 MITM 탈취 위험 제거. seoul_transit 2종은 상류가 HTTPS 미응답이라 http 유지 + README에
키 노출 주의 명시.
- disclosure: 15개 서비스 _explain의 `detail or ' ' + str(e.payload)` 폴백 제거 — 상류
비-JSON(HTML/XML) 원문이 도구 응답으로 새던 경로 차단(추출된 detail만 사용, status는 유지).
- validation: kakao text·discord content에 min_length=1 — 빈 본문이 상류 400을 만나기 전에 차단.
- correctness: seoul_subway_arrivals 경과시간을 KST-aware로 계산(naive 호스트 로컬타임 비교 →
ZoneInfo("Asia/Seoul")). UTC+10~+14 호스트의 오표시 제거.
887 passed(+2: kakao/discord 빈문자열 거부) · ruff clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
배포 전 점검의 파싱 하드닝(심층방어). 최신 expat은 XXE/billion-laughs를 기본 차단하나, requires-python>=3.11이라 구형 expat 환경의 회귀를 대비. - 코어 arcsolve/xml.py: safe_fromstring() — defusedxml로 파싱하고 악의적 구조(외부 엔티티· 엔티티 확장)는 ET.ParseError로 정규화(기존 except ParseError 경로가 그대로 처리). - 서비스 5종(arxiv·egen·feeds·ev_charger·pubmed)의 ET.fromstring(8개 호출)을 코어 safe_fromstring으로 교체(서비스 폴더에 defusedxml 직접 의존 없이 코어 경유 — 규칙2). - defusedxml>=0.7.1 의존성 추가(순수 파이썬, fastmcp 3.3.1 해석 유지 확인). 891 passed(+4: 정상 파싱·billion-laughs/XXE 거부·malformed) · ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8ebacd0 to
5f92c1a
Compare
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.
무엇 / 왜
배포 전 보안·정확성 점검(7렌즈 워크플로우, 적대적 검증) 결과를 반영한 하드닝 6커밋. 기능 PR(#35·#36·#41·#38·#39) 머지 후 main 위로 리베이스해 하드닝만 담는다.
🔴 블로커 (해결)
assert_public_url로 내부망/메타데이터/loopback 차단 +get_text(guard_ssrf, max_bytes)..claudePyPI sdist 누출 —.gitignore·hatch sdistexclude·release 빌드후 가드.🟠 곧 고칠 것 (해결)
mask_error_details+ NetworkError URL 마스킹 · data.go.kr 6종 HTTPS · OAuth state 필수화(CSRF) · release 태그↔버전 게이트.🟡 하드닝 (해결)
응답 크기 컷오프 ·
defusedxml(코어arcsolve/xml.py, XXE/billion-laughs) ·str(e.payload)노출 제거(15종) · UpstreamError 절단 · retry 캡 · 손상 cred 복구 · 토큰 응답 검증 · REFRESH_TOKEN 마스킹 · seoul KST 타임존 · kakao/discord 빈입력 차단.⏭️ 의도적 보류
토큰스토어 크로스프로세스 락(in-proc 원자적·cross-proc은 filelock 필요) · publish environment(PyPI publisher와 동시 변경 필요) · 의존성 상한 캡(fastmcp 3.x가 최신 httpx/pydantic 요구 → 캡 시 깨진 2.x 강등).
검증
891 passed(+34 보안 테스트) ·ruffclean · sdist 민감파일 0건.🤖 Generated with Claude Code