Skip to content

fix(backend): 공개 헬스 엔드포인트가 인프라 상세를 인증 없이 노출하던 문제 - #185

Merged
i3months merged 1 commit into
devfrom
fix/system-health-detail-exposure
Aug 19, 2026
Merged

fix(backend): 공개 헬스 엔드포인트가 인프라 상세를 인증 없이 노출하던 문제#185
i3months merged 1 commit into
devfrom
fix/system-health-detail-exposure

Conversation

@i3months

Copy link
Copy Markdown
Member

변경 사항

마지막 점검에서 나왔다. /api/system/*SecurityConfig 의 permitAll 목록에 있는데, 컴포넌트 상세를 그대로 담고 있었다:

"rabbitmq": { "version": "4.3.5" }
"s3":       { "bucket": "stackup" }
"aiServer": { "queue": "ai.generate.questions", "consumers": 1, "pendingMessages": 0 }

Actuator 는 기본값이 show-details: never 다. 그런데 SystemHealthServiceHealthDescriptor 에서 상세를 직접 꺼내 자기 응답에 담으면서 그 보호를 우회하고 있었다.

  • RabbitMQ 버전 → 알려진 CVE 를 겨냥하는 데 쓰인다
  • 버킷명 → S3 직접 탐침의 출발점
  • 큐 이름·적체량 → 내부 토폴로지와 실시간 부하

이 중 대부분은 오늘 내가 늘렸다. rabbitmq 상세는 #183(키 오타 수정)으로 처음 나오기 시작했고, s3·aiServer 상세는 #184(indicator 구현)로 추가했다. 늘린 김에 닫는다.

수정

ComponentHealthResponse 에서 details 를 제거해 이름·상태만 담는다.

  • 프로브(liveness/readiness) 용도에는 상태만으로 충분하다
  • 상세가 필요하면 호스트에서 Spring 자체 /actuator/health 를 본다. 외부로 라우팅되지 않는 것을 확인했다 — 공개 URL 로 /actuator/health 를 치면 SPA HTML 이 돌아온다(nginx 가 백엔드로 넘기지 않음)
  • 반사로 상세를 꺼내던 extractDetails 도 함께 사라진다(원래도 ReflectiveOperationException 을 삼키던 취약한 코드였다)

프론트·realtime 어디서도 이 엔드포인트를 호출하지 않아(생성 타입에만 존재) 소비자 영향은 없다.

테스트

health_doesNotExposeComponentDetails — 상태는 그대로 전달되지만 응답 타입에 상세 필드가 아예 없다는 것을 RecordComponent 로 못 박는다. 다시 추가하려면 이 테스트를 지워야 하므로 의도가 드러난다.

기존 테스트의 상세 단언은 제거했다(필드 자체가 없어졌다).

함께: frontend/.env.example 의 잘못된 호스트

점검 중 발견. VITE_API_BASE_URL·VITE_SSE_BASE_URLhttps://www.udangtang.site 를 가리키고 있었다. 실제 배포 호스트는 https://stack-up.shop 다 — deploy-app.yml 이 프론트 빌드에 주입하는 값이 진실이다. 새로 온 사람이 그대로 복사하면 존재하지 않는 백엔드를 본다.

참고: VITE_REALTIME_BASE_URLhttp://localhost:38020 으로 남아 있다. 의도적인지(로컬 realtime 을 띄우고 개발) 아니면 같이 어긋난 건지 몰라 건드리지 않았다.

영향 범위

  • DB 마이그레이션: 없음
  • API contract 변경 있음: ComponentHealthResponse.details 제거. 소비자 없음 확인
  • 환경변수: 없음 (.env.example 문서값만 교정)

리뷰어 체크포인트

  • 상세를 완전히 없앨지, 인증된 요청에만 줄지. 공개 프로브 + 호스트 actuator 조합이면 충분하다고 판단했다
  • /api/system/health 를 permitAll 에서 빼는 선택지도 있다. 프로브가 /live·/ready 만 쓰면 가능하다

마지막 점검에서 나왔다. `/api/system/*` 는 permitAll 인데 컴포넌트 상세를 그대로 담고 있었다:

    "rabbitmq": { "version": "4.3.5" }
    "s3":       { "bucket": "stackup" }
    "aiServer": { "queue": "ai.generate.questions", "consumers": 1, "pendingMessages": 0 }

Actuator 는 기본값이 `show-details: never` 다. 그런데 `SystemHealthService` 가 descriptor 에서
상세를 직접 꺼내 자기 응답에 담으면서 **그 보호를 우회**하고 있었다. RabbitMQ 버전은 알려진
CVE 를 겨냥하는 데 쓰이고, 버킷명·큐 이름·적체량은 내부 토폴로지와 부하를 그대로 드러낸다.

`rabbitmq` 상세는 #183(키 오타 수정)으로, `s3`·`aiServer` 상세는 #184(indicator 구현)로
오늘 내가 늘린 것이다 — 늘린 김에 닫는다.

`ComponentHealthResponse` 에서 `details` 를 제거해 **이름·상태만** 담는다. 프로브 용도에는
그것으로 충분하고, 상세가 필요하면 호스트에서 Spring 자체 `/actuator/health` 를 본다
(nginx 가 외부로 라우팅하지 않는 것을 확인했다 — 공개 URL 로는 SPA HTML 이 돌아온다).
반사(reflection)로 상세를 꺼내던 `extractDetails` 도 함께 사라진다.

프론트·realtime 어디서도 이 엔드포인트를 호출하지 않아 소비자 영향은 없다.

테스트: `health_doesNotExposeComponentDetails` — 상태는 전달되지만 응답 타입에 상세 필드가
아예 없다는 것을 record 컴포넌트로 못 박는다.

## 함께: frontend/.env.example 의 잘못된 호스트

`VITE_API_BASE_URL`·`VITE_SSE_BASE_URL` 가 `https://www.udangtang.site` 를 가리키고 있었다.
실제 배포 호스트는 `https://stack-up.shop` 다(deploy-app.yml 이 프론트 빌드에 주입하는 값).
새로 온 사람이 그대로 복사하면 존재하지 않는 백엔드를 보게 된다.
@i3months
i3months merged commit 479929e into dev Aug 19, 2026
5 checks passed
@i3months
i3months deleted the fix/system-health-detail-exposure branch August 19, 2026 06:57
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