Skip to content

[Chore] CI 구축 및 Vercel 배포 후 fork sync 자동화#278

Merged
u-zzn merged 10 commits into
developfrom
chore/ci-cd-setup/#276
May 3, 2026
Merged

[Chore] CI 구축 및 Vercel 배포 후 fork sync 자동화#278
u-zzn merged 10 commits into
developfrom
chore/ci-cd-setup/#276

Conversation

@u-zzn
Copy link
Copy Markdown
Member

@u-zzn u-zzn commented Apr 11, 2026

📌 Issue Number

#276

🔍 Changes

GitHub Actions 기반으로 CI 파이프라인 구성 + Vercel 배포 이후 fork 동기화 자동화를 추가했습니다.

  • PR / push 시 lint → typecheck → build 자동 검증 (ci.yml)
  • PR 빌드 결과를 sticky comment로 자동 업데이트
  • Discord 웹훅을 통한 빌드 결과 알림
  • 동일 브랜치 중복 CI 실행 방지 (concurrency)
  • Vercel production 배포 성공 시 fork 레포 자동 동기화 (vercel-sync.yml)
  • 기존 labeler와 중복되던 assign/reviewer 자동화 제거

📃 Describe

✔️ 왜 CI/CD 자동화를 도입했을까요?

지금까지는 PR을 올린 후에 빌드가 정상 동작하는지, 린트나 타입 오류가 없는지 수동으로 확인해야 했습니다. 이 과정은 사람이 매번 수동으로 진행하기에는 반복적이고, 놓치기 쉬운 부분이 많다고 생각했습니다.
또한 현재 띵스룸은 Vercel 배포 + 개인 fork 기반 작업 구조를 사용하고 있기 때문에 배포 이후 fork와의 동기화도 수동으로 관리해야 하는 불편함이 있었습니다 😥

그래서 PR 단계에서 코드 품질을 자동 검증하고, 배포 이후 fork 상태까지 자동으로 맞추는 CI + 배포 후 후처리 자동화 구조를 도입하게 되었습니다 💪


✔️ CI 파이프라인 구조 (ci.yml)

PR 및 main/develop 브랜치에 대해 아래 순서로 자동 검증이 수행됩니다.

lint → typecheck → build

☑️ 트리거 전략

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

현재 브랜치 전략 기준으로 보면

  • develop → 기능 통합 및 테스트 단계
  • main → 실제 배포 기준 브랜치

이 두 브랜치는 모두 코드 안정성이 중요한 구간이기 때문에 PR 또는 push 시 CI를 통해 반드시 검증이 필요하다고 판단했습니다.
반면, feature 브랜치까지 CI를 모두 실행할 경우 잦은 push로 인해 불필요한 Actions 실행이 증가하고 로그 관리가 복잡해질 것 같아서 develop 브랜치와 main 브랜치에만 CI 검증을 적용했습니다.

☑️ concurrency 설정

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

같은 브랜치에서 여러 번 push가 발생하면 이전 CI를 자동으로 취소하여 가장 최신 커밋 기준의 CI 결과만 확인할 수 있도록 했습니다.
github.ref 기준으로 브랜치별 CI 그룹을 만들고, 새로운 push가 들어오면 기존 실행을 자동 취소하도록 설정했습니다.

☑️ pnpm 캐시 최적화
기존에는 actions/cache를 사용해 pnpm store 경로를 직접 구하고, 캐시 key까지 수동으로 설정해야 했습니다.
이번 작업을 통해 actions/setup-node@v4의 내장 캐시 옵션을 사용해 설정을 간소화했습니다.

uses: actions/setup-node@v4
with:
  cache: "pnpm"

pnpm-lock.yaml 기준으로 의존성 캐시가 관리되기 때문에, 불필요한 설치 시간을 줄이면서도 워크플로우 설정을 더 간단하게 유지할 수 있도록 했습니다 :)

☑️ typecheck 스크립트 재사용

pnpm run lint && pnpm run typecheck

tsc --noEmit을 CI에서 직접 실행하는 대신, package.json에 정의된 typecheck 스크립트를 재사용하도록 했습니다.
이렇게 하면 로컬에서 실행하는 검사 방식과 CI에서 실행하는 검사 방식이 동일해지고, 나중에 typecheck 옵션이 변경되더라도 package.json만 수정하면 되어 유지보수가 더 쉬워진다고 생각했습니다!

☑️ PR 결과 자동 코멘트

marocchino/sticky-pull-request-comment

PR에서 CI가 실행될 때마다 lint / build 결과를 코멘트로 남기도록 설정했습니다.

일반 코멘트 방식은 push할 때마다 새로운 코멘트가 계속 쌓일 수 있지만, sticky-pull-request-comment는 동일한 header의 코멘트를 덮어쓰는 방식으로 동작합니다.
결과적으로 PR에는 항상 가장 최신 CI 결과 하나만 유지됩니다!

☑️ Discord 알림
PR 기준으로만 Discord 알림이 전송되도록 설정했습니다.
전에 타 프로젝트에서 작업할 때 commit마다 알림을 보냈었는데, push가 여러 번 발생할 때마다 Discord 메시지가 계속 쌓여서 개별 commit보다는 PR 단위로 빌드 성공/실패 상태를 공유해도 괜찮지 않을까? 생각했습니다.

이제 GitHub Actions 탭을 직접 확인하지 않아도, Discord에서 PR의 검증 상태를 빠르게 확인하실 수 있습니다 :)
혹시 알림 단위를 commit 별로 변경하는 것이 더 좋으면 알려주세요 ☺️

☑️ assign job 제거
처음에는 CI workflow 안에서도 reviewer / assignee를 자동 지정하도록 코드를 구현했지만, 이미 사전에 작업한 labeler.yml에서 동일한 역할을 처리하고 있었습니다.

두 workflow에서 같은 작업을 동시에 수행할 필요가 없다고 생각했고, CI의 목적도 코드 검증이 아닌 PR 관리 자동화까지 섞이게 된다고 판단했습니다.

그래서 reviewer / assignee 지정은 labeler.yml에서만 담당하도록 정리하고, CI에서는 lint / typecheck / build 같은 순수 검증 역할만 수행하도록 분리했습니다 :)


✔️ Vercel 배포 후 fork 자동 동기화 (vercel-sync.yml)

☑️ 왜 필요한가?
현재 띵스룸은 아래와 같은 구조로 운영되고 있습니다.

  • 조직 레포 → 개발 진행
  • 개인 fork → Vercel 배포 연결

이 구조에서는 조직 레포의 main 브랜치에 변경사항이 머지되더라도, 개인 fork 레포가 자동으로 최신 상태로 맞춰지지 않아서 배포 이후 개인 fork에서 직접 sync를 맞춰줘야 했기 때문에, 누락 가능성도 있고 반복 작업이 불필요하게 계속 발생한다고 생각해왔습니다 😥

그래서 Vercel 배포가 성공한 이후, 개인 fork 레포도 자동으로 최신 상태가 되도록 동기화 작업을 추가했습니다!

☑️ 트리거 방식

repository_dispatch:
  types: [vercel.deployment.success]

Vercel에서 배포가 성공했을 때 전달되는 vercel.deployment.success 이벤트를 기준으로 workflow가 실행되도록 설정했습니다.
즉, 단순히 main에 머지되었을 때 바로 sync하는 것이 아니라, Vercel 배포가 성공한 이후에 fork sync가 실행되도록 구성했습니다!

또한 workflow_dispatch도 함께 추가하여, 초기 테스트나 sync가 누락된 경우에는 GitHub Actions 탭에서 수동으로 실행할 수 있도록 했습니다 :)

☑️ 실행 조건

production + main 브랜치만 동작

Vercel은 PR 생성 시에도 preview 배포를 생성하기 때문에, 모든 배포 성공 이벤트에 반응하면 PR preview 배포 때마다 fork sync가 반복될 수 있다는 것을 작업 하면서 새롭게 알게 되었습니다.

