-
Notifications
You must be signed in to change notification settings - Fork 0
189 lines (163 loc) · 6.72 KB
/
Copy pathpr-validation.yml
File metadata and controls
189 lines (163 loc) · 6.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# ──────────────────────────────────────────────────────────────────────────────
# PR Validation — Enforces PR hygiene and merge readiness
#
# Runs on pull_request events targeting main and develop.
# Validates PR title format, description quality, commit hygiene,
# and ensures no secrets or forbidden patterns are introduced.
# ──────────────────────────────────────────────────────────────────────────────
name: PR Validation
on:
pull_request:
branches: [main, develop]
types: [opened, edited, synchronize, reopened]
permissions:
contents: read
pull-requests: read
jobs:
# ── PR Title Format ─────────────────────────────────────────────────────
pr-title:
name: PR Title Format
runs-on: ubuntu-latest
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
requireScope: false
subjectPattern: ^.{1,70}$
subjectPatternError: |
PR title subject must be 70 characters or fewer.
Current: "{subject}" ({length} chars)
# ── PR Description ──────────────────────────────────────────────────────
pr-description:
name: PR Description Check
runs-on: ubuntu-latest
steps:
- name: Check PR description is not empty
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const body = (pr.body || '').trim();
if (body.length < 30) {
core.setFailed(
'PR description must be at least 30 characters. ' +
'Include: summary of changes, risks, and how to test.'
);
return;
}
core.info(`PR description length: ${body.length} chars — OK`);
# ── Secrets & Sensitive Files ───────────────────────────────────────────
secrets-scan:
name: Secrets & Sensitive Files
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for sensitive file changes
run: |
# Files that should never be committed
FORBIDDEN_PATTERNS=(
'\.env$'
'\.env\.'
'credentials\.json'
'\.pem$'
'\.key$'
'id_rsa'
'id_ed25519'
)
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD 2>/dev/null || echo "")
if [ -z "$CHANGED_FILES" ]; then
echo "No changed files detected (or base ref unavailable). Skipping."
exit 0
fi
VIOLATIONS=""
for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
MATCHES=$(echo "$CHANGED_FILES" | grep -iE "$pattern" || true)
if [ -n "$MATCHES" ]; then
VIOLATIONS="$VIOLATIONS\n - $MATCHES (matched: $pattern)"
fi
done
if [ -n "$VIOLATIONS" ]; then
echo "::error::Sensitive files detected in PR:$VIOLATIONS"
exit 1
fi
echo "No sensitive files detected."
- name: Scan for hardcoded secrets in diff
run: |
DIFF=$(git diff origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.js' '*.json' '*.yml' '*.yaml' '*.md' 2>/dev/null || echo "")
if [ -z "$DIFF" ]; then
echo "No diff available. Skipping."
exit 0
fi
# Patterns that suggest hardcoded secrets (added lines only)
SECRET_PATTERNS=(
'AKIA[0-9A-Z]{16}' # AWS Access Key
'sk-[a-zA-Z0-9]{20,}' # OpenAI / Stripe-style keys
'ghp_[a-zA-Z0-9]{36}' # GitHub PAT
'github_pat_[a-zA-Z0-9_]{82}' # GitHub fine-grained PAT
'sk-ant-[a-zA-Z0-9-]{90,}' # Anthropic API key
'AIza[0-9A-Za-z_-]{35}' # Google API key
)
FOUND=""
for pattern in "${SECRET_PATTERNS[@]}"; do
MATCHES=$(echo "$DIFF" | grep -E "^\+" | grep -oE "$pattern" || true)
if [ -n "$MATCHES" ]; then
FOUND="$FOUND\n Pattern: $pattern"
fi
done
if [ -n "$FOUND" ]; then
echo "::error::Potential hardcoded secrets detected in diff:$FOUND"
echo "::error::Remove secrets and use environment variables or GitHub Secrets instead."
exit 1
fi
echo "No hardcoded secrets detected."
# ── Diff Size Check ─────────────────────────────────────────────────────
diff-size:
name: Diff Size Check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check diff size
uses: actions/github-script@v7
with:
script: |
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
per_page: 100,
});
const totalChanges = files.reduce((sum, f) => sum + f.changes, 0);
const totalFiles = files.length;
core.info(`PR changes: ${totalChanges} lines across ${totalFiles} files`);
// Warn (not fail) on large PRs
if (totalChanges > 1000) {
core.warning(
`Large PR detected: ${totalChanges} lines changed across ${totalFiles} files. ` +
`Consider splitting into smaller, focused PRs for easier review.`
);
}
if (totalFiles > 50) {
core.warning(
`PR touches ${totalFiles} files. Consider splitting into smaller PRs.`
);
}