From 4f825f3441923d54efc59d6fbe0300175b645726 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 01:11:18 +0900 Subject: [PATCH 1/4] test(security): require explicit conversion job authority --- .../ConversionJobTenantAuthorityTest.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/test/java/com/clearfolio/viewer/model/ConversionJobTenantAuthorityTest.java diff --git a/src/test/java/com/clearfolio/viewer/model/ConversionJobTenantAuthorityTest.java b/src/test/java/com/clearfolio/viewer/model/ConversionJobTenantAuthorityTest.java new file mode 100644 index 00000000..cf0a1684 --- /dev/null +++ b/src/test/java/com/clearfolio/viewer/model/ConversionJobTenantAuthorityTest.java @@ -0,0 +1,91 @@ +package com.clearfolio.viewer.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +/** + * Proves that the tenant-aware conversion-job constructor never manufactures + * production authority when explicit tenant or subject claims are absent. + */ +class ConversionJobTenantAuthorityTest { + + @Test + void explicitConstructorRejectsMissingTenantAuthority() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new ConversionJob( + UUID.randomUUID(), + null, + "subject-a", + "report.pdf", + "application/pdf", + "hash", + 10L, + 3 + ) + ); + + assertEquals("tenantId must not be blank", exception.getMessage()); + } + + @Test + void explicitConstructorRejectsBlankTenantAuthorityAfterSanitization() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new ConversionJob( + UUID.randomUUID(), + " \u0000 ", + "subject-a", + "report.pdf", + "application/pdf", + "hash", + 10L, + 3 + ) + ); + + assertEquals("tenantId must not be blank", exception.getMessage()); + } + + @Test + void explicitConstructorRejectsMissingSubjectAuthority() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new ConversionJob( + UUID.randomUUID(), + "tenant-a", + null, + "report.pdf", + "application/pdf", + "hash", + 10L, + 3 + ) + ); + + assertEquals("subjectId must not be blank", exception.getMessage()); + } + + @Test + void explicitConstructorRejectsBlankSubjectAuthorityAfterSanitization() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new ConversionJob( + UUID.randomUUID(), + "tenant-a", + " \u0000 ", + "report.pdf", + "application/pdf", + "hash", + 10L, + 3 + ) + ); + + assertEquals("subjectId must not be blank", exception.getMessage()); + } +} From 4411b1dddce332c7ce86d79b9475c146a6f4939e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 01:15:52 +0900 Subject: [PATCH 2/4] fix(security): require explicit conversion job authority --- .../viewer/model/ConversionJob.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/clearfolio/viewer/model/ConversionJob.java b/src/main/java/com/clearfolio/viewer/model/ConversionJob.java index c5b553ba..d6054c06 100644 --- a/src/main/java/com/clearfolio/viewer/model/ConversionJob.java +++ b/src/main/java/com/clearfolio/viewer/model/ConversionJob.java @@ -82,7 +82,12 @@ public ConversionJob( } /** - * Creates a conversion job with tenant and subject ownership metadata. + * Creates a conversion job with explicit tenant and subject ownership metadata. + * + *

This authority-bearing constructor fails closed when tenant or subject + * claims are absent. Development callers that intentionally use demo + * authority must use one of the convenience constructors that supplies the + * demo identities explicitly.

* * @param jobId job identifier * @param tenantId tenant isolation boundary @@ -104,8 +109,8 @@ public ConversionJob( int maxAttempts ) { this.jobId = jobId; - this.tenantId = normalizeOrDefault(tenantId, DEFAULT_TENANT_ID); - this.subjectId = normalizeOrDefault(subjectId, DEFAULT_SUBJECT_ID); + this.tenantId = requireAuthority(tenantId, "tenantId"); + this.subjectId = requireAuthority(subjectId, "subjectId"); this.originalFileName = sanitize(originalFileName); this.contentType = sanitize(contentType); this.contentHash = contentHash; @@ -125,6 +130,14 @@ private String sanitize(String value) { return value.replace("\u0000", ""); } + private String requireAuthority(String value, String fieldName) { + String sanitized = sanitize(value); + if (sanitized == null || sanitized.isBlank()) { + throw new IllegalArgumentException(fieldName + " must not be blank"); + } + return sanitized.strip(); + } + private String normalizeOrDefault(String value, String fallback) { String sanitized = sanitize(value); if (sanitized == null || sanitized.isBlank()) { From 0670e1660377bcdd26c746324f7bd5a382828285 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 01:16:42 +0900 Subject: [PATCH 3/4] test(security): remove demo fallback expectation for explicit authority --- .../viewer/model/ConversionJobTest.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java b/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java index dafeb708..e34f4abb 100644 --- a/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java +++ b/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java @@ -76,24 +76,6 @@ void constructorAcceptsExplicitTenantMetadata() { assertFalse(job.belongsToTenant("tenant-b")); } - @Test - void constructorFallsBackToDemoMetadataForBlankTenantClaims() { - ConversionJob job = new ConversionJob( - UUID.randomUUID(), - " \u0000 ", - null, - "report.docx", - "application/octet-stream", - "hash", - 10L, - 3 - ); - - assertEquals(TenantContext.DEMO_TENANT_ID, job.getTenantId()); - assertEquals(TenantContext.DEMO_SUBJECT_ID, job.getSubjectId()); - assertFalse(job.belongsToTenant(null)); - } - @Test void clampsMaxAttemptsToOneWhenZeroIsConfigured() { ConversionJob job = new ConversionJob( From 5b5b4536640570fb53cff6144ca95605df0bf1c5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 01:19:33 +0900 Subject: [PATCH 4/4] test(model): restore null and blank tenant comparison coverage --- .../java/com/clearfolio/viewer/model/ConversionJobTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java b/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java index e34f4abb..44d2ecd1 100644 --- a/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java +++ b/src/test/java/com/clearfolio/viewer/model/ConversionJobTest.java @@ -55,6 +55,8 @@ void constructorSetsDefaultDemoTenantMetadata() { assertEquals(TenantContext.DEMO_TENANT_ID, job.getTenantId()); assertEquals(TenantContext.DEMO_SUBJECT_ID, job.getSubjectId()); assertTrue(job.belongsToTenant(TenantContext.DEMO_TENANT_ID)); + assertFalse(job.belongsToTenant(null)); + assertFalse(job.belongsToTenant(" \u0000 ")); } @Test