Skip to content

feat: impl resizing image logic - #59

Merged
jaejo merged 11 commits into
developfrom
feature/image-resizing
Aug 6, 2026
Merged

jaejo merged 11 commits into
developfrom
feature/image-resizing

Conversation

@jaejo

@jaejo jaejo commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

작업 내용

  • 프로필 이미지 업로드 시 보안 검증(압축 폭탄 방어, 위장 확장자 차단) 강화
  • 업로드된 이미지를 프로필 규격(300×300)으로 자동 리사이징 및 85% 품질 압축 기능 구현

🛠️ 주요 변경 사항

  1. ImageProcessor 컴포넌트 도입 (기존 ImageFileValidator 대체)
  • Thumbnailator 라이브러리를 활용해 프로필 규격(300×300) Center Crop 리사이징 수행
  • 85% 품질 압축(outputQuality(0.85))을 적용하여 파일 저장 용량 절감 및 로딩 성능 최적화
  • 원본 해상도가 300px 미만인 경우, 억제 확대(Upscaling)로 인한 화질 저하를 방지하기 위해 짧은 변 기준 리사이징
  1. 보안 검증 및 OOM 예방 로직 강화
  • Decompression Bomb (압축 폭탄) 방어: 픽셀 전체 디코딩 전 헤더 레이어에서 가로x세로 픽셀 수 상한(최대 1,600만 픽셀 = 4000x4000) 검사
  • 위장 확장자 차단: ImageIO 매직바이트 감지 기반 실제 포맷과 확장자 교차 검증 (클라이언트 Content-Type 헤더 미신뢰)
  • 메모리 보호: readWithLimit으로 파일 용량 상한 초과 시 스트림 읽기 즉시 중단
  1. 저장소(Storage) 및 유틸리티 개선
  • StorageDirectoryValidator를 신설하여 로컬 저장소 저장 시 Path Traversal 방지 및 디렉토리 자동 생성
  • LocalStorageServiceS3StorageServiceImageProcessor를 주입받아 리사이징/압축된 결과 스트림을 저장하도록 통합

변경 사항

체크리스트

  • 테스트 코드 작성 완료
  • 리뷰어 지정 완료

참고 사항

관련 이슈

Summary by CodeRabbit

  • 새로운 기능

    • 프로필 이미지 업로드 시 300×300 크기로 자동 리사이징 및 중앙 크롭을 지원합니다.
    • S3와 로컬 저장소에서 이미지 형식·크기·해상도를 검증하고 최적화합니다.
    • WebP 이미지 처리를 지원합니다.
  • 버그 수정

    • 잘못된 이미지 파일과 확장자 불일치를 업로드 단계에서 차단합니다.
    • 허용되지 않은 경로 접근과 경로 이탈을 방지합니다.
    • 파일 업로드 용량 설정 명칭을 max-file-size로 정리했습니다.

@jaejo
jaejo requested review from Junkov0 and idktomorrow August 4, 2026 11:31
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

이미지 검증과 리사이징을 ImageProcessor로 통합했습니다. 로컬 및 S3 저장소는 처리된 이미지와 감지된 MIME 타입을 사용합니다. 저장 경로 검증을 추가하고 프로필 이미지에 300x300 규격을 적용했습니다.

Changes

이미지 처리 및 저장

