diff --git a/.claude/commands/repo-maintenance.md b/.claude/commands/repo-maintenance.md index ae1fe456..7717d425 100644 --- a/.claude/commands/repo-maintenance.md +++ b/.claude/commands/repo-maintenance.md @@ -283,7 +283,7 @@ GitHub はフル SHA 固定を immutable な使い方として推奨しており | # | チェック | 推奨値 | リスク | | --- | ------------------------------ | ------------------------------------------ | ------------------------------ | -| 1 | 3rd-party actions の SHA 固定 | フルSHA(40文字)で参照 | タグ改ざんによるコード注入 | +| 1 | 全 actions の SHA 固定 | フルSHA(40文字)で参照 | タグ改ざんによるコード注入 | | 2 | `GITHUB_TOKEN` の権限制限 | workflow/job レベルで `permissions` を明示 | 過剰権限によるトークン悪用 | | 3 | 許可 actions の制限 | Repository Settings で制限 | 任意の actions 実行リスク | | 4 | `pull_request_target` の安全性 | 未使用 or secrets 非参照 | Fork PR 経由のシークレット漏洩 | @@ -298,15 +298,19 @@ for workflow in .github/workflows/*.yml; do BASENAME=$(basename "$workflow") CONTENT=$(cat "$workflow") - # 1. 3rd-party actions の SHA 固定チェック - # actions/ と github/ org 以外の actions を検出 - THIRD_PARTY=$(echo "$CONTENT" | grep -oE "uses: [^/]+/[^@]+@[^ ]+" | \ - grep -vE "^uses: (actions|github)/" | \ - grep -vE "@[0-9a-f]{40}") - if [ -n "$THIRD_PARTY" ]; then + # 1. actions の SHA 固定チェック(全 actions 対象) + # first-party(actions/, github/)もタグ参照のままの repo が多数あったため全件検出する。 + # 3rd-party は critical、first-party は warning として区別する + UNPINNED=$(echo "$CONTENT" | grep -oE "uses: [^/]+/[^@]+@[^ ]+" | \ + grep -vE "@[0-9a-f]{40}" | grep -v "uses: \./") + if [ -n "$UNPINNED" ]; then while IFS= read -r line; do - ISSUES+=("$BASENAME: SHA未固定の3rd-party action: $line") - done <<< "$THIRD_PARTY" + if echo "$line" | grep -qE "^uses: (actions|github)/"; then + ISSUES+=("$BASENAME: SHA未固定の first-party action (warning): $line") + else + ISSUES+=("$BASENAME: SHA未固定の 3rd-party action (critical): $line") + fi + done <<< "$UNPINNED" fi # 2. GITHUB_TOKEN 権限チェック(top-level permissions の有無) @@ -412,9 +416,38 @@ elif [ -n "$EXISTING_ENV_BRANCHES" ]; then fi ``` +**保護設定の実態検証(レビュー × 必須チェックの「両輪」確認):** + +最も頻出する設定不備は「レビュー必須 or 必須ステータスチェックの**片方しか**設定されていない」パターン。 +チェック欠落なら CI 赤のままマージ可能、レビュー欠落ならレビューなしマージが可能になる。 +/setup-team-protection の実行有無に関わらず、現在の実態を API で直接検証する: + +```bash +PROTECTION=$(gh api "repos/$REPO/branches/main/protection" 2>/dev/null) + +REVIEWS=$(echo "$PROTECTION" | jq -r '.required_pull_request_reviews.required_approving_review_count // 0') +CHECKS=$(echo "$PROTECTION" | jq -r '.required_status_checks.contexts // [] | length') +ENFORCE_ADMINS=$(echo "$PROTECTION" | jq -r '.enforce_admins.enabled // false') + +[ "$REVIEWS" -lt 1 ] && ISSUES+=("main: レビュー必須が未設定(0名)→ レビューなしでマージ可能") +[ "$CHECKS" -lt 1 ] && ISSUES+=("main: 必須ステータスチェックが未設定 → CI 赤のままマージ可能") +[ "$ENFORCE_ADMINS" != "true" ] && ISSUES+=("main: enforce_admins 無効 → 管理者は保護をバイパス可能") + +# マージ済みブランチの自動削除(無効だとリモートブランチが累積する) +DELETE_ON_MERGE=$(gh api "repos/$REPO" --jq '.delete_branch_on_merge' 2>/dev/null) +[ "$DELETE_ON_MERGE" != "true" ] && \ + ISSUES+=("repo: delete_branch_on_merge 無効 → マージ済みブランチが累積") +``` + +**MODE が `full` の場合の修正:** + +- レビュー / 必須チェックの欠落 → `/setup-team-protection` で両方を設定(必須チェックは `Quality Gate` を登録し、3.5.0.2 の fallback とセットで配布) +- `delete_branch_on_merge` → `gh api -X PATCH "repos/$REPO" -F delete_branch_on_merge=true` で自動修正 + 結果: -- ✅ 保護ルール設定済み +- ✅ 保護ルール設定済み(レビュー + 必須チェックの両輪) +- ⚠️ 片輪のみ設定(欠落側を明示: レビュー 0 名 / 必須チェックなし) - ⚠️ 未設定の保護ルールあり(詳細をリスト) - ⚠️ `pre-production` / `production` ブランチ未保護 → 保護を提案 - 🔧 設定を適用 @@ -1159,6 +1192,57 @@ grep -rn 'keito4/config' templates/ | grep -v 'README\|\.md' || echo "OK: no har このチェックは情報提供のみで、自動修正は行わない。 +### 3.5.4 CI Blocking Gate Check + +品質チェックが「CI に存在し、かつ blocking である」ことを確認: + +**背景:** +ローカルフック(husky / lint-staged)は `--no-verify` や別環境からの push で素通りできるため、 +CI 側に同じゲートがなければ品質保証にならない。実際に多くのリポジトリで以下の 2 パターンが発生していた: + +1. lint / typecheck がローカルフックのみで CI に存在しない +2. CI には存在するが `continue-on-error: true` で実質非ブロッキング(型エラーを数十件容認していた例あり) + +**確認ロジック:** + +```bash +# 1. package.json の品質スクリプトが CI から参照されているか +for script_name in lint typecheck type-check format:check test; do + jq -e --arg s "$script_name" '.scripts[$s]' package.json >/dev/null 2>&1 || continue + if ! grep -rqE "run (-w [^ ]+ )?$script_name\b|npx $script_name\b" .github/workflows/*.yml 2>/dev/null; then + ISSUES+=("品質スクリプト '$script_name' が CI のどのワークフローからも実行されていない(ローカルフックのみ)") + fi +done + +# 2. 品質ジョブの continue-on-error 検出 +for workflow in .github/workflows/*.yml; do + [ ! -f "$workflow" ] && continue + grep -q "continue-on-error: true" "$workflow" || continue + # lint/typecheck/test/coverage/audit を含むジョブ・ステップ近傍での使用を警告 + if grep -B5 "continue-on-error: true" "$workflow" | grep -qiE "(lint|typecheck|type-check|format|test|coverage)"; then + ISSUES+=("$(basename "$workflow"): 品質チェックに continue-on-error: true(実質非ブロッキング)") + fi +done +``` + +**結果パターン:** + +| 状態 | 対応 | +| ------------------------------------ | ----------------------------------------------------------- | +| 全品質スクリプトが CI で blocking | ✅ スキップ | +| CI 未参照の品質スクリプトあり | ⚠️ → full mode で ci.yml への追加を提案(テンプレート参照) | +| continue-on-error の品質チェックあり | ⚠️ → 解除可否を確認(段階導入中なら解除計画の Issue 化) | + +**Note**: npm audit 等のセキュリティ advisory は意図的に non-blocking とする運用もあるため、 +`continue-on-error` の検出は機械的に修正せず、意図を確認したうえで解除 or 理由のコメント追記を提案する。 + +**結果:** + +- ✅ CI Blocking Gates: 全品質チェックが CI で blocking +- ⚠️ CI 未参照の品質スクリプト: X 件(リスト表示) +- ⚠️ 非ブロッキングの品質チェック: X 件(リスト表示) +- ⏭️ スキップ(`package.json` なし) + ### 3.6 GitHub Actions Cost Optimization Check GitHub Actions のコスト最適化のため、全ワークフローの設定を確認: @@ -2086,10 +2170,40 @@ fi echo "🔧 SECURITY.md を追加しました" ``` +**内容の実態整合チェック:** + +SECURITY.md が存在する場合、プレースホルダーの残存と「記載と実態の乖離」を検証する。 +「secret scanning 有効」と記載しながら実際の GitHub 設定は無効、というドキュメントと実態の乖離は +誤った安心感を生むため、ファイルの存在チェックだけでは不十分: + +```bash +if [ "$HAS_SECURITY" = "true" ]; then + SEC_FILE=$([ -f "SECURITY.md" ] && echo "SECURITY.md" || echo ".github/SECURITY.md") + + # 1. プレースホルダー / TODO の残存 + if grep -qE "TODO|FIXME|security@example\.com|\[INSERT|