Skip to content

Comments

[CHORE] 프리뷰 배포#422

Open
unifolio0 wants to merge 2 commits intodevelopfrom
chore/#421
Open

[CHORE] 프리뷰 배포#422
unifolio0 wants to merge 2 commits intodevelopfrom
chore/#421

Conversation

@unifolio0
Copy link

@unifolio0 unifolio0 commented Feb 20, 2026

🚩 연관 이슈

closed #421

📝 작업 내용

  • 프리뷰 배포 기능을 구현했습니다.
  • 관련 환경 변수나 시크릿 변수는 노션의 ENV 페이지에 기록했습니다.
  • dev나 prod에는 영향이 안 가도록 했는데 제가 프론트는 첨이라 확실하진 않으니 확인 부탁드립니다.

🏞️ 스크린샷 (선택)

🗣️ 리뷰 요구사항 (선택)

Summary by CodeRabbit

  • New Features
    • PR 미리보기 자동 배포 및 PR 종료 시 미리보기 정리(배포 URL 코멘트 제공)
    • 애플리케이션 기본 경로(VITE_BASE_PATH) 지원으로 다양한 배포 경로 가능
    • 라우팅 구조 개선 및 전역 에러/뒤로가기 처리 강화
    • OAuth 및 번역 파일 경로가 배포 기본 경로를 반영하도록 개선

@gemini-code-assist
Copy link

Summary of Changes

Hello @unifolio0, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 프리뷰 배포 기능을 지원하기 위해 애플리케이션의 기본 경로(base path) 처리 방식을 개선합니다. VITE_BASE_PATH 환경 변수를 도입하여 동적으로 URL을 생성하고 라우팅을 설정함으로써, 다양한 배포 환경에서 애플리케이션이 올바르게 동작하도록 합니다. 이는 개발 및 프로덕션 환경에 영향을 주지 않으면서 프리뷰 환경을 독립적으로 운영할 수 있는 기반을 마련합니다.

Highlights

  • 프리뷰 배포 기능 지원: 프리뷰 배포 기능을 구현하기 위한 기반을 마련했습니다.
  • URL 생성 로직 업데이트: VITE_BASE_PATH 환경 변수를 활용하여 애플리케이션 내에서 생성되는 모든 URL의 기본 경로를 동적으로 처리하도록 업데이트했습니다.
  • React Router 라우팅 설정: React Router의 createBrowserRouterbasename 옵션을 추가하여 라우팅 경로가 VITE_BASE_PATH에 따라 올바르게 동작하도록 설정했습니다.
  • Google OAuth state 파라미터 추가: Google OAuth 인증 요청 시 state 파라미터에 VITE_BASE_PATH를 포함하여 인증 후 리다이렉트 시 올바른 경로로 돌아올 수 있도록 했습니다.
Changelog
  • src/constants/debate_template.ts
    • createTableShareUrl 함수에서 VITE_BASE_PATH를 사용하여 공유 URL 경로를 조정했습니다.
  • src/page/DebateVotePage/DebateVotePage.tsx
    • 투표 URL 생성 시 VITE_BASE_PATH를 적용하도록 수정했습니다.
  • src/routes/routes.tsx
    • createBrowserRouterVITE_BASE_PATHbasename으로 설정하여 라우팅 기본 경로를 동적으로 처리하도록 변경했습니다.
  • src/util/arrayEncoding.ts
    • createTableShareUrl 함수에서 VITE_BASE_PATH를 사용하여 공유 URL 경로를 조정했습니다.
  • src/util/googleAuth.ts
    • Google OAuth 인증 요청 시 VITE_BASE_PATHstate 파라미터로 추가하여 리다이렉트 후 경로를 유지하도록 했습니다.
  • src/vite-env.d.ts
    • ImportMetaEnv 인터페이스에 VITE_BASE_PATH 속성을 추가하여 타입 정의를 업데이트했습니다.
  • vite.config.ts
    • Vite 설정에서 base 옵션을 VITE_BASE_PATH 환경 변수에 따라 동적으로 설정하도록 변경했습니다.
Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/cleanup-preview.yml
    • .github/workflows/deploy-preview.yml
Activity
  • 현재까지 풀 리퀘스트에 대한 활동은 제공되지 않았습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

PR이 develop 대상 PR 이벤트에 대해 Preview 배포·정리 GitHub Actions를 추가하고, VITE_BASE_PATH 환경변수를 기반으로 URL 생성, 라우팅 및 i18n 로딩 경로를 베이스패스에 맞춰 조정합니다.