Layer / File(s) Summary
이미지 검증 및 리사이징
momogo-core/build.gradle, momogo-core/src/main/java/com/momogo/core/common/util/storage/*, momogo-api/src/main/resources/application.yaml
ImageProcessor가 파일 크기, 확장자, 실제 포맷, 해상도 및 픽셀 수를 검증합니다. 이미지의 중앙 정사각형 크롭과 압축을 수행합니다. WebP 및 Thumbnailator 의존성을 변경했습니다.
저장 경로 검증
momogo-core/src/main/java/com/momogo/core/common/util/storage/StorageDirectoryValidator.java, momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
디렉터리를 정규화하고 절대 경로와 .. 세그먼트를 거부합니다. 로컬 저장 및 삭제 경로가 기준 디렉터리를 벗어나지 않도록 검증합니다.
저장소 처리 결과 연동
momogo-core/src/main/java/com/momogo/core/common/storage/StorageService.java, momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java, momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java
업로드 계약에 ImageResizeSpec을 추가했습니다. 로컬 및 S3 저장소가 검증 결과의 바이트, 확장자 및 MIME 타입을 사용하도록 변경했습니다.
프로필 이미지 업로드 적용
momogo-core/src/main/java/com/momogo/core/domain/user/service/UserServiceImpl.java
프로필 이미지 업로드에 ImageResizeSpec.PROFILE을 전달합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UserServiceImpl
  participant StorageService
  participant ImageProcessor
  participant LocalStorageService
  participant S3StorageService
  UserServiceImpl->>StorageService: 프로필 이미지와 ImageResizeSpec.PROFILE 전달
  StorageService->>ImageProcessor: 이미지 검증 및 리사이징 요청
  ImageProcessor-->>StorageService: 처리된 바이트, 확장자 및 MIME 타입 반환
  StorageService->>LocalStorageService: 처리 결과 저장
  StorageService->>S3StorageService: 처리 결과 저장
Loading

Possibly related PRs

Suggested reviewers: idktomorrow, junkov0

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 이미지 리사이징 로직 구현이라는 주요 변경 사항을 명확하게 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/image-resizing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jaejo jaejo changed the title Feature/image resizing feat: impl resizing image logic Aug 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
momogo-core/build.gradle (1)

34-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Thumbnailator 패치 버전을 확인하세요.

net.coobird:thumbnailator:0.4.20은 사용할 수 있지만, 현재 Maven Central에는 0.4.21이 등록되어 있습니다. (repo1.maven.org)

가능하면 0.4.21로 업그레이드하고 이미지 포맷, EXIF, 리사이징 회귀 테스트를 실행하세요. 최신 패치 사용은 유지보수성을 높입니다. 반면 동작 변경 가능성이 있으므로 ImageProcessor 흐름의 호환성 확인이 필요합니다.

0.4.20을 유지해야 한다면 호환성 또는 운영 검증 결과를 문서화하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/build.gradle` at line 34, Update the Thumbnailator dependency in
the build.gradle implementation declaration from 0.4.20 to 0.4.21, then verify
ImageProcessor compatibility by running image-format, EXIF, and resizing
regression tests.

Source: MCP tools

momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java (3)

34-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

프로필 규격 상수를 설정값으로 분리하는 방안을 고려해주세요.

ImageProcessorcommon.util 패키지의 범용 컴포넌트입니다. 그런데 PROFILE_TARGET_WIDTH, PROFILE_TARGET_HEIGHT, OUTPUT_QUALITY는 프로필 이미지라는 특정 도메인 정책입니다. 지금은 프로필 업로드만 존재하므로 동작에 문제는 없습니다.

다만 썸네일이나 방 대표 이미지처럼 다른 규격이 추가되면, 이 클래스를 수정해야 하거나 유사 클래스가 복제됩니다. 두 가지 대안이 있습니다.

  • validateImage(stream, filename, contentType, targetWidth, targetHeight) 형태로 규격을 파라미터화합니다. 장점은 단순함이고, 단점은 호출부가 규격을 알아야 한다는 점입니다.
  • 규격을 @ConfigurationProperties 레코드로 묶어 주입합니다. 장점은 환경별 조정이 가능한 점이고, 단점은 클래스가 하나 늘어나는 점입니다.

YAGNI 관점에서 지금 당장 필요한 변경은 아닙니다. 두 번째 이미지 규격이 생기는 시점에 적용하면 충분합니다.

Also applies to: 104-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`
around lines 34 - 36, Defer changes to PROFILE_TARGET_WIDTH,
PROFILE_TARGET_HEIGHT, and OUTPUT_QUALITY in ImageProcessor; no implementation
change is required until a second image specification is introduced.

58-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

maxMarkSize 네이밍이 실제 역할과 맞지 않습니다.

이 값은 readWithLimit에 전달되는 업로드 허용 최대 바이트 수입니다. "mark"는 InputStream#mark(int)의 마크 버퍼 크기를 연상시킵니다. 현재 코드는 mark/reset을 전혀 사용하지 않으므로 이름이 오해를 만듭니다. 이전 구현에서 mark 기반이었다면 남은 흔적일 수 있습니다.

maxUploadSize와 프로퍼티 키 app.file.upload.max-size로 바꾸는 편이 의도를 정확히 전달합니다. 프로퍼티 키를 변경하면 설정 파일도 함께 수정해야 합니다.

Also applies to: 62-62, 67-67, 89-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java` at
line 58, Rename the ImageProcessor field maxMarkSize to maxUploadSize and update
all references, including the constructor assignment and readWithLimit call.
Change the associated configuration property key to app.file.upload.max-size,
and update the corresponding application configuration entries to match.

201-202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

확장자 파싱이 두 저장소 구현에 중복되었습니다. 근본 원인은 ImageValidationResult의 정보 부족입니다.

ImageProcessor.validateImage는 이미 확장자를 파싱하고, 화이트리스트로 검증하고, 실제 포맷과 교차 검증합니다. 그런데 ImageValidationResult는 그 결과를 노출하지 않습니다. 그래서 두 저장소가 originalFileName을 다시 파싱합니다.

이 구조는 세 가지 부작용을 만듭니다.

  1. DRY 위반: 동일한 6줄이 두 파일에 복제되었습니다. 확장자 정책이 바뀌면 세 곳을 수정해야 합니다.
  2. 도달 불가 방어 코드: originalFileName != null && originalFileName.contains(".") 검사는 validateImage가 이미 통과시킨 뒤에 실행됩니다. 항상 참입니다. 읽는 사람은 extension이 빈 문자열이 될 수 있다고 오해합니다.
  3. 정보 손실: 저장 파일명 확장자는 원본 파일명에서 오고, Content-Type은 감지된 포맷에서 옵니다. 두 값의 출처가 다르면 향후 출력 포맷을 정규화할 때 어긋납니다.

수정 방향 — 각 위치에서 필요한 변경은 다음과 같습니다.

  • momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java#L201-L202: ImageValidationResult에 확장자 필드를 추가하고, validateImage가 검증된 ext를 채워 반환하도록 변경합니다. InputStream 대신 byte[] data를 노출하면 S3 쪽의 readAllBytes() 재복사도 사라집니다.
  • momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java#L44-L50: 로컬 확장자 파싱 블록을 삭제하고 validationResult.extension()을 사용합니다.
  • momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java#L51-L57: 동일하게 파싱 블록을 삭제하고 validationResult.extension()을 사용합니다.
♻️ 리팩터링 예시

ImageProcessor.java:

-    public record ImageValidationResult(InputStream inputStream, String detectedContentType) {
+    public record ImageValidationResult(byte[] data, String detectedContentType, String extension) {
+        public InputStream inputStream() {
+            return new ByteArrayInputStream(data);
+        }
     }
         String detectedMimeType = FORMAT_TO_MIME_TYPE.get(dimension.format());
-        return new ImageValidationResult(new ByteArrayInputStream(resizedBytes), detectedMimeType);
+        return new ImageValidationResult(resizedBytes, detectedMimeType, "." + ext);

LocalStorageService.java:

-            try (InputStream validatedStream = validationResult.inputStream()) {
-                String extension = "";
-                if (originalFileName != null && originalFileName.contains(".")) {
-                    extension = originalFileName.substring(originalFileName.lastIndexOf("."));
-                }
-
-                String savedFileName = UUID.randomUUID() + extension;
+            String savedFileName = UUID.randomUUID() + validationResult.extension();

S3StorageService.java:

-            try (InputStream validatedStream = validationResult.inputStream()) {
-                String extension = "";
-                if (originalFileName != null && originalFileName.contains(".")) {
-                    extension = originalFileName.substring(originalFileName.lastIndexOf("."));
-                }
-
-                String savedFileName = UUID.randomUUID() + extension;
-                String key = directory + "/" + savedFileName;
-
-                byte[] bytes = validatedStream.readAllBytes();
+            String savedFileName = UUID.randomUUID() + validationResult.extension();
+            String key = directory + "/" + savedFileName;
+            byte[] bytes = validationResult.data();

참고 개념 — 레코드로 byte[]를 노출하면 배열이 가변이므로 equals/hashCode가 참조 기준으로 동작합니다. 값 비교가 필요하면 접근자에서 clone()을 반환하거나 ByteBuffer.wrap(...).asReadOnlyBuffer()를 쓰는 방법이 있습니다. 이 용례에서는 단일 소비 후 폐기되므로 실제 문제는 없습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`
around lines 201 - 202, The extension parsing logic is duplicated across storage
implementations because ImageValidationResult does not expose the validated
extension. In
momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java (lines
201-202), add an extension field to the ImageValidationResult record and update
the validateImage method to populate this field with the validated extension;
optionally replace InputStream with byte[] data to eliminate downstream
readAllBytes() copies. In
momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
(lines 44-50), remove the local extension parsing block and replace it with a
call to validationResult.extension(). In
momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java
(lines 51-57), remove the duplicate extension parsing block and replace it with
a call to validationResult.extension(). This consolidates the single source of
truth for the validated extension and eliminates the unreachable defensive
checks in both storage services.
momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java (1)

58-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

directory 형식을 구현 단에서 맞추는 편이 좋습니다.

현재 호출부는 상수 PROFILE_IMAGE_DIR만 전달합니다. 하지만 LocalStorageServiceresolveSafely(uploadRoot, directory)에서 null/blank를 거부하고 경로 조작 후보를 검지만, S3StorageServicedirectory + "/"null이면 "null/...", ../가 있으면 그 값 그대로 S3 객체 키에 넣습니다. 저장소 교체 시 같은 입력에 대한 동작이 달라지지 않도록 StorageService#upload 계약에서 사용할 수 있는 directory 형식을 문서화하거나 함께 검증하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java`
at line 58, StorageService#upload의 directory 입력 계약이 구현마다 달라 S3에서 null, blank, 또는
경로 조작 값이 그대로 사용됩니다. StorageService#upload 계약에 허용되는 directory 형식을 문서화하고,
S3StorageService의 키 생성 경로에서 LocalStorageService의 null/blank 및 안전성 검증과 동일하게 검증하도록
수정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`:
- Around line 111-127: Update resizeImage so resizing failures no longer return
originalBytes; propagate the exception to the caller instead of treating the
image as successfully processed. Preserve the existing resize behavior, and
ensure WebP write failures follow the exception path rather than saving the
original payload.

---

Nitpick comments:
In `@momogo-core/build.gradle`:
- Line 34: Update the Thumbnailator dependency in the build.gradle
implementation declaration from 0.4.20 to 0.4.21, then verify ImageProcessor
compatibility by running image-format, EXIF, and resizing regression tests.

In
`@momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java`:
- Line 58: StorageService#upload의 directory 입력 계약이 구현마다 달라 S3에서 null, blank, 또는
경로 조작 값이 그대로 사용됩니다. StorageService#upload 계약에 허용되는 directory 형식을 문서화하고,
S3StorageService의 키 생성 경로에서 LocalStorageService의 null/blank 및 안전성 검증과 동일하게 검증하도록
수정하세요.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`:
- Around line 34-36: Defer changes to PROFILE_TARGET_WIDTH,
PROFILE_TARGET_HEIGHT, and OUTPUT_QUALITY in ImageProcessor; no implementation
change is required until a second image specification is introduced.
- Line 58: Rename the ImageProcessor field maxMarkSize to maxUploadSize and
update all references, including the constructor assignment and readWithLimit
call. Change the associated configuration property key to
app.file.upload.max-size, and update the corresponding application configuration
entries to match.
- Around line 201-202: The extension parsing logic is duplicated across storage
implementations because ImageValidationResult does not expose the validated
extension. In
momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java (lines
201-202), add an extension field to the ImageValidationResult record and update
the validateImage method to populate this field with the validated extension;
optionally replace InputStream with byte[] data to eliminate downstream
readAllBytes() copies. In
momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
(lines 44-50), remove the local extension parsing block and replace it with a
call to validationResult.extension(). In
momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java
(lines 51-57), remove the duplicate extension parsing block and replace it with
a call to validationResult.extension(). This consolidates the single source of
truth for the validated extension and eliminates the unreachable defensive
checks in both storage services.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d82ca148-58a1-488e-91c3-9855590621bb

📥 Commits

Reviewing files that changed from the base of the PR and between 517b009 and 4235d79.

📒 Files selected for processing (5)
  • momogo-core/build.gradle
  • momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/util/ImageFileValidator.java
  • momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java
💤 Files with no reviewable changes (1)
  • momogo-core/src/main/java/com/momogo/core/common/util/ImageFileValidator.java

Comment thread momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java (2)

198-205: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

ImageValidationResult record의 data 필드가 방어적 복사 없이 원본 배열 참조를 그대로 노출합니다.

record ImageValidationResult(byte[] data, ...)는 배열을 그대로 저장하고, 자동 생성된 data() 접근자도 같은 참조를 반환합니다. 호출자(S3StorageService)가 validationResult.data()로 얻은 배열을 수정하면 내부 상태가 오염될 수 있습니다. record는 불변을 지향하지만, 배열 필드는 자동으로 불변이 되지 않는 Java의 잘 알려진 함정입니다.

생성자에서 data.clone()으로 방어적 복사를 하거나, 접근자를 재정의해 복사본을 반환하는 방법을 고려하십시오.

♻️ 제안하는 수정
     public record ImageValidationResult(byte[] data, String detectedContentType, String extension) {
+        public ImageValidationResult(byte[] data, String detectedContentType, String extension) {
+            this.data = data.clone();
+            this.detectedContentType = detectedContentType;
+            this.extension = extension;
+        }
+
+        `@Override`
+        public byte[] data() {
+            return data.clone();
+        }
+
         public InputStream inputStream() {
             return new ByteArrayInputStream(data);
         }
     }

이미 검증이 끝난 데이터를 저장소 레이어로 넘기는 경계 지점이므로, 불변성을 보장하면 향후 유지보수 시 실수를 예방할 수 있습니다. As per path instructions, **/main/**/*.java는 "클린코드, 리팩토링" 관점을 확인해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`
around lines 198 - 205, Update the ImageValidationResult record to defensively
copy its byte-array data both when storing it and when exposing it through
data(), preventing callers such as S3StorageService from mutating the internal
state. Keep inputStream() reading from the protected data.

Source: Path instructions


58-68: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

DataSizeint로 캐스팅하면 큰 설정값에서 오버플로우가 발생할 수 있습니다.

this.maxFileSize = (int) maxFileSize.toBytes();에서, 운영 환경에서 app.file.upload.max-file-size를 2GB 이상으로 설정하면 int 캐스팅 시 값이 음수로 오버플로우됩니다. 그러면 readWithLimittotalBytes + bytesRead > limit 검사가 첫 조각부터 항상 참이 되어, 모든 업로드가 즉시 거부됩니다.

기본값 10MB에서는 문제가 없지만, 설정 실수에 취약한 구조입니다. maxFileSize 필드를 long으로 유지하면 이 문제를 근본적으로 방지할 수 있습니다.

♻️ 제안하는 수정
     private final Set<String> allowedExtensions;
-    private final int maxFileSize;
+    private final long maxFileSize;

     public ImageProcessor(
             `@Value`("${app.file.upload.allowed-extensions}") List<String> allowedExtensions,
             `@Value`("${app.file.upload.max-file-size:10MB}") DataSize maxFileSize
     ) {
         this.allowedExtensions = allowedExtensions.stream()
                 .map(String::toLowerCase)
                 .collect(Collectors.toUnmodifiableSet());
-        this.maxFileSize = (int) maxFileSize.toBytes();
+        this.maxFileSize = maxFileSize.toBytes();
     }

그리고 readWithLimit(InputStream inputStream, long limit)으로 시그니처를 맞추고 내부 totalByteslong으로 변경해야 합니다.

Java 21 기준으로 볼 때, 설정값을 신뢰할 수 없는 외부 입력처럼 다루고 방어적으로 타입을 넓히는 것이 실무에서 더 안전한 습관입니다. As per path instructions, **/main/**/*.java는 "실무 활용 가능성, 불필요한 로직, 클린코드, 리팩토링"을 확인해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`
around lines 58 - 68, ImageProcessor의 maxFileSize를 int에서 long으로 변경하고, 생성자에서
DataSize.toBytes()를 손실 없이 보존하도록 할당하세요. readWithLimit(InputStream, long)의 limit과
내부 totalBytes도 long으로 맞춰 2GB 이상 설정에서도 오버플로우 없이 크기 검사가 동작하도록 수정하세요.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java`:
- Around line 18-22: Update the path validation in StorageDirectoryValidator to
inspect normalized.split("/", -1) and reject only segments exactly equal to
"..", while continuing to reject absolute paths. Preserve valid directory names
such as archive..2026 and update the related comment if needed to describe the
segment-based validation.
- Around line 20-25: Update StorageDirectoryValidator.validate() to produce the
same canonical path semantics as resolveSafely(): remove "." segments, empty
segments, and duplicate slashes while retaining the existing traversal and
absolute-path rejection. Ensure the normalized directory returned by validate()
is identical for LocalStorageService and S3StorageService, and add coverage for
inputs such as images/./profile and images//profile.

---

Nitpick comments:
In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`:
- Around line 198-205: Update the ImageValidationResult record to defensively
copy its byte-array data both when storing it and when exposing it through
data(), preventing callers such as S3StorageService from mutating the internal
state. Keep inputStream() reading from the protected data.
- Around line 58-68: ImageProcessor의 maxFileSize를 int에서 long으로 변경하고, 생성자에서
DataSize.toBytes()를 손실 없이 보존하도록 할당하세요. readWithLimit(InputStream, long)의 limit과
내부 totalBytes도 long으로 맞춰 2GB 이상 설정에서도 오버플로우 없이 크기 검사가 동작하도록 수정하세요.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 65102035-4656-41ef-864a-8a017f4c4531

