Skip to content

Commit cb79c37

Browse files
mapedersenclaude
andcommitted
feat: add Dependabot security protection
πŸ›‘οΈ Block Dependabot production deployments: - Detect Dependabot commits and skip deployment - Add auto-merge workflow for safe updates - Require manual deployment after dependency updates πŸš€ Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fc90702 commit cb79c37

2 files changed

Lines changed: 222 additions & 10 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
name: πŸ€– Dependabot Auto-merge
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
11+
jobs:
12+
# ═══════════════════════════════════════════════════════════════════════════════
13+
# πŸ€– DEPENDABOT PR HANDLING
14+
# ═══════════════════════════════════════════════════════════════════════════════
15+
dependabot-auto-merge:
16+
name: πŸ€– Dependabot Auto-merge
17+
runs-on: ubuntu-latest
18+
if: github.actor == 'dependabot[bot]'
19+
timeout-minutes: 10
20+
21+
steps:
22+
- name: πŸ“₯ Checkout PR
23+
uses: actions/checkout@v4
24+
with:
25+
token: ${{ secrets.GITHUB_TOKEN }}
26+
27+
- name: πŸ” Analyze Dependabot PR
28+
id: analyze
29+
run: |
30+
echo "πŸ€– Analyzing Dependabot PR..."
31+
32+
# Get PR details
33+
PR_TITLE="${{ github.event.pull_request.title }}"
34+
PR_BODY="${{ github.event.pull_request.body }}"
35+
36+
echo "πŸ“‹ PR Title: $PR_TITLE"
37+
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
38+
39+
# Determine update type
40+
if echo "$PR_TITLE" | grep -q "Bump.*from.*to"; then
41+
echo "πŸ”„ Standard dependency update detected"
42+
echo "update_type=dependency" >> $GITHUB_OUTPUT
43+
elif echo "$PR_TITLE" | grep -qi "security"; then
44+
echo "πŸ”’ Security update detected"
45+
echo "update_type=security" >> $GITHUB_OUTPUT
46+
else
47+
echo "❓ Unknown update type"
48+
echo "update_type=unknown" >> $GITHUB_OUTPUT
49+
fi
50+
51+
- name: πŸ” Check if CI passed
52+
uses: actions/github-script@v7
53+
id: ci-check
54+
with:
55+
script: |
56+
const { data: checks } = await github.rest.checks.listForRef({
57+
owner: context.repo.owner,
58+
repo: context.repo.repo,
59+
ref: context.payload.pull_request.head.sha,
60+
});
61+
62+
// Look for main CI workflow
63+
const ciChecks = checks.check_runs.filter(check =>
64+
check.name.includes('Quality Gate') ||
65+
check.name.includes('Code Quality')
66+
);
67+
68+
const allPassed = ciChecks.every(check => check.status === 'completed' && check.conclusion === 'success');
69+
const anyFailed = ciChecks.some(check => check.conclusion === 'failure');
70+
71+
if (anyFailed) {
72+
console.log('❌ Some CI checks failed');
73+
core.setOutput('ci_status', 'failed');
74+
} else if (allPassed && ciChecks.length > 0) {
75+
console.log('βœ… All CI checks passed');
76+
core.setOutput('ci_status', 'passed');
77+
} else {
78+
console.log('⏳ CI checks still running or not found');
79+
core.setOutput('ci_status', 'pending');
80+
}
81+
82+
- name: 🎯 Auto-merge Decision
83+
id: decision
84+
run: |
85+
CI_STATUS="${{ steps.ci-check.outputs.ci_status }}"
86+
UPDATE_TYPE="${{ steps.analyze.outputs.update_type }}"
87+
88+
echo "🎯 Making auto-merge decision..."
89+
echo " CI Status: $CI_STATUS"
90+
echo " Update Type: $UPDATE_TYPE"
91+
92+
SHOULD_MERGE="false"
93+
94+
# Auto-merge conditions
95+
if [[ "$CI_STATUS" == "passed" ]]; then
96+
if [[ "$UPDATE_TYPE" == "security" ]]; then
97+
echo "πŸ”’ Security update with passing CI - auto-merging"
98+
SHOULD_MERGE="true"
99+
elif [[ "$UPDATE_TYPE" == "dependency" ]]; then
100+
# Check if it's a patch/minor update (safer)
101+
if echo "${{ steps.analyze.outputs.pr_title }}" | grep -E "(patch|minor)" > /dev/null; then
102+
echo "πŸ”„ Patch/minor update with passing CI - auto-merging"
103+
SHOULD_MERGE="true"
104+
else
105+
echo "⚠️ Major update - requires manual review"
106+
SHOULD_MERGE="false"
107+
fi
108+
fi
109+
else
110+
echo "❌ CI not passed - skipping auto-merge"
111+
SHOULD_MERGE="false"
112+
fi
113+
114+
echo "should_merge=$SHOULD_MERGE" >> $GITHUB_OUTPUT
115+
echo "🎯 Decision: Auto-merge = $SHOULD_MERGE"
116+
117+
- name: βœ… Auto-merge Dependabot PR
118+
if: steps.decision.outputs.should_merge == 'true'
119+
uses: actions/github-script@v7
120+
with:
121+
script: |
122+
console.log('βœ… Auto-merging Dependabot PR...');
123+
124+
await github.rest.pulls.merge({
125+
owner: context.repo.owner,
126+
repo: context.repo.repo,
127+
pull_number: context.payload.pull_request.number,
128+
commit_title: `πŸ€– ${context.payload.pull_request.title}`,
129+
commit_message: `Auto-merged by Dependabot workflow\n\n${context.payload.pull_request.body}`,
130+
merge_method: 'squash'
131+
});
132+
133+
console.log('πŸŽ‰ Dependabot PR auto-merged successfully!');
134+
135+
- name: πŸ’¬ Add Review Comment
136+
if: steps.decision.outputs.should_merge == 'false'
137+
uses: actions/github-script@v7
138+
with:
139+
script: |
140+
const ciStatus = '${{ steps.ci-check.outputs.ci_status }}';
141+
const updateType = '${{ steps.analyze.outputs.update_type }}';
142+
143+
let message = '## πŸ€– Dependabot Auto-merge Analysis\n\n';
144+
145+
if (ciStatus === 'failed') {
146+
message += '❌ **CI checks failed** - This PR requires manual review and fixes.\n\n';
147+
} else if (ciStatus === 'pending') {
148+
message += '⏳ **CI checks still running** - Auto-merge will be reconsidered once checks complete.\n\n';
149+
} else if (updateType === 'dependency' && !`${{ steps.analyze.outputs.pr_title }}`.match(/(patch|minor)/)) {
150+
message += '⚠️ **Major version update detected** - This requires manual review for potential breaking changes.\n\n';
151+
} else {
152+
message += 'ℹ️ **Manual review required** - This PR did not meet auto-merge criteria.\n\n';
153+
}
154+
155+
message += '### πŸ“‹ Auto-merge Criteria\n';
156+
message += '- βœ… Security updates with passing CI\n';
157+
message += '- βœ… Patch/minor dependency updates with passing CI\n';
158+
message += '- ❌ Major version updates (require manual review)\n';
159+
message += '- ❌ Any PR with failing CI checks\n\n';
160+
message += '**Note:** Production deployment is automatically skipped for all Dependabot commits for security.';
161+
162+
await github.rest.issues.createComment({
163+
owner: context.repo.owner,
164+
repo: context.repo.repo,
165+
issue_number: context.payload.pull_request.number,
166+
body: message
167+
});
168+
169+
# ═══════════════════════════════════════════════════════════════════════════════
170+
# πŸ“Š SUMMARY REPORTING
171+
# ═══════════════════════════════════════════════════════════════════════════════
172+
summary:
173+
name: πŸ“Š Dependabot Summary
174+
runs-on: ubuntu-latest
175+
needs: [dependabot-auto-merge]
176+
if: always() && github.actor == 'dependabot[bot]'
177+
178+
steps:
179+
- name: πŸ“Š Generate Summary
180+
run: |
181+
echo "## πŸ€– Dependabot PR Summary" >> $GITHUB_STEP_SUMMARY
182+
echo "" >> $GITHUB_STEP_SUMMARY
183+
echo "**PR:** ${{ github.event.pull_request.title }}" >> $GITHUB_STEP_SUMMARY
184+
echo "**Author:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
185+
echo "**Status:** ${{ needs.dependabot-auto-merge.result }}" >> $GITHUB_STEP_SUMMARY
186+
echo "" >> $GITHUB_STEP_SUMMARY
187+
188+
if [[ "${{ needs.dependabot-auto-merge.result }}" == "success" ]]; then
189+
echo "βœ… **Dependabot PR processed successfully**" >> $GITHUB_STEP_SUMMARY
190+
echo "" >> $GITHUB_STEP_SUMMARY
191+
echo "### πŸ›‘οΈ Security Note" >> $GITHUB_STEP_SUMMARY
192+
echo "Production deployment is **automatically skipped** for Dependabot commits." >> $GITHUB_STEP_SUMMARY
193+
echo "Use the Manual Deployment workflow if you need to deploy after testing." >> $GITHUB_STEP_SUMMARY
194+
else
195+
echo "⚠️ **Dependabot PR requires attention**" >> $GITHUB_STEP_SUMMARY
196+
fi

