Repository navigation
security(ci): Actions SHA pinning 정책을 조직 수준으로 넓힌다 — BE 포함 전 저장소 감사 선행 #1591
Description
작업 유형
security — 보안 취약점·권한·자격·공급망 대응
주 담당 모듈
platform — Android 앱·CI·빌드·릴리스·저장소 운영
개요
#1538 에서 Afternote-FE 저장소 한 곳의 Actions 정책을 좁혔다. 조직 수준은 손대지 않았고, 여전히 열려 있다.
실측 (2026-08-30)
// GET /orgs/Afternote/actions/permissions
{"enabled_repositories":"all","allowed_actions":"all","sha_pinning_required":false}
// GET /repos/Afternote/Afternote-FE/actions/permissions ← #1538 에서 좁힘
{"enabled":true,"allowed_actions":"selected","sha_pinning_required":true}조직이 all 이라 Afternote-FE 를 뺀 나머지 저장소(Afternote-BE 포함)는 지금도 uses: someone/action@main 이 그대로 통과한다. FE 한 곳만 막아 둔 상태라 조직 전체의 공급망 경계는 아직 없는 셈이다.
조직 쪽을 그냥 켜면 안 되는 이유가 이 이슈의 본체다 — 조직 설정은 하위 저장소 전부에 즉시 적용되고, BE 워크플로의 pinning 상태를 감사하지 않은 채 켜면 그쪽 CI 가 startup_failure 로 죽는다. job 이 만들어지지 않아 로그에 사유도 남지 않는다.
완료 조건
- 조직 하위 전 저장소의 워크플로
uses를 전수 스캔해 floating tag·branch 참조를 열거한다 (Afternote-BE 포함, 열린 PR 브랜치까지) - floating 참조가 남아 있으면 해당 저장소에서 SHA 고정을 먼저 끝낸다
- 조직 전체가 호환되는 것을 확인한 뒤
orgs/Afternote/actions/permissions의sha_pinning_required를 켠다 - 조직
allowed_actions를 좁힐지 판단한다 — 저장소마다 쓰는 액션이 달라 조직 목록은 합집합이 된다. 좁히는 값이 실제로 있는지, 아니면 저장소별 목록으로 두는 게 맞는지 근거와 함께 결정한다 - 켠 뒤 차단이 실제로 도는 것을 안전한 fixture 로 확인한다 (chore(ci): Actions SHA pinning required·허용 정책을 저장소 설정에 적용한다 #1538 에서 쓴 절차: 정책 적용 전 대조군 run → 적용 후 같은 내용 재실행 → 결과 뒤집힘 확인 → fixture 브랜치 삭제)
참고
#1538 에서 이미 끝난 것(이 이슈 범위 아님) — Afternote-FE 저장소의 sha_pinning_required 활성화, allowed_actions: selected 적용(github_owned + verified + 명시 패턴 5종), 허용 목록을 supply-chain-policy.test.mjs 에 미러링(#1590), fixture 로 차단·허용 양쪽 실측.
#1538 에서 얻은 실측 사실 두 가지가 조직 쪽 판단에도 그대로 쓰인다.
github_owned_allowed는actions조직뿐 아니라github조직도 덮는다 —github/codeql-action은 patterns 없이 통과한다.patterns_allowed의*는/를 넘지 않는다.gradle/actions/*로 서브패스 액션을 한 번에 덮을 수 없고 액션마다 한 줄씩 필요하다.
2026-09-13 착수 결과
3개 저장소의 브랜치 160개와 열린 PR head 34개를 조회해 workflow/action 고유 blob 195개를 파싱했다. 조회·파싱 오류는 0건이다. 감사 시각은 03:12 UTC이며 모든 결과는 ref별 commit SHA에 고정했다.
- 기본 브랜치: BE 미고정 32건, demo 2건. 열린 PR head의 미고정은 0건이다.
- 과거 브랜치까지 포함한 미고정은 1014건이며 동일 코드의 브랜치 중복을 포함한다. FE 13개·BE 75개·demo 2개 브랜치에 남아 있다.
- 조직 정책은 sha_pinning_required=false / allowed_actions=all 유지. BE/demo 및 재사용할 기존 브랜치 고정과 담당 저장소 검증 전 활성화하지 않는다.
조직 허용 목록 판단
조직 allowed_actions를 selected로 좁힐 실익이 있다. 기본 브랜치와 열린 PR에서 사용하는 원격 액션은 20종이며 GitHub 소유를 제외하면 아래 10종이다. GitHub 소유 액션을 허용하고 verified 공급자 전체 허용을 끄면, 목록에 없는 새 공급자의 액션까지 자동 허용되는 범위를 줄인다. 오래된 브랜치까지는 원격 24종이므로 유지할 브랜치를 정한 뒤 실제 목록을 확정한다.
후보: github_owned_allowed=true, verified_allowed=false. patterns_allowed:
anishathalye/proof-html@*appleboy/scp-action@*appleboy/ssh-action@*docker/build-push-action@*docker/login-action@*docker/setup-buildx-action@*google-github-actions/auth@*gradle/actions/dependency-submission@*gradle/actions/setup-gradle@*pozil/auto-assign-issue@*
API에 이 값을 적용하지 않았다. BE 기본 브랜치 32건·demo 2건 SHA 고정과 검증, 유지할 옛 브랜치 정리, 외부 composite action의 추가 의존 확인이 선행이다. BE CI·배포 담당자가 BE 수정과 검증을 맡고, 1hyok의 조직 운영 범위에서는 demo 담당·브랜치 유지 범위를 확정하고 정책 전후 fixture를 준비한다. 저장소별 더 좁은 목록도 유지할 수 있다.
GitHub 공식 문서상 SHA 강제는 액션에 적용되며 reusable workflow는 tag 참조가 허용된다. 정책 fixture에서 두 대상을 구분한다. 정책 문서, API 정의.
Activity
OnlyReadOneBook commentedon Aug 30, 2026
감사 완료 — 조직 설정은 아직 켜지 않았다
전수 감사를 끝냈습니다. 결과와 판정은 #1607 에서
docs/security/actions-supply-chain.md로 들어갑니다.orgs/Afternote/actions/permissions는 조회(GET)만 했고 그대로입니다 —{"enabled_repositories":"all","allowed_actions":"all","sha_pinning_required":false}.1. 전수 스캔 (2026-08-30T13:25:28Z)
조직 저장소는 3개가 전부입니다(아카이브·포크 0건,
public_repos: 2+total_private_repos: 1과 일치). 기본 브랜치 3개 + Afternote-FE 열린 PR 35건의 head, 38개 리비전 ·uses4,699건을 훑었습니다. 워크플로뿐 아니라 composite action 매니페스트(action.yml)까지 포함합니다.uses./)Afternote-FE(developfc8f32cd)Afternote-BE(main0a4b870c)demo-repository(main57ea595f, private)floating 34건 전량
Afternote-BEactions/checkout@v7Afternote-BEactions/dependency-review-action@v5Afternote-BEactions/setup-java@v5Afternote-BEactions/setup-python@v5Afternote-BEactions/upload-artifact@v7Afternote-BEappleboy/scp-action@v1.0.0Afternote-BEappleboy/ssh-action@v1.2.5Afternote-BEdocker/login-action@v4Afternote-BEgithub/codeql-action/analyze@v4Afternote-BEgithub/codeql-action/init@v4Afternote-BEgradle/actions/dependency-submission@v6Afternote-BEgradle/actions/setup-gradle@v6demo-repositoryanishathalye/proof-html@v1.1.0demo-repositorypozil/auto-assign-issue@v1startup_failure로 죽습니다. 이슈 본문의 우려가 실측으로 확인됐습니다.actions/dependency-review-action@v5는 태그가 아니라 브랜치입니다 —refs/tags/v5는 없고refs/heads/v5만 있습니다(현재a1d282b3…, 태그v5.0.0과 같은 커밋). 업스트림이 force-push 하면 그 시점부터 다른 코드가 BE CI 에서 돕니다.demo-repository는 2026-08-27 에 만들어진 GitHub 데모 저장소로 run 이 1건뿐입니다. 워크플로를 고치는 것보다 Actions 를 끄는 편이 쌉니다.감사는
.github/scripts/audit-org-action-pinning.mjs로 재현합니다. floating 이 남아 있으면 exit 1 이라 정책을 켜기 직전 게이트로 그대로 씁니다.2.
allowed_actions판정 — 조직 목록은 만들지 않습니다완료 조건의 «좁힐지 판단한다» 에 대한 답입니다. 조직은
all로 두고, 허용 목록은 저장소별로 좁힙니다. 근거 셋:(1) 조직 목록은 구조적으로 가장 느슨한 목록이 됩니다. 저장소별 외부 액션 집합이 FE 14종 / BE 12종 / demo 2종이라 합집합이 20종입니다.
github_owned_allowed가 덮는actions/*·github/*를 빼도 조직 patterns 는 10줄이 되는데, FE 가 #1538 에서 정한 5줄에 FE 가 쓰지도 않는appleboy/scp-action·appleboy/ssh-action·pozil/auto-assign-issue·anishathalye/proof-html·docker/login-action이 얹힙니다.(2) 조직 목록이 저장소 목록을 덮어쓰는지가 문서에 없습니다. REST 스펙(
api.github.com.json, 2026-08-30 시점)의 조직·저장소selected-actions엔드포인트 설명 어디에도 상하위 관계가 없고, 저장소 설정 문서는 "You might not be able to manage these settings if your organization has an overriding policy" 라고만 합니다. 대체하는 쪽이 맞다면 (1)의 10줄이 곧 FE 의 새 경계가 되고 #1538 이 좁혀 둔 것이 도로 넓어집니다. 실측으로 가리려면 조직 정책을 실제로 켜야 해서, 확인되지 않은 채로는 손대지 않았습니다.(3)
patterns_allowed는 공개 저장소에만 적용됩니다. REST 스펙의selected-actions스키마 주석입니다 — "Thepatterns_allowedsetting only applies to public repositories."demo-repository는 private 이라 조직 목록을 만들어도 그 저장소는 patterns 로 안 막힙니다.→ 조직 수준에서 켤 값은 boolean 인
sha_pinning_required하나입니다. 합집합 문제가 없고 어떤 저장소도 느슨해지지 않습니다.3. 켜기까지 남은 선행 조건
Afternote-BE의 floating 32건 → 0. 이 저장소에서 고칠 수 없는 몫이라 PR 을 열지 않았습니다. 문서에 워크플로별 참조 표와 감사 시점 해석된 커밋 SHA 를 남겼습니다.demo-repository의 2건 처리. SHA 고정 / Actions 끄기(gh api --method PUT repos/Afternote/demo-repository/actions/permissions -F enabled=false) / 저장소 삭제 중 택일.4. 켜는 명령과 fixture 검증 (승인 대기)
fixture 검증은 #1538 과 절차가 하나 달라집니다. Afternote-FE 는 이미 저장소 수준에서 pinning 이 켜져 있어, 그대로는 조직 정책과 저장소 정책을 구별할 수 없습니다. 대조군을 잡으려면 FE 의 저장소 수준 pinning 을 잠시 끄고(그동안 FE 에 floating 이 머지될 수 있는 창이 열립니다) floating fixture 가 통과하는 것을 본 뒤, 조직 정책을 켜고 같은 fixture 로 결과가 뒤집히는지 확인하고, FE 저장소 설정을 되돌립니다. 전 절차와 되돌리기 명령은 문서에 있습니다.
또 하나 — 조직 설정 화면 문서에 따르면 재사용 워크플로는 태그 참조가 계속 허용됩니다("Reusable workflows can still be referenced by tag"). FE 의 재사용 워크플로 참조는
develop기준 6건(고유 파일 4개) 전부 로컬(./.github/workflows/*.yml)이라 어느 쪽이든 영향이 없습니다.docs(platform): 재사용 워크플로 참조 건수를 실측값으로 바로잡는다 (#1591)
docs(platform): Actions SHA 고정 정책의 조직 수준 감사를 남긴다 (#1591) (#1607)
OnlyReadOneBook commentedon Sep 23, 2026
2026-09-23 BE 고정 진행
BE 기본 브랜치의 floating 32건을 담당별로 나눴다. 담당은 BE 역할 지도(CI/CD 는 1hyok)와 파일별 커밋 이력으로 정했다.
ci.yml·codeql.yml·dependency-submission.yml·quality.ymldeploy.yml·tls-expiry.yml두 이슈를 이 이슈의 blocked_by 로 걸었다. Afternote-BE#299 의 SHA 는 오늘 태그를 다시 해석한 값이다.
docs/security/actions-supply-chain.md의 0830 값과 비교하면 이 PR 이 쓰는 액션 중github/codeql-actionv4 하나만 다르다(0830 에는 v4.37.9cdf488f5, 지금은 v4.38.11c5b6756). 고친 뒤 4개 파일은 감사 스크립트의 분류 함수 기준 floating 0건이다.남은 것:
sha_pinning_required켜기는 BE 고정 두 건이 모두 머지된 뒤에 한다. 조직·저장소 설정은 이번에 바꾸지 않았다.dependabot.yml의github-actions가open-pull-requests-limit: 0이라 고정한 SHA 가 자동으로 올라오지 않는다. Afternote-BE#299 본문에 남은 위험으로 적었고 바꾸지는 않았다.