📥 Commits

Reviewing files that changed from the base of the PR and between 4235d79 and 0eedab3.

📒 Files selected for processing (6)
  • momogo-api/src/main/resources/application.yaml
  • momogo-core/build.gradle
  • momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java
  • momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • momogo-core/build.gradle
  • momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java (3)

77-107: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

이미지 처리 계약을 고정하는 테스트를 추가하세요.

이 변경은 입력 허용 여부, 확장자/포맷 교차 검증, 픽셀 제한, 리사이징 결과, MIME 타입 및 실패 예외를 동시에 변경합니다. 제공된 변경 범위에 테스트가 없어 저장소에 잘못된 바이트가 저장되거나 크기 계약이 깨져도 발견하기 어렵습니다.

최소한 다음 테스트를 추가하세요.

  • 큰 가로·세로 이미지가 중앙 크롭 후 기대한 크기를 갖는지 확인
  • 300보다 작은 이미지의 업스케일 정책 확인
  • 확장자와 실제 포맷 불일치 거부
  • 최대 바이트 및 최대 픽셀 경계값 확인
  • JPEG, PNG, WebP의 실제 reader/writer 동작 확인
  • 헤더는 유효하지만 픽셀 데이터가 잘린 이미지의 예외 코드 확인

As per path instructions: 꼭 필요한 부분에 집중하고, 문제점과 대안을 제시했습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`
around lines 77 - 107, ImageProcessor.validateImage and its related format,
size, and resize behavior lack contract tests. Add focused tests covering
centered cropping of oversized images, the defined policy for upscaling images
smaller than 300px, extension/actual-format mismatches, exact maximum byte and
pixel boundaries, real JPEG/PNG/WebP reader-writer compatibility, and truncated
pixel data with the expected BusinessException error code.

Source: Path instructions


110-120: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

프로필 이미지 크기 계약을 현재 동작과 맞게 정리하세요.

현재 구현은 원본의 짧은 변과 PROFILE_TARGET_SIZE 중 작은 값으로 출력하므로, targetSize = 300이더라도 200×400 이미지는 200×200으로 저장됩니다. 즉, 26행의 “300*300” 규격 문서와 112행의 동작 불일치입니다.

  • 고정 300×300을 목표로 한다면 112행을 int targetSize = profileTargetSize;로 바꾸세요. 이후 업스케일 정책을 결정하고 테스트/문서를 함께 맞춰야 합니다.
  • 최대 크기를 목표로 한다면 상수명을 PROFILE_MAX_SIZE로 바꾸고 문서도 300×300이 아닌 상한 규격으로 수정하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`