fork 동기화는 실제 배포 기준인 main 브랜치의 production 배포 이후에만 필요하다고 판단해서 production 환경이면서 main 브랜치에서 발생한 배포 성공 이벤트일 때만 동작하도록 조건을 제한했습니다!

☑️ fork 동기화 방식

tgymnich/fork-sync

fork 동기화는 tgymnich/fork-sync 액션을 사용하도록 구성했습니다.

기본 GITHUB_TOKEN만으로는 개인 fork 레포에 접근하거나 push 권한이 부족할 수 있기 때문에, 별도로 발급한 FORK_SYNC_TOKEN을 GitHub Secrets에 등록하여 사용하도록 했습니다.

이를 통해 Vercel 배포 성공 이후 upstream → fork 방향으로 자동 동기화가 이루어지도록 했습니다!

☑️ Debug step 추가
초기 연동 단계에서는 Vercel에서 전달되는 webhook payload가 예상한 형태로 들어오는지 확인이 필요하다고 판단했습니다.
그래서 environment, git_ref, deployment_url 등의 값을 Actions 로그에 출력하도록 Debug step을 추가했습니다.
이를 통해 production 배포인지, main 브랜치 기준 배포인지, 배포 URL이 정상적으로 전달되는지 확인할 수 있습니다.
추후 webhook 동작이 안정적으로 확인되면 해당 step은 제거하거나 유지 여부를 다시 검토할 수 있을 것 같습니다 🙂

☑️ GitHub Actions 버전 수정
초기에는 actions/checkout@v5, actions/setup-node@v5처럼 @v5 버전을 사용하고자 했습니다.

하지만 GitHub Actions는 각 action마다 개별적으로 버전이 관리되기 때문에, 모든 action이 동일하게 최신 버전을 제공하지는 않습니다. 현재 기준에서 안정적으로 사용할 수 있는 버전은 @v4였으며, @v5는 해당 action에서 제공되지 않아 workflow 실행 시 오류가 발생할 수 있었습니다.

항목 v5 v4
버전 존재 여부 ❌ 해당 action에서 미지원 ✅ 공식 제공 버전
릴리즈 상태 정의되지 않음 안정적인 stable 버전
버전 관리 방식 사용 불가 (레포에 존재하지 않음) GitHub에서 관리되는 주요 릴리즈
사용 가능성 실행 시 action not found 오류 발생 정상 실행 가능

따라서 실제 사용 가능한 stable 버전인 @v4로 수정하여, workflow가 정상적으로 동작하도록 구성했습니다 :)

☑️ pnpm 버전 관리 방식 개선
초기에는 workflow 안에서 pnpm 버전을 직접 지정하려고 했지만, 이미 package.jsonpackageManager 필드로 pnpm 버전이 명시되어 있었습니다.

그래서 workflow에서 version을 하드코딩하지 않고, package.jsonpackageManager 값을 기준으로 pnpm 버전이 결정되도록 수정했습니다.


✔️ 왜 CD 워크플로우를 따로 만들지 않았을까요?

초기에는 GitHub Actions 기반으로 cd.yml을 별도로 구성하여 배포까지 자동화하는 방향을 고려했습니다.
하지만 현재 띵스룸은 Vercel과 개인 fork 레포가 연결된 구조로 이미 운영되고 있었기 때문에, 일반적인 CD 파이프라인을 그대로 적용하는 것은 적절하지 않다고 판단했습니다.

현재 구조에서는 코드가 main 브랜치에 머지되면 Vercel이 자동으로 build와 deploy를 수행하고, 실제 서비스 배포 역시 Vercel이 담당하고 있습니다. 즉, 배포 자체는 이미 완전히 자동화되어 있는 상태이기 때문에, GitHub Actions에서 다시 배포(CD)를 구성하면 역할이 중복되는 구조가 됩니다.

또한 띵스룸은 이미 개인 fork 레포를 기반으로 배포가 이루어지는 흐름으로 이루어져 있는 상황이기에, 단순히 “배포를 실행하는 CD”보다 배포 이후 fork 레포의 상태를 최신으로 맞춰주는 후처리 작업이 더 중요한 상황이라고 판단했습니다.

이러한 이유로 CD 워크플로우를 따로 두는 대신,

  • CI → 코드 품질 검증 (ci.yml)
  • CD → Vercel이 담당
  • 배포 이후 처리 → fork sync (vercel-sync.yml)

와 같이 역할을 분리하고, Vercel 배포 성공 이후에 fork 레포를 자동 동기화하는 vercel-sync.yml을 별도로 구성하게 되었습니다!

하나의 워크플로우에서 build와 deploy를 모두 처리하는 방식이 아니라, 현재 띵스룸 구조에 맞게 “배포는 Vercel에 맡기고, GitHub Actions는 검증과 후처리에 집중하는 구조”로 설계하는 것이 더 적절하다고 생각했습니다 ☺️

👀 To Reviewer

  • CI 단계에서 build까지 포함하는 것이 적절한지, 아니면 Vercel에 맡기는게 더 나은지
  • concurrency 설정이 현재 띵스룸 서비스 규모에서 적절한지
  • Discord 알림을 PR 기준으로만 보내는 구조가 괜찮은지 (세세하게 commit 별로 전부 보내는 구조가 더 나을까욤? 🤔)
  • Vercel 배포 성공 이후 vercel-sync.yml로 개인 fork를 동기화하는 방식이 안정적인지

확인 부탁드립니다 🙂

또한 현재는 배포 이후 sync를 맞춰야 하는 구조를 기준으로 자동화를 구성했는데, 해당 방식을 제거하고 처음부터 조직 레포 기준으로 CD 플로우를 다시 구축하는 것이 더 나을지도 함께 의견 부탁드립니다 🙇

📸 Screenshot

PR 빌드 결과 자동 코멘트

image

Discord 알림 정상 동작

image

Summary by CodeRabbit

Chores

  • CI 자동화 워크플로우 추가: 푸시/PR에서 린트·타입체크·빌드 실행 및 결과 요약을 PR에 자동 게시
  • 빌드 상태 알림 개선: Discord 웹훅으로 빌드/타입체크 결과 알림 전송
  • 실패 감지 강화: 린트·빌드 결과에 따른 전체 빌드 상태 체크 추가(문제 발생 시 워크플로우 실패)
  • 포크 동기화 워크플로우 추가: 메인 브랜치 동기화 자동화 및 수동 실행 지원

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 11, 2026

🚀 빌드 결과

🩵 린트 검사 완료
💙 빌드 성공

✔️ 로그 확인하기
Actions 탭에서 자세히 보기!

@github-actions github-actions Bot requested a review from jogpfls April 11, 2026 09:46
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

두 개의 GitHub Actions 워크플로우가 추가/교체됩니다. ci.yml은 pnpm 기반으로 린트·타입체크·빌드를 실행하고 PR 코멘트 및 Discord 알림을 게시하며 빌드 실패 조건을 체크합니다. vercel-sync.ymlmain 브랜치 푸시(또는 수동) 시 포크의 main을 업스트림 main과 동기화합니다.

Changes

CI workflow

Layer / File(s) Summary
Workflow Metadata & Triggers
.github/workflows/ci.yml
새 워크플로우 ci 추가, pushpull_request 대상(main, develop).
Permissions & Concurrency
.github/workflows/ci.yml
리포지토리 권한 contents: read, pull-requests: write; concurrency: ci-${{ github.ref }}cancel-in-progress: true.
Environment Setup
.github/workflows/ci.yml
Ubuntu 런너, Node.js 20.x 설정, pnpm 설치 및 캐시 구성, pnpm install --frozen-lockfile.
Lint/Typecheck & Build Steps
.github/workflows/ci.yml
pnpm run lint/타입체크와 pnpm run build를 별도 단계로 실행하며 각각 continue-on-error: true로 결과를 기록.
PR Commenting & Notifications
.github/workflows/ci.yml
풀리퀘일 때 스티키 PR 코멘트로 린트·타입체크·빌드 결과를 게시/갱신하고, Discord 웹훅으로 임베드 형식 알림 전송(빌드 결과에 따라 제목/색상 변경).
Final Status Check
.github/workflows/ci.yml
람다식/조건으로 lint/typecheck 또는 build 실패 시 워크플로우를 실패시키는 “Check Build Status” 단계 포함(exits with code 1).

