2525 - ' **.txt'
2626 - ' docs/**'
2727 schedule :
28- - cron : ' 0 6 * * *' # Daily at 06:00 UTC
29-
30- # Avoid duplicate runs for the same source branch and repository.
31- # For pull_request events, uses the source repo name from
32- # github.event.pull_request.head.repo.full_name; otherwise uses github.repository.
33- # For push events, uses the branch name from github.ref_name.
34- # For pull_request events, uses the source branch name from github.head_ref.
35- # This ensures events for the same repo and branch share the same group,
36- # and avoids cross-fork collisions when branch names are reused.
37- concurrency :
38- group : >-
39- ${{ github.workflow }}-${{
40- github.event.pull_request.head.repo.full_name || github.repository
41- }}-${{ github.head_ref || github.ref_name }}
42- cancel-in-progress : true
28+ # Batch Fuzzing: 01:30 AM UTC+7 = 18:30 UTC
29+ - cron : ' 30 18 * * *'
30+ # Corpus Pruning: 04:00 AM UTC+7 = 21:00 UTC (2.5 h after Batch Fuzzing)
31+ - cron : ' 0 21 * * *'
4332
33+ # Restrict default permissions to read-only at the workflow level.
34+ # Each job that needs write access declares it explicitly.
4435permissions :
45- contents : write
46- issues : write
36+ contents : read
4737
4838jobs :
49- fuzzing :
39+ # -------------------------------------------------------------------------
40+ # 1. PR Fuzzing
41+ # Quick check for "shallow" bugs introduced by new code.
42+ # Target: under 10 minutes. Triggered by pull_request or push.
43+ # -------------------------------------------------------------------------
44+ pr-fuzzing :
45+ name : PR Fuzzing
46+ if : github.event_name == 'pull_request' || github.event_name == 'push'
5047 runs-on : ubuntu-latest
48+ permissions :
49+ contents : write # Push corpus updates to gh-pages
50+ issues : write # Allow run_fuzzers to file issues on crashes
51+ # Cancel in-progress runs for the same branch to avoid wasted resources.
52+ # Uses the source repo name to avoid cross-fork collisions.
53+ concurrency :
54+ group : >-
55+ pr-fuzzing-${{
56+ github.event.pull_request.head.repo.full_name || github.repository
57+ }}-${{ github.head_ref || github.ref_name }}
58+ cancel-in-progress : true
5159 strategy :
5260 fail-fast : false
5361 matrix :
5462 sanitizer : [address]
5563 steps :
64+ - name : Summarize job parameters
65+ run : |
66+ {
67+ echo "## PR Fuzzing"
68+ echo ""
69+ echo "| Property | Value |"
70+ echo "| --- | --- |"
71+ echo "| Sanitizer | \`${{ matrix.sanitizer }}\` |"
72+ echo "| Mode | \`code-change\` |"
73+ echo "| Fuzz seconds | 300 |"
74+ echo "| Trigger | \`${{ github.event_name }}\` |"
75+ echo "| Ref | \`${{ github.head_ref || github.ref_name }}\` |"
76+ } >> "$GITHUB_STEP_SUMMARY"
77+
5678 - name : Build Fuzzers (${{ matrix.sanitizer }})
5779 id : build
5880 uses : google/clusterfuzzlite/actions/build_fuzzers@v1
@@ -66,15 +88,178 @@ jobs:
6688 with :
6789 github-token : ${{ secrets.GITHUB_TOKEN }}
6890 fuzz-seconds : 300
69- mode : ${{ github.event_name == 'pull_request' && 'code-change' || 'batch' }}
91+ mode : code-change
92+ sanitizer : ${{ matrix.sanitizer }}
93+ storage-repo : https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
94+ storage-repo-branch : gh-pages
95+ storage-repo-branch-coverage : gh-pages
96+
97+ - name : Report results
98+ if : always()
99+ run : |
100+ {
101+ echo ""
102+ if [ "${{ steps.build.outcome }}" != "success" ]; then
103+ echo ":x: **Build failed.** Check the build step log for details."
104+ elif [ "${{ steps.run.outcome }}" = "success" ]; then
105+ echo ":white_check_mark: **PR fuzzing completed — no new crashes found.**"
106+ else
107+ echo ":x: **PR fuzzing found a crash or encountered an error.**"
108+ echo "Download the \`${{ matrix.sanitizer }}-pr-artifacts\` artifact for crash inputs."
109+ fi
110+ } >> "$GITHUB_STEP_SUMMARY"
111+
112+ - name : Upload crash artifacts
113+ if : failure() && steps.run.outcome == 'failure'
114+ uses : actions/upload-artifact@v4
115+ with :
116+ name : ${{ matrix.sanitizer }}-pr-artifacts
117+ path : ./out/artifacts
118+
119+ # -------------------------------------------------------------------------
120+ # 2. Batch Fuzzing
121+ # Deep, long-running session to build corpus and find "deep" edge cases.
122+ # Duration: ~2 hour. Scheduled daily at 01:30 AM UTC+7 (18:30 UTC).
123+ # -------------------------------------------------------------------------
124+ batch-fuzzing :
125+ name : Batch Fuzzing
126+ if : >-
127+ github.event_name == 'schedule' &&
128+ github.event.schedule == '30 18 * * *'
129+ runs-on : ubuntu-latest
130+ permissions :
131+ contents : write # Push corpus updates to gh-pages
132+ issues : write # Allow run_fuzzers to file issues on crashes
133+ # Do not cancel in-progress batch runs; let them finish naturally.
134+ concurrency :
135+ group : batch-fuzzing-${{ github.repository }}
136+ cancel-in-progress : false
137+ strategy :
138+ fail-fast : false
139+ matrix :
140+ sanitizer : [address]
141+ steps :
142+ - name : Summarize job parameters
143+ run : |
144+ {
145+ echo "## Batch Fuzzing"
146+ echo ""
147+ echo "| Property | Value |"
148+ echo "| --- | --- |"
149+ echo "| Sanitizer | \`${{ matrix.sanitizer }}\` |"
150+ echo "| Mode | \`batch\` |"
151+ echo "| Fuzz seconds | 7200 |"
152+ echo "| Schedule | 01:30 AM UTC+7 (18:30 UTC) |"
153+ } >> "$GITHUB_STEP_SUMMARY"
154+
155+ - name : Build Fuzzers (${{ matrix.sanitizer }})
156+ id : build
157+ uses : google/clusterfuzzlite/actions/build_fuzzers@v1
158+ with :
159+ sanitizer : ${{ matrix.sanitizer }}
160+ language : python
161+
162+ - name : Run Fuzzers (${{ matrix.sanitizer }})
163+ id : run
164+ uses : google/clusterfuzzlite/actions/run_fuzzers@v1
165+ with :
166+ github-token : ${{ secrets.GITHUB_TOKEN }}
167+ fuzz-seconds : 7200
168+ mode : batch
70169 sanitizer : ${{ matrix.sanitizer }}
71170 storage-repo : https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
72171 storage-repo-branch : gh-pages
73172 storage-repo-branch-coverage : gh-pages
74173
174+ - name : Report results
175+ if : always()
176+ run : |
177+ {
178+ echo ""
179+ if [ "${{ steps.build.outcome }}" != "success" ]; then
180+ echo ":x: **Build failed.** Check the build step log for details."
181+ elif [ "${{ steps.run.outcome }}" = "success" ]; then
182+ echo ":white_check_mark: **Batch fuzzing completed — no new crashes found.**"
183+ echo ""
184+ echo "Corpus has been updated in the \`gh-pages\` branch."
185+ else
186+ echo ":x: **Batch fuzzing found a crash or encountered an error.**"
187+ echo "Download the \`${{ matrix.sanitizer }}-batch-artifacts\` artifact for crash inputs."
188+ fi
189+ } >> "$GITHUB_STEP_SUMMARY"
190+
75191 - name : Upload crash artifacts
76192 if : failure() && steps.run.outcome == 'failure'
77193 uses : actions/upload-artifact@v4
78194 with :
79- name : ${{ matrix.sanitizer }}-artifacts
195+ name : ${{ matrix.sanitizer }}-batch- artifacts
80196 path : ./out/artifacts
197+
198+ # -------------------------------------------------------------------------
199+ # 3. Corpus Pruning
200+ # Housekeeping: removes redundant test cases to keep the fuzzer efficient.
201+ # Scheduled daily at 04:00 AM UTC+7 (21:00 UTC), 2.5 h after Batch Fuzzing.
202+ # -------------------------------------------------------------------------
203+ corpus-pruning :
204+ name : Corpus Pruning
205+ if : >-
206+ github.event_name == 'schedule' &&
207+ github.event.schedule == '0 21 * * *'
208+ runs-on : ubuntu-latest
209+ permissions :
210+ contents : write # Push pruned corpus back to gh-pages
211+ # Do not cancel in-progress pruning runs.
212+ concurrency :
213+ group : corpus-pruning-${{ github.repository }}
214+ cancel-in-progress : false
215+ strategy :
216+ fail-fast : false
217+ matrix :
218+ sanitizer : [address]
219+ steps :
220+ - name : Summarize job parameters
221+ run : |
222+ {
223+ echo "## Corpus Pruning"
224+ echo ""
225+ echo "| Property | Value |"
226+ echo "| --- | --- |"
227+ echo "| Sanitizer | \`${{ matrix.sanitizer }}\` |"
228+ echo "| Mode | \`prune\` |"
229+ echo "| Schedule | 04:00 AM UTC+7 (21:00 UTC) |"
230+ } >> "$GITHUB_STEP_SUMMARY"
231+
232+ - name : Build Fuzzers (${{ matrix.sanitizer }})
233+ id : build
234+ uses : google/clusterfuzzlite/actions/build_fuzzers@v1
235+ with :
236+ sanitizer : ${{ matrix.sanitizer }}
237+ language : python
238+
239+ - name : Prune Corpus (${{ matrix.sanitizer }})
240+ id : prune
241+ uses : google/clusterfuzzlite/actions/run_fuzzers@v1
242+ with :
243+ github-token : ${{ secrets.GITHUB_TOKEN }}
244+ fuzz-seconds : 600
245+ mode : prune
246+ sanitizer : ${{ matrix.sanitizer }}
247+ storage-repo : https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
248+ storage-repo-branch : gh-pages
249+ storage-repo-branch-coverage : gh-pages
250+
251+ - name : Report results
252+ if : always()
253+ run : |
254+ {
255+ echo ""
256+ if [ "${{ steps.build.outcome }}" != "success" ]; then
257+ echo ":x: **Build failed.** Check the build step log for details."
258+ elif [ "${{ steps.prune.outcome }}" = "success" ]; then
259+ echo ":white_check_mark: **Corpus pruning completed successfully.**"
260+ echo ""
261+ echo "The corpus in the \`gh-pages\` branch has been pruned."
262+ else
263+ echo ":x: **Corpus pruning encountered an error.** Check the prune step log."
264+ fi
265+ } >> "$GITHUB_STEP_SUMMARY"
0 commit comments