β€Ž.github/workflows/pipeline.ymlβ€Ž

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,26 @@ jobs:
9696
echo "target_environment=staging" >> $GITHUB_OUTPUT
9797
echo "is_release=false" >> $GITHUB_OUTPUT
9898
elif [[ "${{ github.ref_name }}" == "main" ]]; then
99-
echo "should_test=true" >> $GITHUB_OUTPUT
100-
echo "should_build=true" >> $GITHUB_OUTPUT
101-
echo "should_deploy_staging=false" >> $GITHUB_OUTPUT
102-
echo "should_deploy_production=true" >> $GITHUB_OUTPUT
103-
echo "target_environment=production" >> $GITHUB_OUTPUT
104-
echo "is_release=true" >> $GITHUB_OUTPUT
99+
# Check if this is a Dependabot commit
100+
COMMIT_AUTHOR="${{ github.event.head_commit.author.login }}"
101+
COMMIT_MESSAGE="${{ github.event.head_commit.message }}"
102+
103+
if [[ "$COMMIT_AUTHOR" == "dependabot[bot]" ]] || [[ "$COMMIT_MESSAGE" =~ ^(build\(deps\)|chore\(deps\)) ]]; then
104+
echo "πŸ€– Dependabot commit detected - skipping production deployment"
105+
echo "should_test=true" >> $GITHUB_OUTPUT
106+
echo "should_build=false" >> $GITHUB_OUTPUT
107+
echo "should_deploy_staging=false" >> $GITHUB_OUTPUT
108+
echo "should_deploy_production=false" >> $GITHUB_OUTPUT
109+
echo "target_environment=dependency-update" >> $GITHUB_OUTPUT
110+
echo "is_release=false" >> $GITHUB_OUTPUT
111+
else
112+
echo "should_test=true" >> $GITHUB_OUTPUT
113+
echo "should_build=true" >> $GITHUB_OUTPUT
114+
echo "should_deploy_staging=false" >> $GITHUB_OUTPUT
115+
echo "should_deploy_production=true" >> $GITHUB_OUTPUT
116+
echo "target_environment=production" >> $GITHUB_OUTPUT
117+
echo "is_release=true" >> $GITHUB_OUTPUT
118+
fi
105119
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
106120
case "${{ github.event.inputs.stage }}" in
107121
"test-only")
@@ -140,10 +154,12 @@ jobs:
140154
echo "version=$VERSION" >> $GITHUB_OUTPUT
141155
142156
echo "βœ… Pipeline plan complete:"
143-
echo " πŸ§ͺ Test: $(echo '${{ steps.plan.outputs.should_test }}' | jq -r '.')"
144-
echo " πŸ”¨ Build: $(echo '${{ steps.plan.outputs.should_build }}' | jq -r '.')"
145-
echo " 🎭 Deploy Staging: $(echo '${{ steps.plan.outputs.should_deploy_staging }}' | jq -r '.')"
146-
echo " πŸš€ Deploy Production: $(echo '${{ steps.plan.outputs.should_deploy_production }}' | jq -r '.')"
157+
echo " πŸ‘€ Author: ${{ github.event.head_commit.author.login }}"
158+
echo " πŸ’¬ Commit: ${{ github.event.head_commit.message }}"
159+
echo " πŸ§ͺ Test: ${{ steps.plan.outputs.should_test }}"
160+
echo " πŸ”¨ Build: ${{ steps.plan.outputs.should_build }}"
161+
echo " 🎭 Deploy Staging: ${{ steps.plan.outputs.should_deploy_staging }}"
162+
echo " πŸš€ Deploy Production: ${{ steps.plan.outputs.should_deploy_production }}"
147163
echo " 🎯 Environment: ${{ steps.plan.outputs.target_environment }}"
148164
echo " πŸ“¦ Version: $VERSION"
149165

0 commit comments

Comments
Β (0)