From 09134fd1c8226fb36ad902ff2f1de969ba041a11 Mon Sep 17 00:00:00 2001 From: monthop-gmail Date: Thu, 27 Aug 2026 01:09:11 +0700 Subject: [PATCH] =?UTF-8?q?fix(audit):=20=E0=B9=80=E0=B8=A5=E0=B8=B4?= =?UTF-8?q?=E0=B8=81=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=E0=B8=A7=E0=B9=88=E0=B8=B2=20private=20repo=20=E0=B8=A1?= =?UTF-8?q?=E0=B8=B5=20branch=20protection=20=E0=B8=97=E0=B8=B1=E0=B9=89?= =?UTF-8?q?=E0=B8=87=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=95=E0=B8=B1=E0=B9=89?= =?UTF-8?q?=E0=B8=87=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B9=84=E0=B8=94=E0=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit บน private repo ของแผนฟรี GitHub ตอบ 403 พร้อม body เป็น JSON object "Upgrade to GitHub Pro or make this repository public to enable this feature." — script เดิมเอา body นั้นไปนับว่าเป็นรายการกฎ ทำให้ขึ้น "yes" ในคอลัมน์ branch protection ทั้งที่ไม่มีอะไรกันเลยสักอย่าง พลาดไปในทิศที่อันตรายที่สุด: ตารางบอกว่าปลอดภัย คนเลยไม่ไปตรวจซ้ำ เจอตอน audit org จริง — 5 จาก 8 repo รายงานผิด - แยกสถานะที่สามออกมาเป็น n/a (แผนไม่รองรับ) ต่างจาก no (ยังไม่ได้ตั้ง) เพราะสองอย่างนี้แก้คนละวิธี และ n/a ไม่ควรถูกนับว่า "ขาด" - อย่าต่อ pipe ตรงจาก gh เมื่อ gh อาจ exit non-zero — pipefail จะทำให้ pipeline ล้มก่อน grep ได้ทำงาน (พลาดตรงนี้ตอนแก้รอบแรก) - ตรวจ tree ว่ามี key "tree" จริง ไม่ใช่ error object ที่บังเอิญ parse ได้ - docs/04: เพิ่มตารางฟีเจอร์ที่ใช้ได้/ไม่ได้ตามแผนและ visibility Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Dru5ECsrJrqhTfUhUvDsqw --- docs/04-ci-branch-protection.md | 28 ++++++++++++++++++++++++++++ scripts/audit-repos.sh | 30 +++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docs/04-ci-branch-protection.md b/docs/04-ci-branch-protection.md index 712ae11..00c89f1 100644 --- a/docs/04-ci-branch-protection.md +++ b/docs/04-ci-branch-protection.md @@ -153,6 +153,34 @@ git switch main && echo x >> README.md && git commit -am 'test' && git push git reset --hard origin/main ``` +### ข้อจำกัดตามแผนของ GitHub + +**branch protection / rulesets ใช้กับ private repo ไม่ได้บนแผนฟรี** ต้องเป็น GitHub Pro/Team/Enterprise +บน public repo ใช้ได้ทุกแผน + +เช็คว่า repo ไหนติดข้อจำกัดนี้: + +```bash +gh api repos/OWNER/REPO/rules/branches/main +# → 403 "Upgrade to GitHub Pro or make this repository public to enable this feature." +``` + +`audit-repos.sh` จะแสดงเป็น `n/a` ไม่ใช่ `no` — เพราะสองอย่างนี้ต้องแก้คนละวิธี +`no` = ไปตั้งซะ · `n/a` = ต้องอัปเกรดแผน หรือเปลี่ยน repo เป็น public + +ตารางฟีเจอร์ความปลอดภัยบนแผนฟรี: + +| | public | private | +| --- | --- | --- | +| branch protection / rulesets | ใช้ได้ | **ใช้ไม่ได้** | +| secret scanning + push protection | ใช้ได้ | ใช้ไม่ได้ (ต้องมี Advanced Security) | +| code scanning (CodeQL) | ใช้ได้ | ใช้ไม่ได้ (ต้องมี Advanced Security) | +| Dependabot alerts + security updates | ใช้ได้ | **ใช้ได้** | +| Actions | ไม่จำกัด | กินโควตาของแผน | + +ถ้า repo เป็น private บนแผนฟรี สิ่งที่ทำได้คือเอา `gitleaks` กับ `pip-audit`/`npm audit` +ไปเป็น job ใน CI แทน — ได้ผลใกล้เคียงแต่ต้องดูแลเอง + ### ระวังตอนเริ่มใช้ - ถ้ามี bot/CI ที่ push ตรงเข้า main อยู่ ต้องใส่ bypass ให้มันก่อน ไม่งั้น pipeline พัง diff --git a/scripts/audit-repos.sh b/scripts/audit-repos.sh index a253c30..0d38380 100755 --- a/scripts/audit-repos.sh +++ b/scripts/audit-repos.sh @@ -59,7 +59,13 @@ path_exists() { gh api "repos/$1/contents/$2" --silent >/dev/null 2>&1 } -mark() { [ "$1" -eq 1 ] && printf 'yes' || printf '**no**'; } +mark() { + case "$1" in + 1) printf 'yes' ;; + na) printf '_n/a_' ;; # ฟีเจอร์ใช้ไม่ได้บนแผนนี้ ไม่ใช่ "ยังไม่ได้ตั้ง" + *) printf '**no**' ;; + esac +} while IFS=$'\t' read -r name branch; do [ -n "$name" ] || continue @@ -76,7 +82,7 @@ while IFS=$'\t' read -r name branch; do tree=$(gh api "repos/$nwo/git/trees/$branch?recursive=1" 2>/dev/null || true) - if [ -n "$tree" ] && [ "$(printf '%s' "$tree" | jq -r '.truncated // false')" = "false" ]; then + if [ -n "$tree" ] && printf '%s' "$tree" | jq -e 'has("tree") and (.truncated // false | not)' >/dev/null 2>&1; then paths=$(printf '%s' "$tree" | jq -r '.tree[].path') printf '%s\n' "$paths" | grep -qx 'CLAUDE.md' && has_claude=1 printf '%s\n' "$paths" | grep -qxE '(\.github/|docs/)?CODEOWNERS' && has_owners=1 @@ -90,10 +96,21 @@ while IFS=$'\t' read -r name branch; do [ -n "$(gh api "repos/$nwo/contents/.github/workflows" --jq 'length' 2>/dev/null || true)" ] && has_ci=1 fi - rule_types=$(gh api "repos/$nwo/rules/branches/$branch" --jq '[.[].type] | unique | join(",")' 2>/dev/null || true) - [ -n "$rule_types" ] && [ "$rule_types" != "" ] && has_rules=1 + # ระวัง: บน private repo ของแผนฟรี API ตอบ 403 พร้อม body เป็น JSON object + # ถ้าเอา body มานับตรง ๆ จะกลายเป็น "มี protection" ทั้งที่ตั้งไม่ได้เลย + # เก็บทั้ง stdout และ stderr ไว้ก่อน อย่าต่อ pipe ตรงจาก gh — + # pipefail จะทำให้ pipeline ล้มตาม exit code ของ gh ก่อนที่ grep จะได้ทำงาน + rules_out=$(gh api "repos/$nwo/rules/branches/$branch" 2>&1 || true) + if printf '%s' "$rules_out" | jq -e 'type == "array"' >/dev/null 2>&1; then + [ "$(printf '%s' "$rules_out" | jq 'length')" -gt 0 ] && has_rules=1 + elif printf '%s' "$rules_out" | grep -q 'Upgrade to GitHub'; then + # แผนไม่รองรับ — ต่างจาก "ยังไม่ได้ตั้ง" อย่างสิ้นเชิง + has_rules=na + fi - total_missing=$(( (1 - has_claude) + (1 - has_owners) + (1 - has_prtpl) + (1 - has_ci) + (1 - has_rules) )) + rules_missing=0 + [ "$has_rules" = "0" ] && rules_missing=1 + total_missing=$(( (1 - has_claude) + (1 - has_owners) + (1 - has_prtpl) + (1 - has_ci) + rules_missing )) [ "$total_missing" -gt 0 ] && missing_total=$((missing_total + 1)) if [ "$only_missing" -eq 1 ] && [ "$total_missing" -eq 0 ]; then @@ -119,6 +136,9 @@ printf '%s' "$rows" cat <