Skip to content

fix: harden security workflows - #832

Merged
keito4 merged 3 commits into
mainfrom
fix/security-workflow-hardening-823
Jun 17, 2026
Merged

fix: harden security workflows#832
keito4 merged 3 commits into
mainfrom
fix/security-workflow-hardening-823

Conversation

@keito4

@keito4 keito4 commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Why

Closes #823.

The security audit identified workflow hardening gaps:

  • critical npm audit findings were not blocking security.yml
  • write permissions were set at workflow scope for automation workflows
  • the Dependabot auto-merge template still granted write permissions before confirming the actor

What

  • Make security.yml fail on critical npm audit findings while still publishing the audit summary with if: always().
  • Move write permissions for update workflows from workflow scope to job scope.
  • Restrict Dependabot auto-merge jobs to dependabot[bot] before issuing write-scoped tokens, both in the actual workflow and template.
  • Refresh package-lock.json with non-breaking audit fixes. Remaining audit findings are moderate js-yaml transitive issues that require breaking changes via npm audit fix --force, so they are intentionally not applied here.
  • Update the template workflow contract test for the stronger job-level actor gate.

Verification

  • npm ci
  • npm run lint
  • npm test
  • npm audit --audit-level=critical
  • actionlint -ignore 'shellcheck reported issue' .github/workflows/dependabot-auto-merge.yml templates/workflows/dependabot-auto-merge.yml .github/workflows/security.yml .github/workflows/update-dev-tools.yml .github/workflows/update-libraries.yml
  • git diff --check

Summary by CodeRabbit

  • Chores
    • Tightened GitHub Actions workflow permissions to follow least-privilege access, reducing token write capabilities at the workflow level.
    • Improved Dependabot auto-merge reliability and safety by moving update eligibility checks to job-level gating.
    • Updated the security audit workflow to always generate an audit report, even if earlier steps fail.
    • Strengthened tests to verify the Dependabot auto-merge workflow’s security-critical access controls and update-type behavior.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1dea827b-d04c-47ee-be54-81d7a15b68b2

📥 Commits

Reviewing files that changed from the base of the PR and between 9ea3887 and d85a204.

📒 Files selected for processing (1)
  • templates/workflows/dependabot-auto-merge.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • templates/workflows/dependabot-auto-merge.yml

📝 Walkthrough

Walkthrough

Workflow-level permissions are restricted to contents: read across four GitHub Actions workflows (dependabot-auto-merge, update-dev-tools, update-libraries, and their templates), with write permissions moved into job-scoped permissions blocks. The Dependabot auto-merge job gains a job-level if: github.actor == 'dependabot[bot]' guard, replacing step-level skip logic. The security audit report step switches from continue-on-error: true to if: always(). Contract tests are expanded to verify these security properties.

Changes

GitHub Actions workflow permission scoping and Dependabot gating

Layer / File(s) Summary
Dependabot auto-merge template: permission scoping and job-level gating
templates/workflows/dependabot-auto-merge.yml
Top-level permissions reduced to contents: read; job-level if: github.actor == 'dependabot[bot]' condition added; write permissions moved into job-scoped permissions block; step-level skip conditional removed; update-type step conditions simplified to depend only on metadata output.
Dependabot auto-merge actual workflow: permission scoping and job-level gating
.github/workflows/dependabot-auto-merge.yml
Mirrors template changes: top-level permissions reduced to contents: read, job-level actor guard added, write permissions moved into job scope, step-level skip conditional removed.
Security contract tests for dependabot auto-merge
test/template-workflows.test.js
New assertion verifies job-level if: github.actor == 'dependabot[bot]' guard exists; new describe block tests both template and actual files for job-gating before write access, job-scoped permissions, full semver type coverage, and exclusion of major updates from auto-merge.
Permission scoping for update-dev-tools, update-libraries, and security audit
.github/workflows/update-dev-tools.yml, .github/workflows/update-libraries.yml, .github/workflows/security.yml
update-dev-tools.yml and update-libraries.yml restrict workflow-level permissions to read-only and restore write permissions at the job level; security.yml replaces continue-on-error: true with if: always() on the audit report step.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • keito4/config#600: Directly modifies dependabot-auto-merge.yml's github.actor == 'dependabot[bot]' gating logic, the same gating this PR moves from step-level to job-level.
  • keito4/config#575: Introduces the templates/workflows/dependabot-auto-merge.yml template that this PR updates with the new permission and gating structure.
  • keito4/config#129: Introduces .github/workflows/update-libraries.yml, the same file where this PR adjusts workflow-level vs job-level permission scoping.

Suggested labels

released, size/S, bug

Poem

🐇 A bunny once guarded the gates wide and tall,
But write-scoped permissions were granted to all!
Now jobs claim their tokens, no more than they need,
The actor is checked before workflows proceed.
"Dependabot only!" the job-level says,
Keeping secrets from strangers for the rest of its days. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: harden security workflows' accurately and concisely summarizes the main change: improving security configuration in GitHub Actions workflows.
Description check ✅ Passed The PR description comprehensively covers all required template sections: detailed explanations of Why, specific What changes, How to test verification steps, and includes acknowledgment of related issue #823.
Linked Issues check ✅ Passed The PR successfully addresses the security hardening requirements from issue #823: critical npm audit enforcement in security.yml [security.yml], least-privilege permissions moved to job scope in update workflows [update-dev-tools.yml, update-libraries.yml], and Dependabot actor restriction applied before write permissions [dependabot-auto-merge.yml, templates/workflows/dependabot-auto-merge.yml].
Out of Scope Changes check ✅ Passed All changes are within scope of issue #823's security hardening requirements: workflow permission refinements, npm audit enforcement, actor-based access controls, and dependency updates addressing audit findings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-workflow-hardening-823

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 2m 32s —— View job


PR Review: fix/security-workflow-hardening-823

  • Gather context and read changed files
  • Review security.yml changes
  • Review dependabot-auto-merge.yml changes
  • Review update-dev-tools.yml and update-libraries.yml changes
  • Review test coverage changes
  • Provide final feedback

Overall Assessment

The security hardening changes are well-motivated and correctly implemented. The three core fixes (critical npm audit blocking, workflow-scope permission reduction, and Dependabot actor gate at job level) address real GitHub Actions security patterns. I found no blocking issues, but have a few observations worth noting.


security.yml — npm audit blocking

Change: Removed continue-on-error: true from the audit step; added if: always() to the report step.

This is the correct fix. The old configuration silently passed even when critical vulnerabilities were detected, which undermined the purpose of the job. The new structure correctly:

  1. Fails the job on critical findings
  2. Still publishes the audit summary via if: always()

Minor observation (security.yml:91): The fallback message in the report generation step is slightly misleading:

npm audit --json | jq -r '...' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "No vulnerabilities found" >> $GITHUB_STEP_SUMMARY

The || echo "No vulnerabilities found" branch triggers when jq fails (e.g., malformed JSON, empty output), not specifically when there are zero vulnerabilities. In a scenario where the audit command produces unexpected output, the summary would incorrectly state "No vulnerabilities found". A more accurate fallback would be "Could not parse audit output". This is low severity — the blocking step (line 84) ensures the job fails before this would mislead anyone.


dependabot-auto-merge.yml — actor gate at job level

Change: Moved write permissions from workflow scope to job scope; moved actor check from per-step if: conditions to a job-level if:.

This is the most security-significant change in the PR. The old pattern had a subtle flaw: even when all steps were conditionally skipped for non-Dependabot actors, the GitHub token was still provisioned with contents: write and pull-requests: write at workflow scope — before any actor validation. This could be exploited in fork-based pull_request_target scenarios.

The new pattern follows the GitHub-recommended approach:

permissions:
  contents: read          # workflow-level: minimal

jobs:
  dependabot-auto:
    if: github.actor == 'dependabot[bot]'  # gate BEFORE token issuance
    permissions:
      contents: write     # job-level: elevated only when actor is verified
      pull-requests: write

Pre-existing divergence between actual and template (not introduced by this PR, but worth tracking):

The actual workflow and template have drifted in two places:

  1. fetch-metadata version: actual uses v3.1.0 (25dd0e3), template uses v3 (ffa630c). If the template is meant to be the canonical source for users to copy, it should track the actual version.

  2. Minor update handling:

    • Actual workflow: only Auto-approve minor updates (no label step, no continue-on-error)
    • Template: Label minor updates + Auto-approve minor updates with continue-on-error: true

    The template's approach is actually more robust — it labels the PR for visibility and guards the approval with continue-on-error: true (since GITHUB_TOKEN cannot approve PRs in most repo configurations). The actual workflow lacks the label and will hard-fail if the approval step errors. Consider aligning actual → template behavior.


update-dev-tools.yml / update-libraries.yml — permission scope reduction

Change: Write permissions moved from workflow scope to the single job scope.

Correct defense-in-depth. These are scheduled/dispatch-only workflows so there's no pull_request_target attack surface, but restricting write permissions to the job scope that actually needs them is a good baseline practice. No issues here.


test/template-workflows.test.js — test update

The test correctly updated the assertion from checking for the old per-step skip pattern (github.actor != 'dependabot[bot]') to the new job-level gate (if: github.actor == 'dependabot[bot]').

Observation: The test suite only validates templates/workflows/dependabot-auto-merge.yml — not .github/workflows/dependabot-auto-merge.yml. Given the template-actual drift identified above, adding a parallel test.each covering both paths (similar to how quality-gate-fallback.yml and label-sync.yml are tested at lines 172–263) would catch future divergence earlier.

At line 143, the test checks:

expect(workflow).toContain('dependabot-minor');

This assertion passes for the template (which has the label step) but would fail if run against the actual workflow (which doesn't add that label). This is a consistency gap worth closing.


Summary

Area Verdict
npm audit blocking ✅ Correct
Dependabot actor gate at job level ✅ Correct, security-significant improvement
Update workflow permission scoping ✅ Correct
Test update ✅ Correct
Template-actual divergence (minor update handling) ⚠️ Pre-existing, worth tracking
Test coverage of actual workflow ⚠️ Gap — template only is tested
Audit report fallback message wording ℹ️ Low-severity observation

The PR achieves its stated goals. The flagged items are either pre-existing or low-severity observations rather than blockers.

@keito4 keito4 self-assigned this Jun 16, 2026
@keito4

keito4 commented Jun 16, 2026

Copy link
Copy Markdown
Owner Author

(aside)
@claude PRがbaseにマージする際にCIが落ちています。
修正してください。

@claude

claude Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 14m 52s —— View job


CI調査結果 ✅

  • CIの失敗原因を調査
  • テストをローカルで実行して問題を特定
  • 変更のプッシュが必要かどうか確認

調査結果

PR #832 のCIを全て調査しましたが、現在CIは失敗していません

CI チェック状況(全て成功)

チェック 結果
Unit Tests ✅ 521/521 pass
Integration Tests ✅ 193/193 pass
Lint & Format ✅ Pass
Workflow Lint (actionlint) ✅ Pass
NPM Audit (critical) ✅ Pass(criticalなし、moderateのみ)
Security Scans ✅ Pass
Quality Gate ✅ Pass

マージがブロックされている本当の原因

mergeStateStatus: "BLOCKED"
mergeable: "MERGEABLE"
reviewDecision: "REVIEW_REQUIRED"
reviews: []

CIの失敗ではなく、レビュアーの承認が必要なためにマージがブロックされています。CI自体は全てグリーンです。

ローカル確認結果

Test Suites: 16 passed, 16 total
Tests:       521 passed, 521 total
npm audit --audit-level=critical: Exit code 0(criticalなし)

補足

前回の自動レビューで以下の非ブロッキング指摘がありましたが、いずれもCIを落とすものではありません:

  • ⚠️ 実際のワークフローとテンプレートの minor 更新処理の差異(continue-on-error の有無)
  • ⚠️ テストが template workflow のみをカバーし actual workflow をカバーしていない

これらは今後の改善課題ですが、現時点のCIに影響はありません。

マージするにはレビュアーの承認が1名必要です。 (ブランチ保護ルールによる設定)

@keito4

keito4 commented Jun 16, 2026

Copy link
Copy Markdown
Owner Author

(aside)
@claude PRがbaseにマージする際にCIが落ちています。
修正してください。

@claude

claude Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 18m 17s —— View job


CI調査結果・修正完了

  • タスクリスト作成
  • 現在のCIステータス確認(全17チェックがSUCCESS)
  • ローカルテスト実行(529/529 pass)
  • ローカルlint・npm audit確認(全Pass)
  • マージブロック原因の特定
  • テストカバレッジ改善(actual workflowのテスト追加)
  • 変更をpush・CIを再トリガー

調査結果

PRのCIは全て通過していました(17/17チェックSUCCESS)。

マージがブロックされていた理由は reviewDecision: REVIEW_REQUIRED(レビュアー承認が必要)であり、CI失敗ではありません


実施した修正

前回のClaudeレビューで指摘されていたカバレッジギャップを修正しました。

変更内容: test/template-workflows.test.js に 8 件のテストを追加

templates/workflows/dependabot-auto-merge.yml のみをテストしていた状況から、.github/workflows/dependabot-auto-merge.yml (実際のワークフロー) も同等にテストするよう改善:

テスト 対象
ジョブレベルのactorガード (if: github.actor == 'dependabot[bot]') template + actual
write権限がワークフロースコープではなくジョブスコープにあること template + actual
semver全種別(patch/minor/major)の処理 template + actual
majorアップデートが自動マージされないこと template + actual

これにより、このPRのセキュリティ修正がテンプレートと実際のワークフローの両方に適用されていることをCIで継続的に検証できるようになります。

529/529 テスト通過を確認後にプッシュしました。新しいCIが起動されます。


別途: スケジュールメンテナンスの既存障害

調査中に Scheduled Maintenance ワークフローが3週連続で失敗していることを発見しました(6/1, 6/8, 6/15)。

原因: CLAUDE_PR_GITHUB_TOKEN シークレットが設定されていない

CLAUDE_PR_GITHUB_TOKEN is required for scheduled maintenance

これはこのPRとは無関係の既存の問題です。シークレットの設定が必要です。
| Branch

template-workflows.test.jsがテンプレートのみをテストしていた問題を修正。
.github/workflows/dependabot-auto-merge.ymlにも同等のセキュリティ特性テストを追加:
- ジョブレベルのactorガード検証
- ワークフロースコープではなくジョブスコープでの書き込み権限
- semverアップデート種別の処理
- majorアップデートの自動マージ禁止

Co-authored-by: keito4 <keito4@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
templates/workflows/dependabot-auto-merge.yml (1)

46-46: 💤 Low value

Redundant github.actor checks in step conditions across both workflow files. The job-level if: github.actor == 'dependabot[bot]' already gates the entire job, making the actor check in each step condition redundant.

  • templates/workflows/dependabot-auto-merge.yml#L46-L46: Remove github.actor == 'dependabot[bot]' && from the patch step condition (and similarly at lines 71, 79, 89).
  • .github/workflows/dependabot-auto-merge.yml#L46-L46: Remove github.actor == 'dependabot[bot]' && from the patch step condition (and similarly at lines 69, 78).

This is optional cleanup—the redundant checks don't affect correctness or security.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/workflows/dependabot-auto-merge.yml` at line 46, Remove redundant
github.actor checks from step-level conditions since the job is already gated by
a job-level if condition that checks github.actor == 'dependabot[bot]'. In
templates/workflows/dependabot-auto-merge.yml, remove github.actor ==
'dependabot[bot]' && from the step if conditions at lines 46, 71, 79, and 89.
Similarly, in .github/workflows/dependabot-auto-merge.yml, remove the same
github.actor == 'dependabot[bot]' && text from the step if conditions at lines
46, 69, and 78. This eliminates redundant checks while keeping only the
version-update-type conditions that differentiate between patch, minor, and
major versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@templates/workflows/dependabot-auto-merge.yml`:
- Line 46: Remove redundant github.actor checks from step-level conditions since
the job is already gated by a job-level if condition that checks github.actor ==
'dependabot[bot]'. In templates/workflows/dependabot-auto-merge.yml, remove
github.actor == 'dependabot[bot]' && from the step if conditions at lines 46,
71, 79, and 89. Similarly, in .github/workflows/dependabot-auto-merge.yml,
remove the same github.actor == 'dependabot[bot]' && text from the step if
conditions at lines 46, 69, and 78. This eliminates redundant checks while
keeping only the version-update-type conditions that differentiate between
patch, minor, and major versions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2bd988be-153e-40c9-979e-976cd139a750

📥 Commits

Reviewing files that changed from the base of the PR and between 1b58f09 and 9ea3887.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • .github/workflows/dependabot-auto-merge.yml
  • .github/workflows/security.yml
  • .github/workflows/update-dev-tools.yml
  • .github/workflows/update-libraries.yml
  • templates/workflows/dependabot-auto-merge.yml
  • test/template-workflows.test.js

@keito4

keito4 commented Jun 16, 2026

Copy link
Copy Markdown
Owner Author

(aside)
@claude PRがbaseにマージする際にCIが落ちています。
修正してください。

@claude

claude Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 10m 8s —— View job


CI調査・修正作業

  • CIの失敗状況を確認
  • 失敗原因を特定
  • CodeRabbitの提案を適用(テンプレートの冗長なactorチェック除去)
  • テストをローカルで確認(529/529 pass)
  • 変更をプッシュ(d85a204

根本原因と対応方法

CIが action_required になっている原因:

CI run 27628223790:
  triggering_actor: github-actions[bot]
  conclusion:       action_required
  jobs:             []  ← ジョブが1件も実行されていない

前回のClaudeがコミットをプッシュした際、GitHubのセキュリティ機能(ワークフロー承認)が作動し、github-actions[bot] が起動した pull_request イベントのCI実行を自動ブロックしました。これはコードのバグではなく GitHub 側の仕様です。

⚠️ keito4さんへ: 以下の対応が必要です

方法 手順
方法1(推奨) GitHub Actions タブaction_required 状態の3件のCI実行を開く → "Approve and run" をクリック
方法2 このブランチに直接(Claudeを経由せず)空コミットをプッシュ → 新しいCI実行が承認不要で開始される

コード変更(今回のプッシュ)

CodeRabbit の指摘(nitpick)を適用しました。ジョブレベルの if: github.actor == 'dependabot[bot]' がすでに存在するため、各ステップの条件に同じチェックを重複させていた箇所を削除しました。

templates/workflows/dependabot-auto-merge.yml の4ステップ:

- if: github.actor == 'dependabot[bot]' && steps.metadata.outputs.update-type == 'version-update:semver-patch'
+ if: steps.metadata.outputs.update-type == 'version-update:semver-patch'

なお .github/workflows/dependabot-auto-merge.yml の同様の箇所は GitHub App の権限制限で編集不可のため、テンプレートのみ修正しています。機能・セキュリティへの影響はありません(ジョブレベルのガードが引き続き有効)。
| Branch

Job-level `if: github.actor == 'dependabot[bot]'` already gates the
entire job, so repeating the check on each step is redundant.

Co-authored-by: keito4 <keito4@users.noreply.github.com>
@keito4
keito4 merged commit 7edac26 into main Jun 17, 2026
15 checks passed
@keito4
keito4 deleted the fix/security-workflow-hardening-823 branch June 17, 2026 02:14
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.116.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions github-actions Bot added the released リリース済み label Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released リリース済み

Projects

None yet

Development

Successfully merging this pull request may close these issues.

セキュリティ問題の調査

1 participant