ถ้าคุณเคยเจอกรณีที่ uses: some/action@v4 โดยไม่ได้ pin SHA คุณควรรู้ว่ามันเป็นช่องโหว่ที่เปิดทิ้งไว้เลย เพราะ tag ใน Git เป็น mutable — ใครมีสิทธิ์ push เข้า repo ก็ย้ายปลายทางของ tag นั้นได้ทันที แล้วทุก workflow ที่อ้าง tag นี้จะรันโค้ดของคนอื่นโดยไม่รู้ตัว
GitHub เพิ่งเปิดตัว workflow lockfiles แบบ GA (ประกาศ 28 ต.ค.) ซึ่งเปลี่ยนวิธีแก้ปัญหานี้ไปพอสมควร คือไม่ต้องมานั่ง hash SHA เองแล้ว แต่ให้เครื่องมือจัดการ

ก่อนหน้านี้คำแนะนำมาตรฐานคือ pin ไปที่ full commit SHA แทน tag เช่น:
yamlsteps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: j178/prek-action@44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d # v3.1.0
วิธีนี้ป้องกัน supply-chain attack ได้ เพราะ SHA เปลี่ยนไม่ได้ แต่มีต้นทุนที่แสดงออกมาชัดเจน:
- คนอ่าน workflow ไม่รู้ว่าใช้เวอร์ชันไหน ต้องไปหา SHA ใน GitHub เอง
- อัปเดต dependency กลายเป็นงานยาก ต้องมานั่งหา SHA ใหม่เองทุกครั้ง
- มันไม่ scale โปรเจกต์ที่มี workflow เยอะ ๆ จะดูแลไม่ไหว
Lockfile มาแก้ตรงนี้
ติดตั้ง gh-actions-lock extension ให้ gh CLI ก่อน:
console$ gh extension install github/gh-actions-lock
✓ Installed extension github/gh-actions-lock
แล้วในโปรเจกต์รัน:
console$ gh actions-lock
✓ All 1 workflows valid
เท่านั้นเอง ไฟล์ workflow จะเปลี่ยนจาก SHA เป็น tag:
diffdiff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/[email protected]
- - uses: j178/prek-action@44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d # v3.1.0
+ - uses: j178/[email protected]
แล้วจะได้ไฟล์ .github/workflows/actions.lock ที่เก็บ mapping ระหว่าง tag กับ SHA จริง:
yaml# This file is machine-generated by `gh actions-lock`.
# Do not edit by hand; run `gh actions-lock` to update.
# Docs: https://gh.io/actions-lockfile
version: 'v0.0.3'
workflows:
'.github/workflows/lint.yml':
- 'actions/[email protected]'
- 'j178/[email protected]'
dependencies:
'actions/[email protected]':
ref: 'v7.0.1'
commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1'
owner_id: 44036562
repo_id: 197814629
'j178/[email protected]':
ref: 'v3.1.0'
commit: 'sha1-44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d'
owner_id: 10510431
repo_id: 1052387486
ตอนนี้ทั้งสองอย่างอยู่ด้วยกัน: คนอ่านเห็น tag ที่เข้าใจง่าย และ ระบบมี SHA ที่ตรวจสอบได้
ไม่พัง — Renovate และ Dependabot รองรับ lockfile อยู่แล้ว การอัปเดตแบบ periodic จะอัปเดต lockfile ไปด้วย
แต่ Hugo เตือนเรื่องสำคัญในโพสต์: ใช้ cooldown ด้วย เพื่อไม่ให้การอัปเดตถี่เกินไปจนรับ patch ที่ยังไม่ผ่านการทดสอบ
GitHub แนะนำให้ตรวจสอบ lockfile ใน CI ด้วย เพื่อกันกรณี lockfile หลุดจาก workflow หรือถูกแก้
วิธีที่ Hugo ใช้เองคือแยกเป็น job ต่างหากใน workflow เดิม ให้รันขนานกันได้ และใช้ runner เบากว่า:
yaml# Fails when .github/workflows/actions.lock is out of sync with the
# workflows or a locked pin no longer verifies upstream.
lockfile:
runs-on: ubuntu-slim
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/[email protected]
with:
persist-credentials: false
- run: gh extension install github/gh-actions-lock --pin v0.4.1
- run: gh actions-lock --verify
ข้อควรระวัง: ถ้าเพิ่มไฟล์ workflow ใหม่ หรือแก้ workflow เดิม ต้องรัน gh actions-lock ใหม่ ไม่งั้น CI จะ fail ว่า lockfile ไม่ตรงกับ workflow
Hugo แนะนำ zizmor เป็น security linter สำหรับ GitHub Actions และบอกตรง ๆ ว่า "ถ้ายังไม่ใช้ แนะนำให้ใช้เลย"
จุดที่สำคัญคือ zizmor v1.32.0 เป็นเวอร์ชันแรกที่รองรับ lockfiles เพราะก่อนหน้านั้นมันจะบอกให้ hash-pin action ซึ่งขัดกับแนวทาง lockfile ใหม่
ใช้กับ pre-commit ได้:
yaml- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.32.0
hooks:
- id: zizmor
Hugo ยกตัวอย่างโปรเจกต์ Python หลายตัวที่ใช้แนวทางนี้แล้ว:
python-pillow/Pillow#10428
python/release-tools#477
python/miss-islington#758
python/cpython#161811
cpython เองก็ใช้ ซึ่งบอกได้ว่าแนวทางนี้ไม่ได้เป็นแค่เทคนิคสำหรับโปรเจกต์เล็ก
ลำดับที่ง่ายที่สุด:
bashgh extension install github/gh-actions-lock
gh actions-lock
git add .github/workflows/actions.lock .github/workflows/
git commit -m "chore: lock GitHub Actions"
แล้วอย่าลืม:
- เพิ่ม job
lockfile ที่รัน gh actions-lock --verify ใน CI
- เพิ่ม zizmor เข้า pre-commit hook (เวอร์ชัน v1.32.0 ขึ้นไป)
- ตั้ง cooldown ให้ Renovate/Dependabot อัปเดตแบบมีจังหวะ
ข้อดีคือทำครั้งเดียวแล้วทีมทั้งทีมได้ประโยชน์ เพราะ lockfile อยู่ใน repo ทุกคน pull มาได้เลย และคนที่เพิ่งเข้ามาทีมก็ได้ความปลอดภัยนี้ฟรี ๆ