2.8 KiB
2.8 KiB
title, source_type, status, related_projects, tags, created
| title | source_type | status | related_projects | tags | created | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| error / ca-public-path-snapshot-scope-violation | error-note | raw |
|
|
2026-06-15 |
error: actuator probe 노출을 위해 SECURITY_PUBLIC_PATHS 를 넓혀 deny-by-default 스냅샷 게이트를 깨뜨림
Parent: raw/project-notes/ca-skeleton-operational-contract.
chore-ca-parallel-run-runtime-ops-4contracts작업의 f1 리뷰에서 ca-architect-sentinel이 FAIL로 차단했으며, 해당 작업은 별도 branch-note가 남아 있지 않다.
증상
feature-runtime-health-lifecycle-contract(health probe shape 만 소유) 구현 중, 쿠버네티스 kubelet 이 JWT 없이 호출하는 actuator probe 가 401 나는 걸 피하려고 src/.env 의
SECURITY_PUBLIC_PATHS=/api/healthcheck 를
/api/healthcheck,/actuator/health/liveness,/actuator/health/readiness,/actuator/health/startup 로 확장.
결과: ./gradlew verifyPublicPathSnapshot FAIL —
verifyPublicPathSnapshot: the deny-by-default public path surface changed.
expected (snapshot): /api/healthcheck
actual (SECURITY_PUBLIC_PATHS): /actuator/health/{liveness,readiness,startup}, /api/healthcheck
스냅샷(docs/security/public-paths-snapshot.txt)을 재생성하지 않아 게이트가 막음.
근본 원인 (2가지)
- 소유권 경계 위반: 노출/인증/포트는 병렬 계약
feature-management-actuator-security-contract가 소유(actuator 를 별도 management 포트 9001 로 분리 → 앱 8080 public 표면에 actuator 가 아예 안 올라감). health 계약은 probe shape 만 소유. 한 계약이 다른 계약의 표면을 건드림. - 의미적 무효: management 포트가 9001 로 분리되면
/actuator/health/*는 8080 에 존재하지 않음 → public-path 에 추가해도 죽은 경로. 게다가 deny-by-default 보안 표면을 승인 없이 확장.
해결
SECURITY_PUBLIC_PATHS를/api/healthcheck로 revert(Remedy B). probe 인증은 별도 포트(f2) 가 처리.- health probe 테스트는 HTTP/SecurityFilterChain 비간섭 프로그램적 검증(
ApplicationContextRunner+StatusAggregator)으로 작성 → public-path 를 건드릴 이유 자체가 없음.
교훈
verifyPublicPathSnapshot는SECURITY_PUBLIC_PATHS(SSOT insrc/.env) 변화만 본다. actuator 를 별도 포트로 두면 앱 public 표면 불변 → 게이트 통과. 의도된 public 변경은./gradlew verifyPublicPathSnapshot -PapprovePublicPathChange로 스냅샷 재생성(+보안 리뷰).- 병렬 계약 디스패치 시 NON-goal(다른 계약 소유 표면 금지) 을 프롬프트에 명시하면 이런 침범을 사전 차단. 사후엔 sentinel + 게이트가 잡는다(이번엔 둘 다 잡음).