Changes

Cohort / File(s) Summary
GitHub Actions 워크플로우
​.github/workflows/deploy-preview.yml, ​.github/workflows/cleanup-preview.yml
PR 오픈/업데이트 시 S3에 PR별 경로로 빌드 산출물 배포, CloudFront 무효화 및 PR 댓글로 Preview URL 게시. PR 종료 시 S3 경로 삭제 및 정리 댓글 작성.
앱 베이스패스 적용 — URL/ OAuth / 공유
src/constants/debate_template.ts, src/util/arrayEncoding.ts, src/page/DebateVotePage/DebateVotePage.tsx, src/util/googleAuth.ts
VITE_BASE_PATH가 정의된 경우 공유·투표 URL 및 OAuth state에 베이스 경로 프리픽스를 삽입하도록 URL 생성 로직 변경.
라우팅 및 i18n / Vite 설정
src/routes/routes.tsx, src/i18n.ts, vite.config.ts, src/vite-env.d.ts
라우트 트리를 최상위 래퍼(ErrorBoundaryWrapper, BackActionHandler)로 감싸고 LanguageWrapper 경로 추가. createBrowserRouter에 basename을 옵션으로 전달. Vite base 설정 추가 및 ImportMetaEnv에 VITE_BASE_PATH 타입 추가.

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant GitHub as GitHub Actions
    participant S3 as AWS S3
    participant CloudFront as CloudFront
    participant PR as PR 댓글

    rect rgba(100, 150, 255, 0.5)
    Note over User,PR: Deploy Preview (PR 열기/업데이트)
    User->>GitHub: PR 열기 또는 푸시
    GitHub->>GitHub: Node.js 설정 & 코드 체크아웃
    GitHub->>GitHub: .env 생성 (VITE_BASE_PATH 포함) 및 빌드
    GitHub->>S3: 빌드 아티팩트 업로드 (pr-{PR_NUMBER} 경로)
    GitHub->>S3: oauth-handler.html 업로드
    GitHub->>CloudFront: 캐시 무효화 (PR 경로 및 /oauth)
    GitHub->>PR: Preview URL 포함 댓글 생성/갱신
    PR-->>User: 배포 알림
    end

    rect rgba(150, 200, 100, 0.5)
    Note over User,PR: Cleanup Preview (PR 닫기)
    User->>GitHub: PR 닫기
    GitHub->>GitHub: AWS 자격증명 구성
    GitHub->>S3: pr-{PR_NUMBER} 경로 삭제
    GitHub->>PR: 정리 완료 댓글 작성
    PR-->>User: 정리 알림
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

chore

Suggested reviewers

  • useon
  • jaeml06
  • i-meant-to-be

Poem

🐰 프리뷰 길 따라 달려왔어요,
S3에 발자국 남기고 🎒,
캐시를 털어내고 댓글로 인사해요.
VITE_BASE_PATH로 길을 잇고,
깔끔하게 정리, 다시 달려요! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 PR 제목이 프리뷰 배포 기능 도입이라는 주요 변경사항을 정확하게 반영하고 있습니다.
Linked Issues check ✅ Passed PR의 코드 변경사항들이 프리뷰 배포 워크플로우 구현 요구사항을 충족하고 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 프리뷰 배포 기능 구현과 직접 관련되어 있으며 범위를 벗어난 변경은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/#421

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 and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 20, 2026

🚀 Preview 배포 완료!

환경 URL
Preview 열기
API Dev 환경

PR이 닫히면 자동으로 정리됩니다.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

프리뷰 배포를 위한 VITE_BASE_PATH 환경 변수 대응 작업을 진행해 주셨습니다. 전반적으로 필요한 부분에 basename과 경로 접두사 처리가 잘 반영되었습니다. 다만, DebateVotePage.tsx에서 baseUrlundefined일 경우 URL이 비정상적으로 생성될 수 있는 문제와, 경로 접두사를 계산하는 로직이 여러 곳에 중복되어 있는 점에 대해 개선 의견을 드립니다.

Comment on lines +36 to +38
const basePath = import.meta.env.VITE_BASE_PATH;
const pathPrefix = basePath && basePath !== '/' ? basePath : '';
return `${baseUrl}${pathPrefix}/vote/${pollId}`;

Choose a reason for hiding this comment

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

high

