I continued with the security-review step from the plan and tightened the API contract so internal storage locations no longer leak out of normal responses.

Changes:

  - src/main/java/org/example/videoclips/application/VideoAssetService.java:401 no longer exposes sourceObjectKey on asset reads or objectKey on clip listings.
  - src/main/java/org/example/videoclips/processing/ClipProcessor.java:64 now records a sanitized SOURCE_MATERIALIZED event message instead of logging the worker-local path.
  - src/test/java/org/example/videoclips/api/VideoAssetControllerTest.java:67 now asserts those fields are absent and that job events do not contain uploads/ or tmp/.
This commit is contained in:
JSLMPR 2026-07-09 00:53:12 +02:00
parent 73070824df
commit 0d45f898f7
3 changed files with 15 additions and 9 deletions

View File

@ -405,7 +405,6 @@ public class VideoAssetService {
response.put("contentType", asset.contentType());
response.put("contentLengthBytes", asset.contentLengthBytes());
response.put("clipProfile", asset.clipProfile());
response.put("sourceObjectKey", asset.sourceObjectKey());
response.put("status", asset.status().name());
response.put("uploadId", asset.uploadId());
response.put("sourceDurationSeconds", asset.sourceDurationSeconds());
@ -417,7 +416,6 @@ public class VideoAssetService {
private Map<String, Object> toClipResponse(Clip clip) {
return Map.of(
"clipId", clip.id(),
"objectKey", clip.objectKey(),
"clipIndex", clip.clipIndex(),
"startSeconds", clip.startSeconds(),
"durationSeconds", clip.durationSeconds(),

View File

@ -63,7 +63,7 @@ public class ClipProcessor {
}
localInputPath = Path.of(properties.getFfmpeg().getInputDirectory()).resolve(asset.sourceObjectKey());
objectStoragePort.materializeSourceObject(asset.sourceObjectKey(), localInputPath);
recordEvent(jobId, "SOURCE_MATERIALIZED", "Source object staged to " + localInputPath);
recordEvent(jobId, "SOURCE_MATERIALIZED", "Source object staged to worker input storage");
generatedClips = videoClipperPort.generateClips(
jobId,
asset.id(),

View File

@ -6,7 +6,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.isOneOf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@ -65,7 +67,7 @@ class VideoAssetControllerTest {
mockMvc.perform(get("/v1/video-assets/{assetId}", assetId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.assetId").value(assetId))
.andExpect(jsonPath("$.sourceObjectKey").value("uploads/" + uploadId + "/source"))
.andExpect(jsonPath("$.sourceObjectKey").doesNotExist())
.andExpect(jsonPath("$.status").value("UPLOADED"));
mockMvc.perform(get("/v1/video-assets/{assetId}/upload-session", assetId))
@ -121,11 +123,16 @@ class VideoAssetControllerTest {
mockMvc.perform(get("/v1/clip-jobs/{jobId}/clips", jobId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.clips.length()", greaterThanOrEqualTo(3)))
.andExpect(jsonPath("$.clips[0].objectKey").exists());
.andExpect(jsonPath("$.clips[0].objectKey").doesNotExist());
mockMvc.perform(get("/v1/clip-jobs/{jobId}/events", jobId))
MvcResult eventsResult = mockMvc.perform(get("/v1/clip-jobs/{jobId}/events", jobId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.events.length()", greaterThanOrEqualTo(1)));
.andExpect(jsonPath("$.events.length()", greaterThanOrEqualTo(1)))
.andReturn();
String eventsBody = eventsResult.getResponse().getContentAsString();
assertFalse(eventsBody.contains("uploads/"));
assertFalse(eventsBody.contains("tmp/"));
}
@Test
@ -220,18 +227,19 @@ class VideoAssetControllerTest {
String clipsResponse = mockMvc.perform(get("/v1/clip-jobs/{jobId}/clips", jobId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.clips[0].objectKey").doesNotExist())
.andReturn()
.getResponse()
.getContentAsString();
String clipId = JsonTestHelper.read(clipsResponse, "$.clips[0].clipId");
String objectKey = JsonTestHelper.read(clipsResponse, "$.clips[0].objectKey");
mockMvc.perform(post("/v1/clips/{clipId}/download-url", clipId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.clipId").value(clipId))
.andExpect(jsonPath("$.url").value(org.hamcrest.Matchers.containsString(objectKey)));
.andExpect(jsonPath("$.downloadUrl").exists())
.andExpect(jsonPath("$.expiresAt").exists());
}
@Test