Vercel sync workflow

Layer / File(s) Summary
Workflow Metadata & Triggers
.github/workflows/vercel-sync.yml
기존 워크플로우 교체: name: sync-fork-for-vercel-deploy, on: push(to main) 및 workflow_dispatch.
Permissions & Job
.github/workflows/vercel-sync.yml
권한 contents: write, 단일 sync-fork 잡이 ubuntu-latest에서 실행.
Checkout & Fork Sync
.github/workflows/vercel-sync.yml
actions/checkout@v4로 포크 체크아웃(토큰: secrets.FORK_SYNC_TOKEN), tgymnich/fork-sync@v2.3.1로 업스트림 main을 포크 main에 동기화(owner 및 토큰 인수 포함).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

자동화가 귀 기울여, 작은 체크들이 밤을 밝히네,
린트는 속삭이고 빌드는 박수를 치며,
PR엔 흔적을 남기고 디스코드는 소식을 전하네,
포크는 본사와 손을 잡아 길을 잇고,
흐름 하나가 조용히 완성된다. 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경 사항의 주요 내용과 직접적으로 관련되어 있습니다. CI 구축과 Vercel 배포 후 fork sync 자동화라는 제목이 추가된 두 개의 워크플로우(ci.yml, vercel-sync.yml)의 목적을 정확하게 반영하고 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/ci-cd-setup/#276

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

Copy link
Copy Markdown

@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 (1)
.github/workflows/ci.yml (1)

96-104: assign 잡은 job-level if로 올리면 러너 낭비를 줄일 수 있어요.

지금은 push 이벤트에서도 job이 생성된 뒤 step에서만 skip돼. job-level 조건으로 올리면 워크플로우가 더 깔끔해져.

리팩터링 예시 diff
   assign:
+    if: github.event_name == 'pull_request' && github.event.action == 'opened'
     runs-on: ubuntu-latest
     steps:
       - name: auto-assign-author-and-reviewer
-        if: github.event_name == 'pull_request' && github.event.action == 'opened'
         uses: hkusu/review-assign-action@v1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 96 - 104, 현재 assign job에 if 조건이 steps
안에 있어 푸시 이벤트에서 불필요하게 러너가 할당됩니다; assign job 블록(심볼: assign) 바로 아래에 if:
github.event_name == 'pull_request' && github.event.action == 'opened'를 옮겨서 job
레벨에서 평가되도록 하고, 해당 조건은 steps의 auto-assign-author-and-reviewer step(uses:
hkusu/review-assign-action@v1)에서 제거하여 전체 워크플로우가 푸시 이벤트 시 이 job을 생성조차 하지 않게
변경하세요.
🤖 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/ci.yml:
- Around line 21-25: The workflow pins GitHub Actions using mutable tags (e.g.,
actions/checkout@v4, pnpm/action-setup@v4, actions/setup-node@v4,
marocchino/sticky-pull-request-comment@v2, hkusu/review-assign-action@v1);
replace each tag reference with the corresponding immutable commit SHA for that
action (lookup the desired release commit SHA on the action's GitHub/Marketplace
page and replace the uses value, e.g., actions/checkout@<commit-sha>), ensuring
every occurrence of those identifiers is updated to the specific SHA to prevent
tag re-assignment risks.
- Around line 66-90: The Notify Discord Alarm step injects PR fields directly
into the shell and JSON, risking command injection and broken JSON; update the
step to set PR_TITLE, PR_USER, PR_URL and DISCORD_WEBHOOK_URL via an env: block
(not inline expansion), validate that the DISCORD_WEBHOOK_URL secret exists
before running, and build the JSON payload using a safe encoder like jq with
--arg to escape title/author/url and include steps.build_step.outcome to choose
COLOR/TITLE; reference the existing variables PR_TITLE, PR_USER, PR_URL,
steps.build_step.outcome and secrets.DISCORD_WEBHOOK_URL when implementing these
changes.

In @.github/workflows/vercel-sync.yml:
- Around line 8-10: The workflow grants unnecessary write permissions: the
permissions block currently lists "contents: write" and "pull-requests: write";
reduce privileges by removing or downgrading "pull-requests: write" (e.g.,
remove the "pull-requests" entry or change it to "pull-requests: read") so only
required rights remain (keep "contents: write" if checkout/fork sync needs it);
update the permissions block accordingly to follow least-privilege principles.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 96-104: 현재 assign job에 if 조건이 steps 안에 있어 푸시 이벤트에서 불필요하게 러너가
할당됩니다; assign job 블록(심볼: assign) 바로 아래에 if: github.event_name == 'pull_request'
&& github.event.action == 'opened'를 옮겨서 job 레벨에서 평가되도록 하고, 해당 조건은 steps의
auto-assign-author-and-reviewer step(uses: hkusu/review-assign-action@v1)에서 제거하여
전체 워크플로우가 푸시 이벤트 시 이 job을 생성조차 하지 않게 변경하세요.
🪄 Autofix (Beta)

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

Run ID: 204bd68f-d47d-43af-9791-8038ca6ac350

📥 Commits

Reviewing files that changed from the base of the PR and between 7c515e4 and b19b6f2.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .github/workflows/vercel-sync.yml