baseUrlundefined일 경우(특히 개발 환경에서), voteUrl"undefined/vote/..."와 같은 잘못된 문자열로 생성됩니다. 또한 baseUrl에 트레일링 슬래시가 포함되어 있을 경우 경로가 중복될 수 있으므로, 다른 파일들(debate_template.ts, arrayEncoding.ts)과 마찬가지로 window.location.origin을 폴백으로 사용하고 슬래시를 정규화하는 로직이 필요합니다.

    const resolvedBaseUrl = (baseUrl || window.location.origin).replace(/\/+$/, '');
    const basePath = import.meta.env.VITE_BASE_PATH;
    const pathPrefix = basePath && basePath !== '/' ? basePath : '';
    return `${resolvedBaseUrl}${pathPrefix}/vote/${pollId}`;

Comment on lines +19 to +20
const basePath = import.meta.env.VITE_BASE_PATH;
const pathPrefix = basePath && basePath !== '/' ? basePath : '';

Choose a reason for hiding this comment

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

medium

VITE_BASE_PATH를 기반으로 pathPrefix를 생성하는 로직이 여러 파일(debate_template.ts, DebateVotePage.tsx, arrayEncoding.ts)에서 동일하게 반복되고 있습니다. 이를 공통 유틸리티 함수로 분리하여 관리하는 것을 권장합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
.github/workflows/deploy-preview.yml (2)

82-83: 프리뷰 도메인이 하드코딩되어 있습니다.

preview.debate-timer.com이 워크플로우 스크립트에 직접 하드코딩되어 있습니다. 시크릿이나 변수로 관리하면 도메인 변경 시 워크플로우 수정 없이 대응할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-preview.yml around lines 82 - 83, The preview
domain is hardcoded when constructing url using prNumber; change this to use a
workflow variable or secret (e.g., PREVIEW_DOMAIN) instead of the literal
"https://preview.debate-timer.com", and update the url construction (where url
is defined) to interpolate that env/secret value (or default) so domain changes
are managed via workflow inputs/environment rather than editing the script.

29-33: .env 생성 시 셸 인젝션 방지를 위해 heredoc 사용을 권장합니다.

${{ vars.ENV }}${{ secrets.ENV }} 값에 셸 특수 문자가 포함되면 예기치 않은 동작이 발생할 수 있습니다. Heredoc을 사용하면 더 안전합니다.

♻️ Heredoc 방식으로 변경
       - name: Setup .env
         run: |
-          echo "${{ vars.ENV }}" > .env
-          echo "${{ secrets.ENV }}" >> .env
+          cat > .env << 'ENVEOF'
+          ${{ vars.ENV }}
+          ENVEOF
+          cat >> .env << 'ENVEOF'
+          ${{ secrets.ENV }}
+          ENVEOF
           echo "VITE_BASE_PATH=/pr-${{ github.event.pull_request.number }}" >> .env
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-preview.yml around lines 29 - 33, The "Setup .env"
step uses multiple echo calls that can be vulnerable to shell interpretation of
special characters in ${{ vars.ENV }} and ${{ secrets.ENV }}; replace the three
echo lines with a single heredoc that writes all content into .env (use a quoted
delimiter like <<'EOF' to prevent the shell from interpreting special
characters), include the expanded values ${{ vars.ENV }} and ${{ secrets.ENV }}
and the VITE_BASE_PATH line inside the heredoc, and close with EOF so the file
is created atomically and safely.
src/page/DebateVotePage/DebateVotePage.tsx (1)

36-38: base path 접두사 로직이 여러 파일에 중복되어 있습니다.

동일한 패턴이 arrayEncoding.ts, debate_template.ts, DebateVotePage.tsx, googleAuth.ts에 반복됩니다:

const basePath = import.meta.env.VITE_BASE_PATH;
const pathPrefix = basePath && basePath !== '/' ? basePath : '';

공통 유틸 함수로 추출하면 유지보수성이 향상됩니다.

♻️ 유틸 함수 추출 예시

예를 들어, src/util/basePath.ts에 아래와 같이 정의:

export function getBasePathPrefix(): string {
  const basePath = import.meta.env.VITE_BASE_PATH;
  return basePath && basePath !== '/' ? basePath : '';
}

