실습 완료: GitHub Actions에 코드 품질·보안 검사 6종을 연결하고,
summary를 Required Status Check로 설정해 실패한 PR은 Merge 차단 → 문제 수정 → 전체 통과 후 Merge까지 확인했다1. 실습 목표
이번 실습의 핵심은 GitHub Actions를 단순 실행하는 것이 아니라, CI 검출 결과를 실제 Merge 정책과 연결하는 SecOps 흐름을 경험하는 것이었다.
Fork + Clone ↓ GitHub Actions Workflow 구성 ↓ PR 생성 → 취약점/오류 검출 ↓ Ruleset + Required Check 설정 ↓ 실패한 PR Merge 차단 ↓ Repair Loop ↓ CI 전체 통과 → Merge
2. Fork와 Pull Request
Fork
Fork는 다른 사용자의 GitHub 저장소를 내 계정으로 복사해 독립적으로 수정·커밋·실습할 수 있게 하는 기능이다.
이번 실습에서는 원본
kintoun-secops/secops-actions-lab을 개인 계정으로 Fork한 뒤 개인 저장소에서 작업했다.- 개인 Fork: acr0209-eng/secops-actions-lab
- 수리 PR: Pull Request #2
Pull Request
Pull Request(PR)는 별도 브랜치의 변경사항을
main에 병합하기 전에 변경 내용, 리뷰, 자동 CI 결과를 확인하는 절차다.Fork 실습에서는 PR 생성 전 상단 Repository와 base/compare 브랜치를 확인해야 한다. 원본 저장소가 아니라 개인 Fork의
main을 대상으로 PR을 생성하는 것이 핵심이다.캡처 위치 1: 개인 Fork에서
base: main / compare: feat/ci-pipeline로 PR을 생성한 화면3. GitHub Actions CI 구성
이번 실습에서는 6개의 검사 도구를 별도 재사용 Workflow로 구성하고,
07-ci-summary.yml에서 하나로 묶었다.도구 | 역할 | Workflow |
Ruff | 포맷·린트·Python 보안 규칙 | 01-ruff.yml |
mypy | 정적 타입 검사 | 02-mypy.yml |
pytest | 단위 테스트·커버리지 | 03-pytest.yml |
pip-audit | Python 의존성 CVE 검사 | 04-pip-audit.yml |
Semgrep | SAST·Taint 분석 | 05-semgrep.yml |
gitleaks | Git History의 Secret 탐지 | 06-gitleaks.yml |
summary | 6개 검사 결과 집계·Merge Gate | 07-ci-summary.yml |
PR ↓ 07 - CI summary ├─ Ruff ├─ mypy ├─ pytest ├─ pip-audit ├─ Semgrep └─ gitleaks ↓ summary
01~06은 workflow_call 기반 재사용 Workflow로 두고, 07이 이들을 호출한다. 이렇게 구성하면 마지막 summary가 모든 검사 결과를 한 번에 집계할 수 있다.4. 1단계 · 검출 — 빨간 CI 확인
Workflow를 조립한 뒤 첫 PR을 생성하자 의도적으로 포함된 오류와 취약점 때문에 여러 Job이 실패했다.
초기 로컬 검사 결과는 다음과 같았다.
검사 | 초기 결과 |
Ruff | 13개 오류 |
mypy | 2개 타입 오류 |
pytest | 1개 테스트 실패 |
pip-audit | 5개 패키지, 18개 알려진 취약점 |
Semgrep | SQL Taint 탐지 |
gitleaks | 가짜 PAT·RSA Key 2건 탐지 |
이 단계에서 빨간 CI는 실습 실패가 아니라 검출이 정상 작동했다는 증거다.
캡처 위치 2: 최초 PR에서 여러 검사와
summary가 빨간색으로 실패한 화면5. 2단계 · Gate — 탐지와 차단의 차이
처음에는 CI가 실패해도
Merge pull request 버튼이 활성화되어 있었다. 즉, 자동 검사가 문제를 찾더라도 Repository 정책이 없으면 결과를 무시하고 Merge할 수 있다.CI만 있음 취약점 탐지 ❌ → 그래도 Merge 가능 CI + Ruleset 취약점 탐지 ❌ → Merge 차단
캡처 위치 3: 빨간 CI인데도 Merge 버튼이 활성화된 첫 PR 화면
GitHub의
Settings → Rules → Rulesets에서 main Branch를 대상으로 Ruleset을 만들고 Required Status Check로 summary 하나만 등록했다.이후 수리 PR에서 일부 Job이 실패하자
summary 옆에 Required가 표시되고 Merge 버튼이 비활성화되는 것을 확인했다.캡처 위치 4:
summary가 Required로 표시되고 Merge 버튼이 비활성화된 화면6. 3단계 · Repair Loop
fix/repair-loop 브랜치 하나를 유지하면서 문제를 도구별로 수정하고 Push할 때마다 CI를 다시 실행했다.pytest → mypy → Ruff lint / security → Semgrep → gitleaks → pip-audit
주요 수정 내용
도구 | 문제 | 수정 |
pytest | 수량 10개 경계값에서 할인이 적용되지 않음 | quantity > 10 → quantity >= 10 |
mypy | 반환 타입·인자 타입 불일치 | 반환형을 실제 계산 결과에 맞추고 문자열 인자를 정수로 수정 |
Ruff | Lint 오류와 MD5, shell=True, SQL 문자열 조립, Pickle 역직렬화 | 코드 정리, SHA-256, 명령 Allowlist, 파라미터 바인딩, JSON 사용 |
Semgrep | 사용자 입력이 SQL 실행 지점까지 흐르는 Taint | SQL Parameter Binding 적용 및 커스텀 Sink 규칙을 의도에 맞게 조정 |
gitleaks | 실습용 가짜 PAT·Private Key 및 PR Token 설정 | 가짜 Secret fingerprint를 .gitleaksignore에 등록하고 GITHUB_TOKEN 전달 |
pip-audit | Flask/Jinja2/Requests/Pytest/Werkzeug 취약 버전 | 안전한 버전으로 Pin 업그레이드하고 취약한 Werkzeug Constraint 제거 |
.gitleaksignore는 이번처럼 의도적으로 넣은 가짜 Secret에 사용했다. 실제 Credential이 노출됐다면 Ignore가 아니라 폐기·회전(Rotation)이 우선이다.pip-audit에서 확인한 전이 의존성 위험
werkzeug는 직접 작성한 dependency가 아니지만 Flask가 끌어오는 전이 의존성이었다. uv.lock에서 의존성을 펼쳐 감사하자 이 패키지의 CVE도 함께 탐지됐다.즉, 직접 설치한 패키지만 보는 것이 아니라 Lock File 전체를 검사해야 공급망 위험을 확인할 수 있다.
캡처 위치 5: Repair Loop 중
mypy, pytest, gitleaks 등이 하나씩 초록으로 바뀐 화면7. 최종 결과
최종적으로 모든 개별 검사와
summary를 통과시킨 뒤 PR을 Merge했다.- 수리 브랜치:
fix/repair-loop
- 최종 PR: #2
- 수리 PR 커밋: 10개
- 변경 파일: 15개
- 최종 상태: Merged
Ruff ✅ mypy ✅ pytest ✅ pip-audit ✅ Semgrep ✅ gitleaks ✅ ↓ summary ✅ ↓ Merge 허용 ✅
캡처 위치 6: 전체 CI 통과 후
summary가 초록색이고 Merge가 가능해진 화면 또는 최종 Merged 화면8. 실습에서 이해한 핵심
GitHub Actions + Security Tools + Branch Ruleset = 검증되지 않은 코드가
main에 들어가기 전에 자동으로 차단하는 SecOps Gate- 탐지와 차단은 다르다. CI가 취약점을 찾더라도 Branch Rule이 없으면 Merge를 강제 차단하지 못한다.
summary하나를 Gate로 두면 운영이 단순해진다. 검사 도구가 늘어나더라도 Ruleset은 그대로 유지할 수 있다.
- Shift Left를 직접 경험했다. 보안 검사가 배포 이후가 아니라 PR 단계에서 자동 실행된다.
- 정적 분석 도구마다 보는 범위가 다르다. Ruff, Semgrep, gitleaks, pip-audit은 서로 다른 계층의 문제를 탐지했다.
- 전이 의존성도 보안 대상이다. Lock File을 검사해야 내가 직접 선언하지 않은 패키지의 CVE까지 확인할 수 있다.
9. 최종 회고
처음에는 GitHub Actions YAML, 재사용 Workflow,
needs, if, Ruleset, 여러 보안 도구가 한꺼번에 등장해 복잡하게 느껴졌다. 하지만 전체 구조를 따라가 보니 핵심은 비교적 명확했다.코드를 올리면 자동으로 검사하고, 실패하면 Merge를 막고, 문제를 수정하면 다시 검사한다.
이번 실습을 통해 CI를 단순 자동화 도구가 아니라 보안 정책을 실제 개발 흐름에 강제하는 수단으로 이해할 수 있었다.