around lines 110 - 120, 프로필 이미지 크기 계약과 resizeImage의 현재 동작이 일치하지 않습니다. 고정 300×300
규격을 유지하려면 resizeImage의 targetSize 계산에서 원본 치수의 최소값을 제거하고 profileTargetSize를 직접
사용하도록 변경한 뒤, 업스케일 허용 여부를 결정해 관련 테스트와 문서를 맞추세요. 최대 크기 정책을 유지하려면
profileTargetSize와 관련 문서 및 상수명을 PROFILE_MAX_SIZE로 정리하고 상한 규격을 명시하세요.

Sources: Path instructions, MCP tools


137-171: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

업로드 오류가 INTERNAL_SERVER_ERROR로 겹치지 않게 예외 범주를 분리하세요.

detectAndValidateFormatgetWidth(0) / getHeight(0)만 읽어 헤더 차원까지 확인하지만, 원본 스트림의 전체 픽셀을 아직 디코드하지 않습니다. 잘린 image는 여기서 통과한 뒤 Thumbnailator toOutputStream에서 디코드 실패가 발생할 수 있습니다. 현재 메서드는 이 실패도 공통 INTERNAL_SERVER_ERROR로 처리하므로, 잘못된 클라이언트 입력이 500 응답과 서버 오류 로그를 일으킵니다.