Comment thread .github/workflows/ci.yml
Comment on lines +21 to +25
- name: Checkout Code
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# SHA pinning 누락된 uses 식별
rg -nP '^\s*uses:\s*[^@]+@(v[0-9]+(\.[0-9]+(\.[0-9]+)?)?|main|master)\s*$' .github/workflows/*.yml

Repository: DdingSroom/dding-sroom-frontend

Length of output: 568


🏁 Script executed:

cat -n .github/workflows/ci.yml | head -110 | tail -90

Repository: DdingSroom/dding-sroom-frontend

Length of output: 3502


🏁 Script executed:

git ls-files .github/workflows/

Repository: DdingSroom/dding-sroom-frontend

Length of output: 133


🏁 Script executed:

find . -maxdepth 2 -type f \( -name "SECURITY.md" -o -name "security.md" -o -name ".github" -o -name "README.md" \) | head -20

Repository: DdingSroom/dding-sroom-frontend

Length of output: 86


🏁 Script executed:

cat -n README.md | head -50

Repository: DdingSroom/dding-sroom-frontend

Length of output: 1590


GitHub Actions의 SHA 커밋 해시로 고정해서 공급망 공격 리스크 줄이기

현재 워크플로우에서 사용하는 액션들이 버전 태그(v4, v2, v1)로만 고정되어 있는데, 태그는 언제든지 다른 커밋을 가리키도록 재지정될 수 있어요. 누군가가 해당 태그를 의도적으로(또는 실수로) 변경하면 예상과 다른 코드가 실행되는 보안 사고로 이어질 수 있습니다.

특히 CI/CD 워크플로우는 저장소에 대한 권한을 가진 상태에서 실행되기 때문에, 이 지점을 통한 공급망 공격(supply chain attack)은 상당히 심각한 결과를 초래할 수 있어요.

다음처럼 구체적인 커밋 SHA로 고정하는 것이 안전합니다:

수정 예시
# 현재 (위험함)
- uses: actions/checkout@v4

# 변경 후 (안전함)
- uses: actions/checkout@c85c95e3d7251135ab7dc9f2f1aaf36f27022370

각 액션의 최신 SHA는 GitHub Marketplace에서 확인할 수 있습니다. 액션 페이지의 "Used by" 섹션이나 릴리스 페이지에서 원하는 버전의 커밋 해시를 찾아 적용하면 돼요.

수정이 필요한 라인들:

  • 22: actions/checkout@v4
  • 25: pnpm/action-setup@v4
  • 30: actions/setup-node@v4
  • 50: marocchino/sticky-pull-request-comment@v2
  • 101: hkusu/review-assign-action@v1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 21 - 25, The workflow pins GitHub
Actions using mutable tags (e.g., actions/checkout@v4, pnpm/action-setup@v4,
actions/setup-node@v4, marocchino/sticky-pull-request-comment@v2,
hkusu/review-assign-action@v1); replace each tag reference with the
corresponding immutable commit SHA for that action (lookup the desired release
commit SHA on the action's GitHub/Marketplace page and replace the uses value,
e.g., actions/checkout@<commit-sha>), ensuring every occurrence of those
identifiers is updated to the specific SHA to prevent tag re-assignment risks.

Comment thread .github/workflows/ci.yml
Comment on lines +66 to +90
- name: Notify Discord Alarm
if: github.event_name == 'pull_request' && always()
run: |
PR_TITLE="${{ github.event.pull_request.title }}"
PR_USER="${{ github.event.pull_request.user.login }}"
PR_URL="${{ github.event.pull_request.html_url }}"

if [ "${{ steps.build_step.outcome }}" == "success" ]; then
COLOR=3066993
TITLE="💙 빌드 성공"
else
COLOR=15158332
TITLE="😭 빌드 실패"
fi

DESCRIPTION="**📄 Title : \`$PR_TITLE\`\n ✏️ 작성자 : \`$PR_USER\`\n\n[🔎 PR보러가기]($PR_URL)"
curl -H "Content-Type: application/json" \
-d "{
\"embeds\": [{
\"title\": \"$TITLE\",
\"description\": \"$DESCRIPTION\",
\"color\": $COLOR
}]
}" \
"${{ secrets.DISCORD_WEBHOOK_URL }}"
Copy link
Copy Markdown

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

🏁 Script executed:

#!/bin/bash
# 직접 삽입 패턴(잠재적 인젝션 지점) 확인
rg -nP '\${{\s*github\.event\.pull_request\.(title|body|user\.login|html_url)\s*}}' .github/workflows/ci.yml -C2

# 문자열로 JSON을 수동 조합하는 패턴 확인
rg -nP 'curl .* -d "\{' .github/workflows/ci.yml -C2

Repository: DdingSroom/dding-sroom-frontend

Length of output: 426


🏁 Script executed:

cat -n .github/workflows/ci.yml | sed -n '60,95p'

Repository: DdingSroom/dding-sroom-frontend

Length of output: 1615


🏁 Script executed:

# 워크플로우 파일에서 다른 PR 메타데이터 삽입 패턴 확인
rg -n 'github\.event\.pull_request\.' .github/workflows/ci.yml

# 다른 curl/JSON 구성 패턴 확인
rg -n 'curl.*-d' .github/workflows/ci.yml -A3

Repository: DdingSroom/dding-sroom-frontend

Length of output: 268


PR 메타데이터 직접 삽입으로 인한 커맨드 인젝션 및 JSON 페이로드 손상 위험

69~71줄의 ${{ github.event.pull_request.title }} 같은 값들이 셸 스크립트 본문에 직접 삽입되면, 제목에 백틱이나 $() 같은 셸 메타문자가 포함될 때 명령 치환이 발생할 수 있어.

더불어 81~89줄처럼 변수를 문자열 기반으로 JSON에 직접 넣으면 PR 제목이나 저자명에 큰따옴표나 개행이 들어왔을 때 JSON 구조가 깨진다. 포크 PR에서는 DISCORD_WEBHOOK_URL 시크릿이 주입되지 않아 단계가 예기치 않게 실패할 수도 있고.

다음처럼 개선하는 게 좋아:

  • env: 블록에서 PR 메타데이터와 시크릿을 환경 변수로 설정
  • jq --arg를 활용해 값을 제대로 이스케이프한 JSON 생성
  • 시크릿 존재 여부를 조건에서 미리 확인
개선 예시
       - name: Notify Discord Alarm
-        if: github.event_name == 'pull_request' && always()
+        if: github.event_name == 'pull_request' && always() && secrets.DISCORD_WEBHOOK_URL != ''
+        env:
+          PR_TITLE: ${{ github.event.pull_request.title }}
+          PR_USER: ${{ github.event.pull_request.user.login }}
+          PR_URL: ${{ github.event.pull_request.html_url }}
+          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
         run: |
-          PR_TITLE="${{ github.event.pull_request.title }}"
-          PR_USER="${{ github.event.pull_request.user.login }}"
-          PR_URL="${{ github.event.pull_request.html_url }}"
-
           if [ "${{ steps.build_step.outcome }}" == "success" ]; then
             COLOR=3066993
             TITLE="💙 빌드 성공"
@@
-          DESCRIPTION="**📄 Title : \`$PR_TITLE\`\n ✏️ 작성자 : \`$PR_USER\`\n\n[🔎 PR보러가기]($PR_URL)"
-          curl -H "Content-Type: application/json" \
-               -d "{
-                 \"embeds\": [{
-                   \"title\": \"$TITLE\",
-                   \"description\": \"$DESCRIPTION\",
-                   \"color\": $COLOR
-                 }]
-               }" \
-               "${{ secrets.DISCORD_WEBHOOK_URL }}"
+          DESCRIPTION="**📄 Title : \`$PR_TITLE\`\n ✏️ 작성자 : \`$PR_USER\`\n\n[🔎 PR보러가기]($PR_URL)"
+          jq -n \
+            --arg title "$TITLE" \
+            --arg description "$DESCRIPTION" \
+            --argjson color "$COLOR" \
+            '{embeds:[{title:$title,description:$description,color:$color}]}' \
+          | curl -fsS -H "Content-Type: application/json" --data `@-` "$DISCORD_WEBHOOK_URL"
🧰 Tools
🪛 actionlint (1.7.12)

[error] 68-68: "github.event.pull_request.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

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

In @.github/workflows/ci.yml around lines 66 - 90, The Notify Discord Alarm step
injects PR fields directly into the shell and JSON, risking command injection
and broken JSON; update the step to set PR_TITLE, PR_USER, PR_URL and
DISCORD_WEBHOOK_URL via an env: block (not inline expansion), validate that the
DISCORD_WEBHOOK_URL secret exists before running, and build the JSON payload
using a safe encoder like jq with --arg to escape title/author/url and include
steps.build_step.outcome to choose COLOR/TITLE; reference the existing variables
PR_TITLE, PR_USER, PR_URL, steps.build_step.outcome and
secrets.DISCORD_WEBHOOK_URL when implementing these changes.

Comment thread .github/workflows/vercel-sync.yml Outdated
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (2)
.github/workflows/ci.yml (2)

22-25: ⚠️ Potential issue | 🟠 Major

GitHub Actions를 태그 대신 커밋 SHA로 고정해줘.

uses: ...@v4/v2/v1 형태는 태그 재지정 리스크가 있어서 CI 공급망 보안에 취약해. 전부 불변 커밋 SHA로 고정하는 게 안전해.

#!/bin/bash
# mutable tag 사용 여부 점검 (read-only)
rg -nP '^\s*uses:\s*[^@]+@(v[0-9]+(\.[0-9]+(\.[0-9]+)?)?|main|master)\s*$' .github/workflows/*.yml

Also applies to: 28-28, 48-48, 99-99

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

In @.github/workflows/ci.yml around lines 22 - 25, Replace mutable GitHub Action
tags with immutable commit SHAs: for each occurrence like uses:
actions/checkout@v4 and uses: pnpm/action-setup@v4 (and other uses: ...@vX or
`@main/`@master), look up the corresponding commit SHA on the action's upstream
repository and update the workflow lines to uses:
actions/checkout@<full-commit-sha> and uses:
pnpm/action-setup@<full-commit-sha>; ensure you update every instance mentioned
(including the ones at the other reported locations) and keep the exact action
name and inputs unchanged so only the reference is hardened.

66-69: ⚠️ Potential issue | 🔴 Critical

PR 메타데이터를 인라인 셸/수동 JSON에 직접 넣는 패턴은 인젝션·페이로드 깨짐 위험이 있어.

github.event.pull_request.* 값을 run 스크립트 내부에서 직접 확장하고, curl -d "{...}"로 JSON을 수동 조합하고 있어.
env:로 값 전달 + jq --arg로 JSON 생성 + secrets.DISCORD_WEBHOOK_URL != '' 가드까지 넣어서 안전하게 바꿔줘.

Also applies to: 79-88

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

In @.github/workflows/ci.yml around lines 66 - 69, The run step currently
expands github.event.pull_request.* directly into shell variables (PR_TITLE,
PR_USER, PR_URL) and builds JSON with curl -d, which risks injection; change the
job to pass these values via the step's env: mapping (e.g., set PR_TITLE,
PR_USER, PR_URL in env), add a conditional guard checking
secrets.DISCORD_WEBHOOK_URL != '' for the step, and construct the payload using
jq with --arg (e.g., jq -n --arg title "$PR_TITLE" --arg user "$PR_USER" --arg
url "$PR_URL" '{title:$title, user:$user, url:$url}') and pipe that to curl
--data `@-` instead of inlining shell-expanded JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/ci.yml:
- Around line 22-25: Replace mutable GitHub Action tags with immutable commit
SHAs: for each occurrence like uses: actions/checkout@v4 and uses:
pnpm/action-setup@v4 (and other uses: ...@vX or `@main/`@master), look up the
corresponding commit SHA on the action's upstream repository and update the
workflow lines to uses: actions/checkout@<full-commit-sha> and uses:
pnpm/action-setup@<full-commit-sha>; ensure you update every instance mentioned
(including the ones at the other reported locations) and keep the exact action
name and inputs unchanged so only the reference is hardened.
- Around line 66-69: The run step currently expands github.event.pull_request.*
directly into shell variables (PR_TITLE, PR_USER, PR_URL) and builds JSON with
curl -d, which risks injection; change the job to pass these values via the
step's env: mapping (e.g., set PR_TITLE, PR_USER, PR_URL in env), add a
conditional guard checking secrets.DISCORD_WEBHOOK_URL != '' for the step, and
construct the payload using jq with --arg (e.g., jq -n --arg title "$PR_TITLE"
--arg user "$PR_USER" --arg url "$PR_URL" '{title:$title, user:$user,
url:$url}') and pipe that to curl --data `@-` instead of inlining shell-expanded
JSON.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81f86a72-3b0d-4662-aaa5-ccf8e933071f

📥 Commits

Reviewing files that changed from the base of the PR and between b19b6f2 and 96774d4.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Copy link
Copy Markdown

@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: 1

♻️ Duplicate comments (2)
.github/workflows/ci.yml (2)

64-89: ⚠️ Potential issue | 🔴 Critical

Discord 알림 step의 인라인 PR 값 주입/수동 JSON 조합을 제거해줘

Line 67-69의 직접 삽입은 셸 인젝션 면에서 취약하고, Line 81-88 문자열 JSON은 제목/작성자에 따옴표·개행이 들어오면 깨질 수 있어. 또한 시크릿 미존재 시 실행 자체를 막는 가드도 필요해. 이전 코멘트와 동일 이슈가 남아 있어.

개선 예시
-      - name: Notify Discord Alarm
-        if: github.event_name == 'pull_request' && always()
-        run: |
-          PR_TITLE="${{ github.event.pull_request.title }}"
-          PR_USER="${{ github.event.pull_request.user.login }}"
-          PR_URL="${{ github.event.pull_request.html_url }}"
+      - name: Notify Discord Alarm
+        if: github.event_name == 'pull_request' && always() && secrets.DISCORD_WEBHOOK_URL != ''
+        env:
+          PR_TITLE: ${{ github.event.pull_request.title }}
+          PR_USER: ${{ github.event.pull_request.user.login }}
+          PR_URL: ${{ github.event.pull_request.html_url }}
+          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
+        run: |

           if [ "${{ steps.build_step.outcome }}" == "success" ]; then
             COLOR=3066993
             TITLE="💙 빌드 성공"
           else
             COLOR=15158332
             TITLE="😭 빌드 실패"
           fi

           DESCRIPTION="📄 Title : \`$PR_TITLE\`\n ✏️ 작성자 : \`$PR_USER\`\n\n[🔎 PR보러가기]($PR_URL)"
-          curl -H "Content-Type: application/json" \
-               -d "{
-                 \"embeds\": [{
-                   \"title\": \"$TITLE\",
-                   \"description\": \"$DESCRIPTION\",
-                   \"color\": $COLOR
-                 }]
-               }" \
-               "${{ secrets.DISCORD_WEBHOOK_URL }}"
+          jq -n \
+            --arg title "$TITLE" \
+            --arg description "$DESCRIPTION" \
+            --argjson color "$COLOR" \
+            '{embeds:[{title:$title,description:$description,color:$color}]}' \
+          | curl -fsS -H "Content-Type: application/json" --data `@-` "$DISCORD_WEBHOOK_URL"
#!/bin/bash
# 인라인 PR 메타데이터 삽입 및 수동 JSON 구성 패턴 재검증
rg -nP '\${{\s*github\.event\.pull_request\.(title|body|user\.login|html_url)\s*}}' .github/workflows/ci.yml -C2
rg -nP 'curl .* -d "\{' .github/workflows/ci.yml -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 64 - 89, The Discord notification step
("Notify Discord Alarm") currently injects PR fields into shell variables and
constructs JSON inline (PR_TITLE, PR_USER, PR_URL) which is unsafe and brittle;
change it to (1) guard early by checking that the DISCORD_WEBHOOK_URL secret is
set and exit the step if not, (2) stop interpolating GitHub context directly
into shell by passing PR metadata via GitHub Actions env/input (or use the
built-in github context in a YAML-encoded action step), and (3) build/encode the
JSON payload safely using a JSON-encoder (e.g., jq, gh api --input, or GitHub
Actions' toJson) instead of manual string concatenation so quotes/newlines are
escaped. Also keep the existing logic that selects COLOR/TITLE based on
steps.build_step.outcome but reference that outcome safely from the job context
rather than injecting into shell unescaped.

22-28: ⚠️ Potential issue | 🟠 Major

GitHub Action uses를 태그가 아니라 커밋 SHA로 고정해줘

Line 22, Line 25, Line 28, Line 48이 모두 가변 태그(v4, v2)라 공급망 리스크가 남아 있어. 이전 코멘트에서 이미 지적된 내용이고 아직 반영되지 않았어.

예시 수정
-      - name: Checkout Code
-        uses: actions/checkout@v4
+      - name: Checkout Code
+        uses: actions/checkout@<full-commit-sha>

-      - name: Install pnpm
-        uses: pnpm/action-setup@v4
+      - name: Install pnpm
+        uses: pnpm/action-setup@<full-commit-sha>

-      - name: Setup Node.js
-        uses: actions/setup-node@v4
+      - name: Setup Node.js
+        uses: actions/setup-node@<full-commit-sha>

-      - name: Output build result
-        uses: marocchino/sticky-pull-request-comment@v2
+      - name: Output build result
+        uses: marocchino/sticky-pull-request-comment@<full-commit-sha>
#!/bin/bash
# mutable tag 사용 지점 재검증
rg -nP '^\s*uses:\s*[^@]+@(v[0-9]+(\.[0-9]+(\.[0-9]+)?)?|main|master)\s*$' .github/workflows/*.yml

Also applies to: 48-48

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

In @.github/workflows/ci.yml around lines 22 - 28, The workflow uses mutable
tags for third-party actions (e.g., uses: actions/checkout@v4,
pnpm/action-setup@v4, actions/setup-node@v4 and any other uses with v2/v4), so
replace each mutable tag with the corresponding pinned commit SHA from the
action's GitHub repo (e.g., actions/checkout@<commit-sha>,
pnpm/action-setup@<commit-sha>, actions/setup-node@<commit-sha>); locate the
uses lines for actions/checkout, pnpm/action-setup and actions/setup-node and
update their `@vX` tags to the specific commit SHAs for the exact release you
want, then commit and run the workflow lint/CI to verify no syntax issues.
🤖 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/ci.yml:
- Around line 47-48: The sticky comment step using
marocchino/sticky-pull-request-comment@v2 can fail on forked PRs because
GITHUB_TOKEN is read-only for pull_request events; update the step's if
condition (currently checking github.event_name == 'pull_request' && always())
to also guard that the PR head repo matches the base repo (e.g., require
github.event.pull_request.head.repo.full_name == github.repository or
github.event.pull_request.head.repo.owner.login == github.repository_owner) so
the step runs only for same-repo PRs; alternatively consider switching the
workflow trigger to pull_request_target but take care when checking out code.

---

Duplicate comments:
In @.github/workflows/ci.yml:
- Around line 64-89: The Discord notification step ("Notify Discord Alarm")
currently injects PR fields into shell variables and constructs JSON inline
(PR_TITLE, PR_USER, PR_URL) which is unsafe and brittle; change it to (1) guard
early by checking that the DISCORD_WEBHOOK_URL secret is set and exit the step
if not, (2) stop interpolating GitHub context directly into shell by passing PR
metadata via GitHub Actions env/input (or use the built-in github context in a
YAML-encoded action step), and (3) build/encode the JSON payload safely using a
JSON-encoder (e.g., jq, gh api --input, or GitHub Actions' toJson) instead of
manual string concatenation so quotes/newlines are escaped. Also keep the
existing logic that selects COLOR/TITLE based on steps.build_step.outcome but
reference that outcome safely from the job context rather than injecting into
shell unescaped.
- Around line 22-28: The workflow uses mutable tags for third-party actions
(e.g., uses: actions/checkout@v4, pnpm/action-setup@v4, actions/setup-node@v4
and any other uses with v2/v4), so replace each mutable tag with the
corresponding pinned commit SHA from the action's GitHub repo (e.g.,
actions/checkout@<commit-sha>, pnpm/action-setup@<commit-sha>,
actions/setup-node@<commit-sha>); locate the uses lines for actions/checkout,
pnpm/action-setup and actions/setup-node and update their `@vX` tags to the
specific commit SHAs for the exact release you want, then commit and run the
workflow lint/CI to verify no syntax issues.
🪄 Autofix (Beta)

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

Run ID: bcf27952-3704-4130-a49d-fa6bda02bd9c

📥 Commits

Reviewing files that changed from the base of the PR and between 96774d4 and 3ab5fcb.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/ci.yml
Comment on lines +47 to +48
if: github.event_name == 'pull_request' && always()
uses: marocchino/sticky-pull-request-comment@v2
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For GitHub Actions pull_request events from forks, is GITHUB_TOKEN write permission (pull-requests: write) downgraded to read-only, and what is the recommended guard pattern for comment-posting steps?

💡 Result:

Yes, for GitHub Actions workflows triggered by pull_request events from forks, the GITHUB_TOKEN has its write permissions (including pull-requests: write) downgraded to read-only. This is a security measure to prevent malicious forks from performing write actions on the base repository. The recommended guard pattern for comment-posting steps (which require pull-requests: write) is to use the pull_request_target event instead of pull_request. This event runs in the context of the base branch (trusted code), providing a full read/write GITHUB_TOKEN even for forks. Example workflow snippet: on: pull_request_target: types: [opened, synchronize] permissions: pull-requests: write contents: read jobs: comment: runs-on: ubuntu-latest steps: - name: Post comment run: | gh pr comment ${{ github.event.pull_request.number }} --body "Your comment here" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Security notes: - Do not checkout the PR head sha (untrusted code) with actions/checkout using ref: ${{ github.event.pull_request.head.sha }} unless strictly sandboxed. - For read-only tasks like building/testing, stick to pull_request. - Explicitly set minimal permissions with the permissions key. - Optionally, enable repo setting "Send write tokens to workflows from pull requests" for pull_request events, but pull_request_target is preferred for write actions like commenting.

Citations:


포크 PR에서 sticky comment 실패 가능성에 대한 보호 필요

정확한 지적이야. GitHub Actions의 보안 정책상 fork PR에서 pull_request 이벤트로 실행되면 GITHUB_TOKEN의 쓰기 권한이 읽기 전용으로 제한돼. 따라서 현재 상태에서는 fork PR에서 코멘트를 작성하려는 이 단계가 실패할 수 있어.

제시한 가드 패턴은 정확한 해결책이야. PR이 같은 저장소에서 나온 경우에만 단계를 실행하도록 제한하면 fork PR에서의 실패를 방지할 수 있어:

가드 패턴 적용
        if: github.event_name == 'pull_request' && always() && github.event.pull_request.head.repo.full_name == github.repository
        uses: marocchino/sticky-pull-request-comment@v2

참고로 더 근본적인 방법은 pull_request_target 이벤트로 변경하는 거야. 이 이벤트는 base 브랜치 컨텍스트에서 실행되기 때문에 fork PR에서도 전체 쓰기 권한을 갖게 돼. 다만 untrusted 코드 실행 위험이 있으니 코드 체크아웃할 때 조심해야 해.

📝 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
if: github.event_name == 'pull_request' && always()
uses: marocchino/sticky-pull-request-comment@v2
if: github.event_name == 'pull_request' && always() && github.event.pull_request.head.repo.full_name == github.repository
uses: marocchino/sticky-pull-request-comment@v2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 47 - 48, The sticky comment step using
marocchino/sticky-pull-request-comment@v2 can fail on forked PRs because
GITHUB_TOKEN is read-only for pull_request events; update the step's if
condition (currently checking github.event_name == 'pull_request' && always())
to also guard that the PR head repo matches the base repo (e.g., require
github.event.pull_request.head.repo.full_name == github.repository or
github.event.pull_request.head.repo.owner.login == github.repository_owner) so
the step runs only for same-repo PRs; alternatively consider switching the
workflow trigger to pull_request_target but take care when checking out code.

@u-zzn u-zzn added ⚙️ Config 프로젝트 설정 및 세팅 유진 ୨ৎ 명지대학교 데이터사이언스전공 정유진 labels Apr 12, 2026
@u-zzn u-zzn requested review from Sohyunnnn and odukong and removed request for jogpfls April 27, 2026 03:52
@u-zzn u-zzn changed the title Chore/ci cd setup/#276 [Chore] CI 구축 및 Vercel 배포 후 fork sync 자동화 Apr 27, 2026
Copy link
Copy Markdown
Contributor

@odukong odukong left a comment

Choose a reason for hiding this comment

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

actions에 대한 버전을 최신버전으로 가져와 사용할 수도 있겠지만, 안정성을 위해 한 단계 낮은 버전(v4)을 사용하거나 package.json에 명시된 명령어를 통해 CI 빌드 과정을 수행하면서 명령어가 수정된다하여도 CI 워크플로우 파일을 수정하지 않아도 된다는 점을 고려하여 설계해준 점이 좋다고 생각이 듭니다!

그런데 한 가지 궁극적으로 궁금한 점이 있습니다.
"개인 fork 레포를 기반으로 배포가 이루어지는 흐름으로 구성되어 배포 이후 fork 레포의 상태를 최신으로 맞춰주는 후처리 작업"을 위해 vercel-sync.yml 워크플로우를 두셨다고 한 점인데요.
vercel-sync.yml도 결국엔 개인 fork repo가 업데이트가 되어야지 실행되는 워크플로우입니다. 해당 워크플로우가 실행되기 위해서는 배포가 성공했을 때(vercel.deployment.success)를 감지하여 즉, 개인 fork repo가 업데이트되고 vercel이 배포하면 vercel-sync.yml이 실행되는 구조니까요.

그런데 여기서 문제가 되는 부분이 조직 repo의 main브랜치로 push가 되었을 때, 이 업데이트 내역이 바로 개인 fork repo에 업데이트되는 과정이 생략되었다는 것 같다는 점입니다. 말씀하신 "Vercel 배포 성공 이후 vercel-sync.yml로 개인 fork를 동기화하는 방식"에서 배포가 되기 위해서는 우선적으로 조직 repo와 개인 fork가 동기화되어야 하는데, 두 명제의 선후 관계가 바뀐 것 같다는 느낌을 받았습니다.
(+"Vercel은 PR 생성 시에도 preview 배포를 생성하기 때문에, 모든 배포 성공 이벤트에 반응하면 PR preview 배포 때마다 fork sync가 반복될 수 있다는 것", 역시 vercel은 개인 repo fork를 바라보고 있기 때문에 조직 PR 이벤트가 발생되어도 배포 성공 이벤트는 일어나지 않을 것 같다는 생각입니다)

즉, 결국엔 현재 구조만으로는 개인 fork repo에서 수동으로 fork를 하는 작업(ex. sync fork 버튼 클릭?)이 여전히 필요하기 때문에 완전한 배포 워크플로우 자동화 가 이루어지지 못하고 있다고 생각이 듭니다.

그렇기 때문에 to. reviewer에 말씀해주신 방향대로 처음부터 조직 repo의 업데이트에 따라 fork가 이루어짐으로써 배포 자동화가 수행되는 방향으로 수정하는 것이 올바른 방향이라고 생각합니다.
우선은 현재 프로젝트에서 설계된 워크플로우를 이해한 대로 리뷰를 남겨두었는데, 잘못 이해한 부분이 있다면 말씀해주시면 감사하겠습니다!

➕ to. Reviewer에 남겨주신 부분에 대해 말씀드리면, 배포 전에 빌드 에러를 확인하여 미리 고치는 과정이 필요하기 때문에 CI 단계에서도 build를 포함하는 방향이 적절하다고 생각하고, 추가적으로 DISCORD 알림은 알림을 보내는 이유가 사실상 pr이 업로드되었으니 확인해달라는 의미에서 비롯되기 때문에..!PR 기준 범위가 충분하다고 생각합니다!

@u-zzn
Copy link
Copy Markdown
Member Author

u-zzn commented Apr 29, 2026

좋은 리뷰 감사합니다 🙂

먼저 실행 순서를 조금 더 명확하게 고려했어야 했는데, 이 부분을 놓친 것 같습니다.
수빈님이 말씀해주신 것처럼 현재 제가 구성한 vercel-sync.yml 구조만으로는 완전한 배포 워크플로우 자동화가 이루어지고 있지 않은 게 맞습니다 ㅠ

제가 처음 의도했던 vercel-sync.yml의 역할은 개인 fork 레포에서 수동으로 Sync fork 버튼을 누르는 작업을 자동화하는 것이었습니다.

다만 제가 트리거를 아래처럼 Vercel 배포 성공 이후로 잡아두면서 문제가 생겼습니다.

on:
  repository_dispatch:
    types: [vercel.deployment.success]

현재 띵스룸 구조에서는 Vercel이 조직 레포가 아니라 개인 fork 레포를 바라보고 있기 때문에,
조직 레포의 main 브랜치에 변경사항이 머지되더라도 개인 fork가 먼저 최신화되지 않으면 Vercel 배포가 새로 발생하지 않습니다.

즉, 현재 제가 작성한 흐름은 아래와 같습니다.

조직 레포 main 머지
↓
개인 fork에는 아직 반영되지 않음
↓
Vercel은 개인 fork를 바라보고 있음
↓
개인 fork에 변경사항이 없으므로 Vercel 배포도 새로 발생하지 않음
↓
vercel.deployment.success 이벤트가 발생하지 않음
↓
vercel-sync.yml도 실행되지 않음

결국 말씀해주신 것처럼 fork sync가 먼저 이루어져야 Vercel 배포가 발생할 수 있는데, 현재 구조는 Vercel 배포가 성공해야 fork sync를 실행하는 구조라서 선후 관계가 뒤집혀 있었습니다.

그래서 이 부분은 아래 방향으로 수정해보고자 하는데, 어떻게 생각하시나요?

조직 레포 main 머지
↓
GitHub Actions 실행
↓
개인 fork 자동 sync
↓
개인 fork에 변경사항 반영
↓
Vercel이 개인 fork 변경사항 감지
↓
Vercel 자동 배포

즉, 기존의 vercel-sync.yml을 “배포 후 sync”가 아니라 조직 레포 main 변경 이후, Vercel 배포를 유도하기 위한 fork 선행 sync workflow로 수정하려고 합니다.

트리거도 아래처럼 변경하는 방향을 생각하고 있습니다.

on:
  push:
    branches: [main]
  workflow_dispatch:

이렇게 하면 조직 레포의 main 브랜치에 변경사항이 반영되었을 때 개인 fork가 먼저 자동으로 최신화되고, 그 결과 Vercel이 개인 fork의 변경사항을 감지하여 배포를 수행하는 흐름이 됩니다.

정리하면, 제가 처음에는 “Vercel 배포 성공 이후 fork sync”라고 설명했지만, 실제 띵스룸 구조에서는 **“조직 레포 main 반영 이후 fork sync → Vercel 배포”**가 맞는 흐름이라고 이해했습니다.

@Sohyunnnn 소현님도 수빈님 의견과 제가 정리한 방향 각각에 대해서 어떻게 생각하시는지 의견 부탁드립니다 ☺️
그리고 수빈님께서도 제가 위와 같이 이해하고 수정 방향을 잡은 부분에 대해 어떻게 생각하시는지 추가로 말씀해주시면 감사하겠습니다! 💪

@odukong
Copy link
Copy Markdown
Contributor

odukong commented Apr 30, 2026

말씀해주신 방향(main 브랜치 변경사항 감지 > sync fork 이벤트 트리거)이라면,
정상적으로 vercel 배포 파이프라인이 동작할 것 같습니다. 생각하신 방향대로 수정해주시면 될 것 같아요!

@Sohyunnnn
Copy link
Copy Markdown
Contributor

수빈님의 말씀에 동의합니다!

현재 Vercel이 개인 fork 레포를 바라보고 있는 구조라면, 배포가 발생하기 위해서는 개인 fork가 먼저 조직 레포의 main 변경사항을 반영하고 있어야 한다고 알고 있습니다.

따라서 “Vercel 배포 성공 이후 fork sync”가 아니라,

조직 레포 main 머지  
↓  
GitHub Actions 실행  
↓  
개인 fork 자동 sync  
↓  
Vercel 자동 배포

흐름으로 수정하는 방향이 이번 PR의 핵심 목적에 더 적합해 보입니다.

말씀해주신 방향대로 수정해주시면 정상적으로 배포 파이프라인이 동작할 것 같습니다!

Copy link
Copy Markdown

@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: 2

🤖 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/vercel-sync.yml:
- Around line 16-20: Remove the unnecessary PAT from the checkout step: stop
passing the FORK_SYNC_TOKEN into actions/checkout@v4 and instead rely on the
default GITHUB_TOKEN; add persist-credentials: false to the checkout step to
avoid writing creds into .git/config. Keep the FORK_SYNC_TOKEN only where the
fork-sync action requires it (the later fork-sync step), passing it there
explicitly (e.g., via with: or env:) so the PAT is scoped to that single action.
- Around line 17-22: Replace mutable action tags with their immutable full
commit SHAs: change uses: actions/checkout@v4 to the corresponding full commit
SHA for actions/checkout and change uses: tgymnich/fork-sync@v2.3.1 to the
corresponding full commit SHA for tgymnich/fork-sync; update the workflow file
to reference those full-length git commit hashes (you can obtain them from each
repo’s tags page or via git ls-remote --tags) so the workflow uses immutable
action revisions instead of movable tags.
🪄 Autofix (Beta)

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

Run ID: b0603416-12c5-4d54-9eb4-f455ea3ec7de

📥 Commits

Reviewing files that changed from the base of the PR and between 3ab5fcb and eed3f02.

📒 Files selected for processing (1)
  • .github/workflows/vercel-sync.yml

Comment on lines +16 to +20
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.FORK_SYNC_TOKEN }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/vercel-sync.yml

Repository: DdingSroom/dding-sroom-frontend

Length of output: 793


checkout 단계에서 불필요한 PAT 노출을 줄이자

현재 코드는 checkout 단계(19줄)에서 FORK_SYNC_TOKEN을 전달하고 있는데, checkout은 사실 기본 GITHUB_TOKEN만으로도 레포지토리 접근이 충분해. 이 PAT는 실제로는 아래 fork-sync 액션(24줄)에서만 필요해.

최소 권한 원칙을 적용하면, checkout은 기본 토큰으로 동작하고 PAT는 정말 필요한 fork-sync 단계에만 전달하는 게 안전해. 여기에 persist-credentials: false를 추가하면 자격증명이 .git/config에 저장되지 않아서 한층 더 방어할 수 있어.

개선 예시
       - name: Checkout repository
         uses: actions/checkout@v4
         with:
-          token: ${{ secrets.FORK_SYNC_TOKEN }}
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/vercel-sync.yml around lines 16 - 20, Remove the
unnecessary PAT from the checkout step: stop passing the FORK_SYNC_TOKEN into
actions/checkout@v4 and instead rely on the default GITHUB_TOKEN; add
persist-credentials: false to the checkout step to avoid writing creds into
.git/config. Keep the FORK_SYNC_TOKEN only where the fork-sync action requires
it (the later fork-sync step), passing it there explicitly (e.g., via with: or
env:) so the PAT is scoped to that single action.

Comment on lines +17 to +22
uses: actions/checkout@v4
with:
token: ${{ secrets.FORK_SYNC_TOKEN }}

- name: Sync upstream -> fork (u-zzn)
uses: tgymnich/fork-sync@v2.3.1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/vercel-sync.yml | head -30

Repository: DdingSroom/dding-sroom-frontend

Length of output: 793


🌐 Web query:

GitHub Actions security SHA pinning vs version tags supply chain risk

💡 Result:

SHA pinning provides significantly better supply chain security than version tags in GitHub Actions. Version tags (e.g., @v1, @v2) are mutable pointers that can be force-pushed to malicious commits if a maintainer account is compromised or tags are rewritten, as seen in real incidents like tj-actions/changed-files (CVE-2025-30066) and aquasecurity/trivy where attackers rewrote tags affecting thousands of workflows. SHA pinning (e.g., @692973e3d937129bcbf40652eb9f2f61becf3332 # v1.0.0) locks to an immutable commit hash, preventing automatic execution of malicious updates since Git commit SHAs cannot change retroactively without a computationally infeasible SHA-1 collision. GitHub officially recommends pinning to full-length commit SHAs as "the only way to use an action as an immutable release," especially for third-party actions. Administrators can now enforce SHA pinning via organization policies, blocking workflows without it. Use tools like Dependabot or Renovate to automate updates by creating PRs for new SHAs with version comments. To find the SHA for a tag: git ls-remote --tags https://github.com/owner/repo.git | grep refs/tags/vX or check the tag page on GitHub. Add comments for readability (e.g., # v4.1.1). Note limitations: SHA pinning secures the action's repo code but not its transitive dependencies (e.g., Docker images pulled by the action, which remain mutable). GitHub's immutable releases feature helps action maintainers make specific tags unchangeable, but consumers should still prefer SHAs. Example: Before (risky): - uses: actions/checkout@v4 After (secure): - uses: actions/checkout@b4ffde6 # v4.1.1

Citations:


서드파티 액션은 버전 태그 대신 커밋 SHA로 고정해야 한다.

배포 및 동기화 워크플로우에서는 공급망 보안이 특히 중요한데, 버전 태그(예: @v4, @v2.3.1)는 변경 가능한 포인터라 계정이 침해되거나 태그가 다시 지정될 경우 악의적인 커밋으로 업데이트될 수 있다. 실제로 tj-actions/changed-files(CVE-2025-30066), aquasecurity/trivy 등에서 태그 재지정으로 인한 공격이 발생했다.

반면 커밋 SHA는 변경 불가능한 Git 해시로, 자동으로 악의적인 업데이트를 실행할 수 없다. GitHub도 공식적으로 "action을 불변 릴리스로 사용하는 유일한 방법"으로 전체 길이의 커밋 SHA 고정을 권장하고 있다.

다음처럼 각 액션의 태그에 해당하는 커밋 SHA로 변경하자:

      - name: Checkout repository
-       uses: actions/checkout@v4
+       uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
        with:
          token: ${{ secrets.FORK_SYNC_TOKEN }}

      - name: Sync upstream -> fork (u-zzn)
-       uses: tgymnich/fork-sync@v2.3.1
+       uses: tgymnich/fork-sync@<fork-sync-commit-sha> # v2.3.1

SHA는 각 저장소의 태그 페이지에서 확인하거나 git ls-remote --tags 명령으로 찾을 수 있다. 나중에 Dependabot이나 Renovate 같은 도구로 자동 업데이트를 설정하면 새 SHA로 PR을 생성해주므로 관리 부담도 크지 않다.

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

In @.github/workflows/vercel-sync.yml around lines 17 - 22, Replace mutable
action tags with their immutable full commit SHAs: change uses:
actions/checkout@v4 to the corresponding full commit SHA for actions/checkout
and change uses: tgymnich/fork-sync@v2.3.1 to the corresponding full commit SHA
for tgymnich/fork-sync; update the workflow file to reference those full-length
git commit hashes (you can obtain them from each repo’s tags page or via git
ls-remote --tags) so the workflow uses immutable action revisions instead of
movable tags.

@u-zzn
Copy link
Copy Markdown
Member Author

u-zzn commented May 3, 2026

넵!! 트리거 push: main으로 변경해서 띵스룸 조직 레포 main에 변경이 반영되는 시점에 바로 fork sync가 이루어지도록 수정했습니다!

수정하는 과정에서 더 이상 사용하지 않는 Vercel payload 기반 조건문과 debug step도 함께 제거했고, fork sync에 필요하지 않은 pull-requests 권한도 제외했습니다. 이 부분도 한 번씩 확인해주시면 감사하겠습니다 :)
workflow_dispatch는 기존대로 유지해서 필요할 경우 수동으로도 실행할 수 있도록 해두었습니다!

저희 이야기 나눈 흐름대로 조직 repo main merge → GitHub Actions 실행 → 개인 fork 자동 sync → Vercel 배포 순서로 동작하도록 정리된 상태인데 확인 한 번씩 부탁드리고, 추가로 고려해야 할 부분이 있다면 편하게 말씀해주시면 감사하겠습니다 ☺️

@odukong
Copy link
Copy Markdown
Contributor

odukong commented May 3, 2026

@u-zzn 수정하신 내용 확인했습니다!

조직 repo main push → fork sync → Vercel 배포 순서로 흐름이 잘 잡힌것같고, 배포 yml 파일을 수정하면서 조건문도 제거할 수 있게 되면서 의미가 더 명확해진 것 같네요 귣~ 일단 어푸

@u-zzn u-zzn merged commit de8ae6c into develop May 3, 2026
2 checks passed
@u-zzn u-zzn deleted the chore/ci-cd-setup/#276 branch May 3, 2026 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚙️ Config 프로젝트 설정 및 세팅 유진 ୨ৎ 명지대학교 데이터사이언스전공 정유진

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants