diff --git a/.jules/sentinel.md b/.jules/sentinel.md index e795cb9dd..755e4b27f 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -32,3 +32,8 @@ **Vulnerability:** The document hashing routine in `DefaultDocumentConversionService` processed file streams without enforcing any maximum size limit on the bytes read. An attacker could exploit this by uploading a maliciously large stream (or exploiting a compression bomb if unzipping), exhausting system memory, CPU, or disk space (DoS). **Learning:** Checking the declared file size (e.g., `file.getSize()`) in initial validation is not always sufficient if the input stream itself can be spoofed or dynamically expanded during reading. The actual bytes read must be verified against bounds continuously. **Prevention:** Always enforce a strict, configurable size limit (e.g., `ConversionProperties.maxUploadSizeBytes`) within the `while` loop that reads from untrusted input streams. Track `totalRead` and throw an exception immediately if the limit is exceeded. + +## 2026-08-19 - 런타임 시크릿 설정 방식 변경 +**Vulnerability:** 환경 변수를 통해 런타임 시크릿(artifact token secret 및 tenant claims HMAC secret)을 직접 주입하고 있었으며, 이는 컨테이너나 프로세스 환경에 민감한 정보가 노출될 위험이 존재했습니다. +**Learning:** `AGENTS.md`의 보안 지침에 따라 환경 변수는 런타임 애플리케이션의 설정 값이 아닌 KV 저장소(configtree 등)를 부트스트랩하기 위한 경로로만 사용되어야 합니다. 또한 Spring Boot 설정에서 `@Value` 어노테이션의 기본값을 완전히 제거하여 설정 트리의 값이 없을 경우 빈 문자열로 덮어씌워지지 않도록 해야 합니다. +**Prevention:** 향후 런타임 시크릿 및 권한 증명과 관련된 새로운 변수가 추가될 때, 환경 변수 주입 방식이 아닌 반드시 KV 기반의 조회 방식(configtree 마운트 등)과 `@Value("${property.name}")` 형식의 기본값 없는 엄격한 주입을 사용해야 합니다. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1187deb2a..87881b490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,3 +70,6 @@ - 루트 `LICENSE`와 Maven license metadata를 추가해 Scorecard License alert가 표준 Apache-2.0 파일을 확인할 수 있게 했습니다. - logback-core 신규 권고(GHSA-jhq6-gfmj-v8fx) 대응을 위해 Logback 관리 버전을 `1.5.35`로 고정했습니다. - 저장소 보안 정책, Maven/GitHub Actions Dependabot 설정, 기본 CodeQL/중앙 SAST 운영 지침, 다운로드 파일명 정규화 Jazzer fuzz target을 추가해 Scorecard 보안 거버넌스 신호를 보강했습니다. + +### 보안 (Security) +- 런타임 환경 변수 대신 Spring Boot configtree를 통해 인증 시크릿(artifact token secret 및 tenant claims HMAC secret)을 로드하도록 마이그레이션했습니다. diff --git a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java index 79a972e3d..bcee94fda 100644 --- a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java +++ b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java @@ -79,7 +79,7 @@ public class ArtifactLinkService { public ArtifactLinkService( ArtifactStore artifactStore, ArtifactLinkLedger artifactLinkLedger, - @Value("${clearfolio.artifact-token.secret:}") String configuredSecret) { + @Value("${clearfolio.artifact-token.secret}") String configuredSecret) { this(artifactStore, artifactLinkLedger, configuredSecret, Clock.systemUTC(), new SecureRandom()); } diff --git a/src/main/java/com/clearfolio/viewer/auth/TenantAccessService.java b/src/main/java/com/clearfolio/viewer/auth/TenantAccessService.java index ab02e96a7..4531e0469 100644 --- a/src/main/java/com/clearfolio/viewer/auth/TenantAccessService.java +++ b/src/main/java/com/clearfolio/viewer/auth/TenantAccessService.java @@ -47,7 +47,7 @@ public TenantAccessService() { */ @Autowired public TenantAccessService( - @Value("${clearfolio.tenant-claims.hmac-secret:}") String claimsHmacSecret, + @Value("${clearfolio.tenant-claims.hmac-secret}") String claimsHmacSecret, @Value("${clearfolio.tenant-claims.max-skew-seconds:300}") long maxSkewSeconds) { this(claimsHmacSecret, maxSkewSeconds, Clock.systemUTC()); } diff --git a/src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java b/src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java index dfeb00a59..9752c40dd 100644 --- a/src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java +++ b/src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java @@ -18,7 +18,7 @@ public class ProductionAuthReadinessConfig { * @param tenantClaimsSecret shared gateway signing secret */ public ProductionAuthReadinessConfig( - @Value("${clearfolio.tenant-claims.hmac-secret:}") String tenantClaimsSecret) { + @Value("${clearfolio.tenant-claims.hmac-secret}") String tenantClaimsSecret) { if (!StringUtils.hasText(tenantClaimsSecret)) { throw new IllegalStateException( "production profile requires clearfolio.tenant-claims.hmac-secret" diff --git a/src/main/resources/application-buyer-demo.yml b/src/main/resources/application-buyer-demo.yml index 4e4df4091..56e5437ac 100644 --- a/src/main/resources/application-buyer-demo.yml +++ b/src/main/resources/application-buyer-demo.yml @@ -23,9 +23,7 @@ viewer: clearfolio: artifact-token: - secret: ${CLEARFOLIO_ARTIFACT_TOKEN_SECRET:} tenant-claims: - hmac-secret: ${CLEARFOLIO_TENANT_CLAIMS_HMAC_SECRET:} max-skew-seconds: ${CLEARFOLIO_TENANT_CLAIMS_MAX_SKEW_SECONDS:300} artifact-link-ledger: path: ${CLEARFOLIO_ARTIFACT_LINK_LEDGER_PATH:} diff --git a/src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java b/src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java index f00ba4c62..a15252cb0 100644 --- a/src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java +++ b/src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java @@ -3,14 +3,19 @@ import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.test.context.runner.ApplicationContextRunner; class ProductionAuthReadinessConfigTest { @Test void productionProfileFailsWithoutSignedTenantClaimsSecret() { - productionRunner().run(context -> assertThat(context.getStartupFailure()) - .hasRootCauseMessage("production profile requires clearfolio.tenant-claims.hmac-secret")); + productionRunner() + .withPropertyValues("clearfolio.tenant-claims.hmac-secret=") + .run(context -> { + assertThat(context.getStartupFailure()).isNotNull(); + assertThat(context.getStartupFailure()).hasRootCauseMessage("production profile requires clearfolio.tenant-claims.hmac-secret"); + }); } @Test