입력 기반 디코딩 실패는 INVALID_INPUT으로 보내고, writer/서버 설정 실패만 INTERNAL_SERVER_ERROR로 남겨주세요. 재시도도 하지 않는 리사이징 실패를 retryable 내부 오류처럼 보이면 사용자가 불안정 서버 경험을 얻습니다. 간단한 대안은 검증 단계에서 전체 디코드만 먼저 수행하는 것입니다. 기존 ImageDimension을 그대로 쓰기 위해 원본 BufferedImage를 저장하면 메모리 수명/메서드 계약 변경까지 고려해야 합니다. 이 방식은 CPU/메모리 비용이 더 들지만 구현이 쉽습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`
around lines 137 - 171, detectAndValidateFormat에서 헤더 크기만 확인하지 말고 원본 이미지 전체 디코딩을
수행해 잘린 이미지와 같은 입력 오류를 검증 단계에서 INVALID_INPUT으로 처리하세요. 기존 ImageDimension 반환 계약과 픽셀
제한 검사는 유지하고, 입력 디코딩 실패가 공통 INTERNAL_SERVER_ERROR로 변환되지 않도록 예외 범주를 분리하세요. 리사이징
과정의 writer 또는 서버 설정 오류만 내부 서버 오류로 남겨야 합니다.

Sources: Path instructions, MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java`:
- Around line 22-25: Update StorageDirectoryValidator.validate to reject
absolute paths before normalizing separators or calling split: reject inputs
beginning with "/" or "\" and Windows drive-absolute forms such as a drive
letter followed by a separator. Preserve relative-path validation, and
explicitly apply the existing policy for "/" and "."; add or update tests for
these cases.

