Skip to content
Open
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'com.auth0:java-jwt:4.2.1'

// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1'
Comment on lines +44 to +48
Copy link

@coderabbitai coderabbitai bot Nov 11, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

QueryDSL 버전 확인 필요

QueryDSL 5.0.0을 사용 중이지만, 이 버전이 최신 안정 버전인지 확인이 필요합니다. 보안 패치와 버그 수정이 포함된 최신 버전을 사용하는 것이 좋습니다.


🌐 Web query:

What is the latest stable version of QueryDSL for Jakarta EE?

💡 Result:

The latest stable QueryDSL release for Jakarta EE is 5.1.0 (checked Nov 11, 2025). [1][2]

Sources:

  • QueryDSL releases/notes. [1]
  • QueryDSL 5.1.0 Javadoc / Maven artifact listing. [2]

QueryDSL을 5.1.0으로 업그레이드하세요

현재 QueryDSL 5.0.0을 사용 중이지만, 최신 안정 버전은 5.1.0입니다. 보안 패치와 버그 수정을 위해 build.gradle의 44-45번 줄을 다음과 같이 업데이트하세요:

implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta'
🤖 Prompt for AI Agents
In build.gradle around lines 44 to 48, the QueryDSL dependency and its
annotation processor are pinned to 5.0.0; update the QueryDSL coordinates on
lines 44-45 to use version 5.1.0 by changing both the implementation and the
querydsl-apt annotationProcessor entries to use ':5.1.0:jakarta' while leaving
the other annotationProcessor entries unchanged.

Copy link
Collaborator

Choose a reason for hiding this comment

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

https://mvnrepository.com/artifact/com.querydsl/querydsl-jpa/5.1.0

확인해보니 쿼리 DSL 전 버전에 취약점이 있네요. 지원 중단돼서 그런가...?

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

QueryDSL 5.0.0 security vulnerabilities CVE

💡 Result:

Short answer: Yes — Querydsl has a reported HQL/SQL injection vulnerability identified as CVE-2024-49203 that affects Querydsl 5.x (including 5.0.0/<=5.1.0 in multiple advisories). The issue concerns injection via JPAQuery.orderBy when untrusted input is used. Recommend not passing untrusted input into orderBy and upgrading to a patched/non-vulnerable release or forked build where the fix is available. [1][2][3][4]

Sources

  • CSIRT.SK advisory describing CVE-2024-49203 and affected Querydsl versions. [1]
  • GitHub Advisory / OpenFeign querydsl security advisory for the same issue (GHSA-6q3q-6v5j-h6vg). [2]
  • Snyk vulnerability page for CVE-2024-49203. [3]
  • Maven / mvnrepository listing for querydsl-apt 5.0.0 that flags CVE-2024-49203. [4]

If you want, I can: (a) check the current latest Querydsl release and which exact versions include the fix, or (b) show concrete mitigations/workarounds and sample code to safely handle ordering parameters.


🌐 Web query:

QueryDSL maintenance status 2025 CVE-2024-49203 fix

💡 Result:

Short answer: Querydsl is actively maintained in 2025 and the CVE-2024-49203 HQL/SQL‑injection issue was fixed — upgrade to the patched releases (Querydsl 5.6.1, 6.10.1; OpenFeign Querydsl 7.0 also lists fixes). Apply the update or validate/whitelist any user-controlled orderBy input if you cannot upgrade immediately. [1][2][3]

Sources:

  • Querydsl release notes showing fixes for CVE-2024-49203 (releases including 5.6.1 and 6.10.1). [1]
  • GitHub security advisory for the issue (GHSA-6q3q-6v5j-h6vg / CVE-2024-49203) with patched versions listed. [2]
  • CSIRT.SK advisory and disclosure details with mitigation guidance. [3]

@hyxklee 좋은 지적 감사합니다! 취약점 확인 결과를 공유드립니다:

취약점 정보 (CVE-2024-49203)

  • QueryDSL 5.0.0과 5.1.0 모두 HQL/SQL 인젝션 취약점이 존재합니다
  • JPAQuery.orderBy()에 검증되지 않은 사용자 입력이 전달될 때 발생합니다

유지보수 상태
QueryDSL은 여전히 활발히 유지보수되고 있습니다. 지원 중단이 아니며, 해당 취약점은 이미 패치되었습니다.

권장사항
build.gradle의 QueryDSL 버전을 5.6.1 이상으로 업그레이드하세요:

// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.6.1:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.6.1:jakarta'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1'

이 PR에서 QueryDSL로 전환하는 시점에 보안 패치가 적용된 버전을 사용하는 것이 중요합니다. 현재 코드를 확인한 결과 orderBy(post.id.desc())처럼 정적 표현식을 사용하고 있어 직접적인 위험은 낮지만, 예방 차원에서 패치된 버전 사용을 강력히 권장합니다.


// mapStruct
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.springframework.data.repository.query.Param;


