Files
llm-wiki/raw/errors/ca-public-path-snapshot-scope-violation.md
T

42 lines
2.8 KiB
Markdown

---
title: error / ca-public-path-snapshot-scope-violation
source_type: error-note
status: raw
related_projects: [ca-skeleton]
tags: [error, ca-skeleton, security, spring-security, actuator, guardrail, scope-discipline]
created: 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가지)
1. **소유권 경계 위반**: 노출/인증/포트는 병렬 계약 `feature-management-actuator-security-contract` 가 소유(actuator 를 **별도 management 포트 9001** 로 분리 → 앱 8080 public 표면에 actuator 가 아예 안 올라감). health 계약은 probe **shape** 만 소유. 한 계약이 다른 계약의 표면을 건드림.
2. **의미적 무효**: 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 in `src/.env`) 변화만 본다. actuator 를 별도 포트로 두면 앱 public 표면 불변 → 게이트 통과. 의도된 public 변경은 `./gradlew verifyPublicPathSnapshot -PapprovePublicPathChange` 로 스냅샷 재생성(+보안 리뷰).
- 병렬 계약 디스패치 시 **NON-goal(다른 계약 소유 표면 금지)** 을 프롬프트에 명시하면 이런 침범을 사전 차단. 사후엔 sentinel + 게이트가 잡는다(이번엔 둘 다 잡음).