Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: ci

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

permissions:
contents: read
pull-requests: write

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

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
Comment on lines +21 to +25
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.


- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Check project
id: check_step
run: pnpm run lint && pnpm run typecheck
continue-on-error: true

- name: Build project
id: build_step
run: pnpm run build
continue-on-error: true

- name: Output build result
if: github.event_name == 'pull_request' && always()
uses: marocchino/sticky-pull-request-comment@v2
Comment on lines +47 to +48
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.

with:
header: build-result
message: |
### 🚀 빌드 결과
${{ steps.check_step.outcome == 'success' && '🩵 린트 검사 완료' || '😥 린트 검사 실패' }}
${{ steps.build_step.outcome == 'success' && '💙 빌드 성공' || '😭 빌드 실패' }}

<details>
<summary>✔️ 로그 확인하기</summary>
<br>
<a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">
Actions 탭에서 자세히 보기!
</a>
</details>

- 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 }}"
Comment on lines +64 to +88
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.


- name: Check Build Status
if: steps.check_step.outcome == 'failure' || steps.build_step.outcome == 'failure'
run: exit 1
27 changes: 27 additions & 0 deletions .github/workflows/vercel-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: sync-fork-for-vercel-deploy

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: write

jobs:
sync-fork:
runs-on: ubuntu-latest

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

Comment on lines +16 to +20
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.

- name: Sync upstream -> fork (u-zzn)
uses: tgymnich/fork-sync@v2.3.1
Comment on lines +17 to +22
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.

with:
github_token: ${{ secrets.FORK_SYNC_TOKEN }}
owner: u-zzn
base: main
head: main
Loading