그 후 각 파일에서 getBasePathPrefix()를 호출하는 방식으로 변경할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/DebateVotePage/DebateVotePage.tsx` around lines 36 - 38, Multiple
files duplicate the base path prefix logic (the two-line snippet calculating
pathPrefix); extract this into a shared util (e.g., export function
getBasePathPrefix() in a new util file) and replace the inline logic in
DebateVotePage (and the other occurrences like arrayEncoding.ts,
debate_template.ts, googleAuth.ts) by importing and calling getBasePathPrefix();
update the code that builds the vote URL (currently returning
`${baseUrl}${pathPrefix}/vote/${pollId}`) to use the returned prefix from
getBasePathPrefix() instead of recalculating it inline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/cleanup-preview.yml:
- Around line 23-25: The cleanup workflow's "Delete Preview from S3" step only
removes objects from S3 but doesn't invalidate CloudFront, so cached assets can
still be served; update that step to run an AWS CloudFront invalidation after
the aws s3 rm command by calling the AWS CLI (aws cloudfront
create-invalidation) using the distribution ID from a secret (e.g.,
AWS_CLOUDFRONT_DISTRIBUTION_ID) and invalidate the preview path
/pr-<pull_request_number>/* (use github.event.pull_request.number to build the
path), and ensure the step handles the operation result (fail the job on
invalidation error or wait for completion) so the preview is fully removed from
the CDN.

In @.github/workflows/deploy-preview.yml:
- Around line 54-69: The oauth-handler.html currently reads the URLSearchParams
'state' and calls window.location.replace(state + '/oauth?' + params.toString())
without validation, enabling open-redirect; update the JS in oauth-handler.html
to validate the extracted state value against an explicit whitelist or ensure it
is a same-origin path (reject absolute URLs with other origins), only perform
window.location.replace when validation passes, and otherwise render a safe
error/notice or a fixed internal redirect; keep the existing
params.delete('state') behavior but do not redirect if validation fails.

In `@src/util/googleAuth.ts`:
- Around line 17-20: The code currently sets params.state = basePath (basePath,
params.state) which enables open-redirects because oauth-handler.html blindly
redirects using state; update oauth-handler.html to treat state only as a safe
local route by validating the received state against a whitelist/pattern (e.g.,
ensure it starts with "/pr-" or matches allowed path regex) and reject or fall
back to a safe default if it doesn't match, and keep using state for CSRF tokens
separately (do not rely on it for arbitrary redirect destinations).

---

Nitpick comments:
In @.github/workflows/deploy-preview.yml:
- Around line 82-83: The preview domain is hardcoded when constructing url using
prNumber; change this to use a workflow variable or secret (e.g.,
PREVIEW_DOMAIN) instead of the literal "https://preview.debate-timer.com", and
update the url construction (where url is defined) to interpolate that
env/secret value (or default) so domain changes are managed via workflow
inputs/environment rather than editing the script.
- Around line 29-33: The "Setup .env" step uses multiple echo calls that can be
vulnerable to shell interpretation of special characters in ${{ vars.ENV }} and
${{ secrets.ENV }}; replace the three echo lines with a single heredoc that
writes all content into .env (use a quoted delimiter like <<'EOF' to prevent the
shell from interpreting special characters), include the expanded values ${{
vars.ENV }} and ${{ secrets.ENV }} and the VITE_BASE_PATH line inside the
heredoc, and close with EOF so the file is created atomically and safely.

In `@src/page/DebateVotePage/DebateVotePage.tsx`:
- Around line 36-38: Multiple files duplicate the base path prefix logic (the
two-line snippet calculating pathPrefix); extract this into a shared util (e.g.,
export function getBasePathPrefix() in a new util file) and replace the inline
logic in DebateVotePage (and the other occurrences like arrayEncoding.ts,
debate_template.ts, googleAuth.ts) by importing and calling getBasePathPrefix();
update the code that builds the vote URL (currently returning
`${baseUrl}${pathPrefix}/vote/${pollId}`) to use the returned prefix from
getBasePathPrefix() instead of recalculating it inline.

Comment on lines +23 to +25
- name: Delete Preview from S3
run: |
aws s3 rm s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --recursive
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

S3 삭제 후 CloudFront 캐시 무효화가 누락되었습니다.

deploy-preview.yml에서는 CloudFront 무효화를 수행하지만, 정리 워크플로우에서는 S3 객체만 삭제합니다. 캐시된 콘텐츠가 CloudFront 엣지에 남아 있어 삭제된 프리뷰가 TTL 만료 전까지 계속 제공될 수 있습니다.

🛠️ CloudFront 무효화 단계 추가
       - name: Delete Preview from S3
         run: |
           aws s3 rm s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --recursive
+
+      - name: Invalidate CloudFront Cache
+        run: |
+          aws cloudfront create-invalidation \
+            --distribution-id ${{ secrets.AWS_PREVIEW_CLOUDFRONT_ID }} \
+            --paths "/pr-${{ github.event.pull_request.number }}/*"
📝 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
- name: Delete Preview from S3
run: |
aws s3 rm s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --recursive
- name: Delete Preview from S3
run: |
aws s3 rm s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --recursive
- name: Invalidate CloudFront Cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.AWS_PREVIEW_CLOUDFRONT_ID }} \
--paths "/pr-${{ github.event.pull_request.number }}/*"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/cleanup-preview.yml around lines 23 - 25, The cleanup
workflow's "Delete Preview from S3" step only removes objects from S3 but
doesn't invalidate CloudFront, so cached assets can still be served; update that
step to run an AWS CloudFront invalidation after the aws s3 rm command by
calling the AWS CLI (aws cloudfront create-invalidation) using the distribution
ID from a secret (e.g., AWS_CLOUDFRONT_DISTRIBUTION_ID) and invalidate the
preview path /pr-<pull_request_number>/* (use github.event.pull_request.number
to build the path), and ensure the step handles the operation result (fail the
job on invalidation error or wait for completion) so the preview is fully
removed from the CDN.

Comment on lines +54 to +69
cat > /tmp/oauth-handler.html << 'OAUTH_EOF'
<!DOCTYPE html>
<html>
<head><title>Redirecting...</title></head>
<body>
<script>
var params = new URLSearchParams(window.location.search);
var state = params.get('state');
if (state) {
params.delete('state');
window.location.replace(state + '/oauth?' + params.toString());
}
</script>
</body>
</html>
OAUTH_EOF
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

oauth-handler.html에 오픈 리다이렉트 취약점이 있습니다.

state 파라미터를 검증 없이 리다이렉트 대상으로 사용하고 있습니다. 공격자가 Google OAuth 콜백에 악의적인 state 값(예: https://evil.com)을 삽입하면, 인증 코드가 공격자 서버로 유출될 수 있습니다.

또한 state가 없을 경우 사용자에게 "Redirecting..." 텍스트만 표시되고 아무 동작도 하지 않습니다.

🔒 state 파라미터 검증 추가
          <script>
            var params = new URLSearchParams(window.location.search);
            var state = params.get('state');
-           if (state) {
+           if (state && /^\/pr-\d+$/.test(state)) {
              params.delete('state');
              window.location.replace(state + '/oauth?' + params.toString());
+           } else {
+             window.location.replace('/oauth?' + params.toString());
            }
          </script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-preview.yml around lines 54 - 69, The
oauth-handler.html currently reads the URLSearchParams 'state' and calls
window.location.replace(state + '/oauth?' + params.toString()) without
validation, enabling open-redirect; update the JS in oauth-handler.html to
validate the extracted state value against an explicit whitelist or ensure it is
a same-origin path (reject absolute URLs with other origins), only perform
window.location.replace when validation passes, and otherwise render a safe
error/notice or a fixed internal redirect; keep the existing
params.delete('state') behavior but do not redirect if validation fails.

Comment on lines +17 to +20
const basePath = import.meta.env.VITE_BASE_PATH;
if (basePath && basePath !== '/') {
params.state = basePath;
}
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

OAuth state 파라미터를 라우팅 목적으로 사용하면 보안 위험이 있습니다.

OAuth state 파라미터는 일반적으로 CSRF 방지를 위해 사용됩니다. 여기서 basePath를 전달하는 용도로 사용하고 있는데, deploy-preview.ymloauth-handler.htmlstate 값을 기반으로 무조건 리다이렉트하므로 오픈 리다이렉트 취약점이 발생합니다.

공격자가 https://preview.debate-timer.com/oauth?code=AUTH_CODE&state=https://evil.com 같은 URL을 구성하면, oauth-handler.htmlhttps://evil.com/oauth?code=AUTH_CODE로 리다이렉트하여 인증 코드가 유출될 수 있습니다.

oauth-handler.html에서 state 값이 허용된 경로 패턴(예: /pr- 접두사)인지 검증해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/googleAuth.ts` around lines 17 - 20, The code currently sets
params.state = basePath (basePath, params.state) which enables open-redirects
because oauth-handler.html blindly redirects using state; update
oauth-handler.html to treat state only as a safe local route by validating the
received state against a whitelist/pattern (e.g., ensure it starts with "/pr-"
or matches allowed path regex) and reject or fall back to a safe default if it
doesn't match, and keep using state for CSRF tokens separately (do not rely on
it for arbitrary redirect destinations).

@unifolio0 unifolio0 deployed to PREVIEW_ENV February 20, 2026 07:00 — with GitHub Actions Active
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.

[CHORE] 프리뷰 배포

1 participant