---

Outside diff comments:
In `@momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java`:
- Around line 77-107: ImageProcessor.validateImage and its related format, size,
and resize behavior lack contract tests. Add focused tests covering centered
cropping of oversized images, the defined policy for upscaling images smaller
than 300px, extension/actual-format mismatches, exact maximum byte and pixel
boundaries, real JPEG/PNG/WebP reader-writer compatibility, and truncated pixel
data with the expected BusinessException error code.
- Around line 110-120: 프로필 이미지 크기 계약과 resizeImage의 현재 동작이 일치하지 않습니다. 고정 300×300
규격을 유지하려면 resizeImage의 targetSize 계산에서 원본 치수의 최소값을 제거하고 profileTargetSize를 직접
사용하도록 변경한 뒤, 업스케일 허용 여부를 결정해 관련 테스트와 문서를 맞추세요. 최대 크기 정책을 유지하려면
profileTargetSize와 관련 문서 및 상수명을 PROFILE_MAX_SIZE로 정리하고 상한 규격을 명시하세요.
- Around line 137-171: detectAndValidateFormat에서 헤더 크기만 확인하지 말고 원본 이미지 전체 디코딩을
수행해 잘린 이미지와 같은 입력 오류를 검증 단계에서 INVALID_INPUT으로 처리하세요. 기존 ImageDimension 반환 계약과 픽셀
제한 검사는 유지하고, 입력 디코딩 실패가 공통 INTERNAL_SERVER_ERROR로 변환되지 않도록 예외 범주를 분리하세요. 리사이징
과정의 writer 또는 서버 설정 오류만 내부 서버 오류로 남겨야 합니다.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e0175ce-fccf-4439-aadd-15284f350df6