public interface PostRepository extends JpaRepository<Post, Long> {
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom {

@Query("""
SELECT p FROM Post p
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package leets.weeth.domain.board.domain.repository;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

import leets.weeth.domain.board.domain.entity.Post;

public interface PostRepositoryCustom {

Slice<Post> findRecentPart(Pageable pageable);

Slice<Post> searchPart(String kw,Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package leets.weeth.domain.board.domain.repository;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;

import com.querydsl.jpa.impl.JPAQueryFactory;

import leets.weeth.domain.board.domain.entity.Post;
import leets.weeth.domain.board.domain.entity.QPost;
import leets.weeth.domain.board.domain.entity.enums.Category;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepositoryCustom {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

@repository 어노테이션이 누락되었습니다.

클래스에 @Repository 어노테이션이 없습니다. Spring의 컴포넌트 스캔 및 예외 변환을 위해 필요하며, JPA repository 구현체의 표준 패턴입니다.

다음 diff를 적용하세요:

+@Repository
 public class PostRepositoryImpl implements PostRepositoryCustom {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public class PostRepositoryImpl implements PostRepositoryCustom {
@Repository
public class PostRepositoryImpl implements PostRepositoryCustom {
🤖 Prompt for AI Agents
In
src/main/java/leets/weeth/domain/board/domain/repository/PostRepositoryImpl.java
around line 19, the class PostRepositoryImpl is missing the Spring @Repository
annotation; add the @Repository annotation to the class declaration and import
org.springframework.stereotype.Repository so Spring can component-scan and apply
exception translation for this JPA repository implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 방식의 구현법은 공식문서를 기반으로 구현해주신 걸까요?? 단순 궁금

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

https://sjh9708.tistory.com/174#google_vignette
위 블로그들 포함하여 여러 구현 블로그 따라 했습니당


private final JPAQueryFactory queryFactory;
private final QPost post = QPost.post;

@Override
public Slice<Post> findRecentPart(Pageable pageable) {
List<Post> result = queryFactory
.selectFrom(post)
.where(post.category.in(Category.StudyLog, Category.Article))
.orderBy(post.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize() + 1)
.fetch();

boolean hasNext = result.size() > pageable.getPageSize();

if (hasNext) {
result.remove(pageable.getPageSize());
}

return new SliceImpl<>(result, pageable, hasNext);
}

@Override
public Slice<Post> searchPart(String kw, Pageable pageable) {
List<Post> result = queryFactory
.selectFrom(post)
.where(post.category.in(Category.StudyLog, Category.Article)
.and(post.title.containsIgnoreCase(kw)
.or(post.content.containsIgnoreCase(kw))
Comment on lines +60 to +61
Copy link
Collaborator

Choose a reason for hiding this comment

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

kw가 null일 경우 NPE이 발생하지는 않나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

테스트 해보았는데 아무것도 입력하지 않는 경우 호출이 불가합니다! 따라서 npe발생이 안될거라 예상됩니당

Controller 확인 결과 , RequestParam이 붙여져있어서 괜찮을 것 같습니다!

))
.orderBy(post.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize() + 1)
.fetch();

boolean hasNext = result.size() > pageable.getPageSize();

if (hasNext) {
result.remove(pageable.getPageSize());
}

return new SliceImpl<>(result, pageable, hasNext);
}
}
17 changes: 17 additions & 0 deletions src/main/java/leets/weeth/global/config/QuerydslConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package leets.weeth.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.querydsl.jpa.impl.JPAQueryFactory;

import jakarta.persistence.EntityManager;

@Configuration
public class QuerydslConfig {

@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager em) {
return new JPAQueryFactory(em);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package leets.weeth.domain.board.domain.repository;

import static org.assertj.core.api.AssertionsForInterfaceTypes.*;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import leets.weeth.config.TestContainersConfig;
import leets.weeth.domain.board.domain.entity.Post;
import leets.weeth.domain.board.domain.entity.enums.Category;
import leets.weeth.domain.board.domain.entity.enums.Part;
import leets.weeth.global.config.QuerydslConfig;

@DataJpaTest
@Import({TestContainersConfig.class , QuerydslConfig.class})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class PostRepositoryTest {

@Autowired
private PostRepository postRepository;

@BeforeEach
void setUp() {
Post post1 = Post.builder()
.title("dsl 테스트 제목")
.content("QueryDSL동작확인")
.category(Category.StudyLog)
.cardinalNumber(1)
.week(1)
.parts(List.of(Part.BE))
.build();

Post post2 = Post.builder()
.title("테스트 제목2")
.content("2동작2확인2")
.category(Category.Article)
.cardinalNumber(2)
.week(1)
.parts(List.of(Part.BE))
.build();

postRepository.saveAll(List.of(post1,post2));

}


@Test
@DisplayName("findRecentPart(): StudyLog 또는 Article 카테고리 게시글 최신순 조회")
void findRecentPart_success() {
//given
Pageable pageable = PageRequest.of(0, 10);

//when
var result = postRepository.findRecentPart(pageable);

//then
assertThat(result).isNotNull();
assertThat(result.getContent()).hasSize(2);
assertThat(result.getContent().get(0).getTitle()).isEqualTo("테스트 제목2");
}

@Test
@DisplayName("searchPart(): 키워드 기반 검색")
void searchPart_success() {
//given
Pageable pageable = PageRequest.of(0, 10);

//when
var result = postRepository.searchPart("dsl", pageable);

//then
assertThat(result.getContent()).hasSize(1);
assertThat(result.getContent().get(0).getTitle()).isEqualTo("dsl 테스트 제목");
}
}

Loading