# 등록 순서 — 이 순서가 결과를 정한다 .authorizeHttpRequests( auth -> { if (publicPaths.length > 0) { auth.requestMatchers(publicPaths).permitAll(); } // Ordered before the authenticated catch-all: a management path must be refused at // the transport, not by an application policy the request has already passed. for (RestrictedPathRule rule : restricted) { auth.requestMatchers(rule.pathPattern()).hasAnyAuthority(rule.authorities()); } auth.anyRequest().authenticated(); }) # 규칙 자신은 약해지는 것을 막는다 30: if (requiredAuthorities.isEmpty()) { 31- throw new IllegalArgumentException( 32- "requiredAuthorities must not be empty: a rule that requires nothing is weaker than the " 33- + "authenticated default it replaces"); 34- } # 규칙을 내놓는 자리 main · FileserverAdminPlaneConfiguration.java:36: return new RestrictedPathRule(ADMIN_PATH_PATTERN, settings.security().adminRoles()); # 프로덕션이 등록하는 패턴과 그것을 켜야 하는 두 스위치 @ConditionalOnProperty( prefix = "app.fileserver-platform.admin", name = "enabled", havingValue = "true") static final String ADMIN_PATH_PATTERN = "/internal/fileserver/**"; -- 그 설정 클래스를 이름으로 부르는 자리 전부 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminManagementContextConfiguration.java:20: * role gate contributed by {@link FileserverAdminPlaneConfiguration} stays: the two guards cover src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminPlaneConfiguration.java:29:public class FileserverAdminPlaneConfiguration { src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java:71: FileserverAdminPlaneConfiguration.class, -- 그 유일한 @Import 가 든 자동설정과 그 조건 @AutoConfiguration @ConditionalOnProperty( prefix = FileserverPlatformSettings.PREFIX, name = "enabled", havingValue = "true") @Import({ FileserverStorageConfiguration.class, FileserverSecurityConfiguration.class, FileserverServiceConfiguration.class, FileserverCleanupConfiguration.class, FileserverAdminPlaneConfiguration.class, FileserverStartupConfiguration.class }) 49: public static final String PREFIX = "app.fileserver-platform"; -- 스캔이 그 패키지를 제외하므로 다른 길이 없다 excludeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @ComponentScan.Filter( type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class), @ComponentScan.Filter( type = FilterType.REGEX, pattern = CaSkeletonApplication.AUTO_CONFIGURED_PACKAGES) }) static final String AUTO_CONFIGURED_PACKAGES = "dev\\.caskeleton\\.bootstrap\\.autoconfigure\\..*" + "|dev\\.caskeleton\\.bootstrap\\.notification\\..*" + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.fileserver\\.admin\\..*" + "|dev\\.caskeleton\\.adapter\\.outbound\\.mongo\\..*" + "|dev\\.caskeleton\\.adapter\\.outbound\\.messaging\\..*" + "|dev\\.caskeleton\\.adapter\\.outbound\\.notification\\..*" + "|dev\\.caskeleton\\.adapter\\.outbound\\.persistence\\..*" + "|dev\\.caskeleton\\.adapter\\.inbound\\.graphql\\..*" + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.error\\..*" + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.budget\\..*" + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.operation\\..*" + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.error\\..*" + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.operation\\..*" + "|dev\\.caskeleton\\.messaging\\..*"; -- 관리 라우트 자체는 관리 컨텍스트에 등록되고 포트는 따로 출하된다 @ManagementContextConfiguration(value = ManagementContextType.ANY, proxyBeanMethods = false) @ConditionalOnProperty( prefix = "app.fileserver-platform", name = {"enabled", "admin.enabled"}, havingValue = "true") @Import(FileserverAdminController.class) 249: port: ${MANAGEMENT_SERVER_PORT:9001} 317: port: ${APP_SERVER_PORT:8080} 694: public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck} 854: enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false} 899: admin-roles: ${APP_FILESERVER_PLATFORM_SECURITY_ADMIN_ROLES:ROLE_FILE_ADMIN} 911: enabled: ${APP_FILESERVER_PLATFORM_ADMIN_ENABLED:false} # 공개 경로를 읽는 자리 — 접근자로 읽는 쪽과 원시 키로 읽는 쪽 test · SecuritySettingsTest.java:54: assertThat(context.getBean(SecuritySettings.class).publicPaths()) test · SecuritySettingsTest.java:67: assertThat(settings.publicPaths()).containsExactly("/healthcheck"); test · SecuritySettingsTest.java:68: assertThat(settings.publicPaths()).isUnmodifiable(); main · SecurityConfig.java:75: String[] publicPaths = securitySettings.publicPaths().toArray(new String[0]); -- 저장소 전체에서 그 키를 쥔 파일. 점 표기와 환경 변수와 중첩 YAML 세 형태로 찾는다 ~ .superpowers/sdd/2026-08-02-warning-zero-build-refactoring/task-8-gradle-review.diff docs/security/public-paths-snapshot.txt docs/superpowers/plans/2026-07-25-ci-control-plane-recovery.md docs/superpowers/plans/2026-07-28-notification-production-capability.md ~ src/.env src/README.md src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/CorsSecurityFilterIntegrationTest.java src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/JwtJwksSecurityFilterIntegrationTest.java src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/SecurityModeWebContractTest.java src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/settings/SecuritySettingsTest.java src/app-bootstrap/src/functionalTest/java/dev/caskeleton/bootstrap/contract/BuildVerificationPurityContractTest.java src/app-bootstrap/src/main/resources/application-local.yml src/app-bootstrap/src/main/resources/application.yml src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/security/PublicHealthPathAgreementTest.java src/app-bootstrap/src/test/resources/application-test.yml src/gradle/public-path-snapshot.gradle src/sample-portfolio/src/main/resources/application.yml src/sample-portfolio/src/test/resources/application-test.yml 전부 / 그중 추적되지 않는 것(~) : 19 / 2 그중 제한 패턴을 함께 언급하는 파일 : (0) # 겹침을 보는 것이 있는가 — 규칙 이름을 쓰는 자리 전부와 공개 경로 스냅숏 게이트 src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/RestrictedPathRule.java src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/SecurityConfig.java src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminPlaneConfiguration.java 공개 경로 스냅숏 게이트가 있는 파일 : src/gradle/public-path-snapshot.gradle 그 게이트가 읽는 두 입력 : File publicPathEnvironmentFile = rootProject.file('.env') rootProject.file('../docs/security/public-paths-snapshot.txt') 값 쪽 입력이 추적되는가 : .gitignore:7:src/.env* src/.env 그 입력이 없으면 : if (!environmentFile.isFile()) { throw new GradleException( "missing public-path environment file ${environmentFile}") 그런데도 그 태스크를 부르는 자리 : .github/workflows/ci-quality-gates.yml:50: run: ./gradlew check verifyPublicPathSnapshot verifyDependencyLocks --warning-mode=fail --no-daemon --stacktrace .github/workflows/ci-quality-gates.yml:117: verifyPublicPathSnapshot .github/workflows/notification-platform.yml:97: ./gradlew verifyEnvKeys verifyPublicPathSnapshot --console=plain .github/workflows/web-release.yml:48: verifyPublicPathSnapshot 워크플로 전체에서 .env 를 입에 올리는 자리 전부 : .github/workflows/fileserver-pr.yml:22: - 'src/.env' .github/workflows/notification-platform.yml:30: - 'src/.env' 그 게이트가 제한 패턴을 언급하는가 : 0