📥 Commits

Reviewing files that changed from the base of the PR and between 0eedab3 and 1f524a5.

📒 Files selected for processing (5)
  • momogo-core/build.gradle
  • momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java
  • momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java (1)

34-45: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

잘못된 파일 시스템 경로를 BusinessException으로 매핑하세요.

StorageDirectoryValidator.validate()..만 차단하고 NUL 같은 Java 21 Path에서 거부되는 경로 문자는 그대로 반환합니다. LocalStorageService.resolveSafely()base.resolve(...).normalize()에서 InvalidPathException을 던질 수 있는데, 이는 unchecked 예외이고 LocalStorageService.upload()catch (IOException)보다 앞서 발생하므로 FILE_UPLOAD_FAILED로 내려가지 않습니다. 현재 공통 처리기는 이 unchecked 예외를 내부 오류로 반환합니다. resolveSafely()에서 InvalidPathException까지 잡거나 validator에서 파일 시스템 경로 규칙을 검증하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java`
around lines 34 - 45, Update StorageDirectoryValidator.validate() or
LocalStorageService.resolveSafely() so Java 21-invalid path input, including NUL
characters, is rejected and mapped to the existing
BusinessException/invalid-input flow before Path resolution can throw
InvalidPathException. Ensure LocalStorageService.upload() does not expose this
unchecked exception as an internal error or FILE_UPLOAD_FAILED.

Sources: Path instructions, MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java`:
- Around line 34-45: Update StorageDirectoryValidator.validate() or
LocalStorageService.resolveSafely() so Java 21-invalid path input, including NUL
characters, is rejected and mapped to the existing
BusinessException/invalid-input flow before Path resolution can throw
InvalidPathException. Ensure LocalStorageService.upload() does not expose this
unchecked exception as an internal error or FILE_UPLOAD_FAILED.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 49c44983-9cfa-4f76-9f10-3ce2bb236b62

📥 Commits

Reviewing files that changed from the base of the PR and between 1f524a5 and 16ed38f.

📒 Files selected for processing (2)
  • momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java
  • momogo-core/src/main/java/com/momogo/core/common/util/StorageDirectoryValidator.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java

Comment thread momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java Outdated
Comment thread momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java Outdated

@idktomorrow idktomorrow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그 외 크게 문제될 부분은 없어보입니다 작성하시느라 고생 많으셨습니다! ❤️

Comment thread momogo-core/src/main/java/com/momogo/core/common/util/ImageProcessor.java Outdated

@Junkov0 Junkov0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 코드 잘 구성하신 것 같습니다!
LGTM

jaejo added 5 commits August 6, 2026 13:36
…izPlatform/MoMoGo into feature/image-resizing

# Conflicts:
#	momogo-core/src/main/java/com/momogo/core/common/util/storage/ImageProcessor.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@momogo-core/src/main/java/com/momogo/core/common/util/storage/ImageProcessor.java`:
- Around line 127-143: Update resizeImage in ImageProcessor so resize or
encoding exceptions are converted to the project’s BusinessException instead of
returning originalBytes; use INVALID_INPUT for invalid image data and
FILE_UPLOAD_FAILED for encoder or processing failures when distinguishable.
Ensure LocalStorageService and S3StorageService propagate this exception so
storage writes are not attempted, and add coverage verifying no upload occurs
after resize failure.

In
`@momogo-core/src/main/java/com/momogo/core/common/util/storage/ImageResizeSpec.java`:
- Around line 7-18: The affected sites are
momogo-core/src/main/java/com/momogo/core/common/util/storage/ImageResizeSpec.java:7-18
and
momogo-core/src/main/java/com/momogo/core/common/util/storage/ImageProcessor.java:127-138.
Choose the square-only policy: update ImageResizeSpec’s constructor to require
width == height and revise its documentation to state that only square
dimensions are supported; ImageProcessor requires no direct change because the
constructor validation prevents unsupported rectangular specifications.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eac92740-1607-463e-9422-b9413f70a646

📥 Commits

Reviewing files that changed from the base of the PR and between 16ed38f and e7e3ec6.

📒 Files selected for processing (8)
  • momogo-api/src/main/resources/application.yaml
  • momogo-core/src/main/java/com/momogo/core/common/storage/LocalStorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/storage/S3StorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/storage/StorageService.java
  • momogo-core/src/main/java/com/momogo/core/common/util/storage/ImageProcessor.java
  • momogo-core/src/main/java/com/momogo/core/common/util/storage/ImageResizeSpec.java
  • momogo-core/src/main/java/com/momogo/core/common/util/storage/StorageDirectoryValidator.java
  • momogo-core/src/main/java/com/momogo/core/domain/user/service/UserServiceImpl.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • momogo-api/src/main/resources/application.yaml

@jaejo
jaejo merged commit 2889e4c into develop Aug 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants