Skip to content

Commit 7d9980e

Browse files
authored
test: Add fuzz test for ParseWebHook (#4076)
1 parent 3117b62 commit 7d9980e

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

github/fuzz_messages_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2026 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"encoding/json"
10+
"fmt"
11+
"testing"
12+
)
13+
14+
// FuzzParseWebHook tests ParseWebHook against arbitrary event types and payloads.
15+
// It verifies that no input triggers a panic or nil pointer dereference.
16+
//
17+
// This fuzz test is intended for integration with OSS-Fuzz (https://google.github.io/oss-fuzz/)
18+
// for continuous fuzzing in the cloud.
19+
//
20+
// To run:
21+
//
22+
// go test -fuzz=^FuzzParseWebHook$ -fuzztime=30s .
23+
func FuzzParseWebHook(f *testing.F) {
24+
seeds := []struct {
25+
eventType string
26+
payload string
27+
}{
28+
{"push", `{"ref": "refs/heads/main", "before": "000000", "after": "123456", "commits": [{"id": "abc", "message": "msg", "added": [], "removed": [], "modified": []}]}`},
29+
{"pull_request", `{"action": "opened", "number": 1, "pull_request": {"title": "test", "state": "open", "user": {"login": "u"}}}`},
30+
{"issues", `{"action": "opened", "issue": {"number": 42, "title": "bug", "state": "open"}}`},
31+
{"release", `{"action": "published", "release": {"tag_name": "v1.0.0", "draft": false}}`},
32+
{"check_run", `{"action": "created", "check_run": {"status": "in_progress", "id": 1}}`},
33+
{"check_suite", `{"action": "completed", "check_suite": {"id": 1, "status": "completed"}}`},
34+
{"workflow_run", `{"action": "requested", "workflow_run": {"id": 123, "status": "queued"}}`},
35+
{"workflow_job", `{"action": "queued", "workflow_job": {"id": 1, "status": "queued"}}`},
36+
{"discussion", `{"action": "created", "discussion": {"title": "hello", "number": 1}}`},
37+
{"ping", `{"zen": "Keep it logically awesome.", "hook_id": 1}`},
38+
{"repository", `{"action": "created", "repository": {"name": "test-repo", "private": false}}`},
39+
{"star", `{"action": "created", "starred_at": "2026-03-11T00:00:00Z"}`},
40+
{"create", `{"ref": "main", "ref_type": "branch"}`},
41+
{"delete", `{"ref": "old-branch", "ref_type": "branch"}`},
42+
{"fork", `{"forkee": {"name": "forked-repo"}}`},
43+
{"deployment", `{"action": "created", "deployment": {"id": 1, "ref": "main"}}`},
44+
{"deployment_status", `{"action": "created", "deployment_status": {"id": 1, "state": "pending"}}`},
45+
{"member", `{"action": "added", "member": {"login": "user"}}`},
46+
{"public", `{"repository": {"name": "now-public"}}`},
47+
{"commit_comment", `{"action": "created", "comment": {"id": 1, "body": "comment"}}`},
48+
}
49+
for _, s := range seeds {
50+
f.Add(s.eventType, []byte(s.payload))
51+
}
52+
53+
for _, messageType := range MessageTypes() {
54+
proto := EventForType(messageType)
55+
if proto == nil {
56+
f.Add(messageType, []byte(`{}`))
57+
continue
58+
}
59+
// Generate a seed by marshaling the zero-value struct so the fuzzer
60+
// starts from a structurally valid JSON skeleton for each event type.
61+
b, err := json.Marshal(proto)
62+
if err != nil {
63+
f.Add(messageType, []byte(`{}`))
64+
continue
65+
}
66+
f.Add(messageType, b)
67+
}
68+
69+
f.Fuzz(func(_ *testing.T, eventType string, payload []byte) {
70+
if len(payload) > 1<<20 {
71+
return
72+
}
73+
event, err := ParseWebHook(eventType, payload)
74+
if err != nil {
75+
return
76+
}
77+
if event != nil {
78+
// Traverse all fields recursively to catch nil pointer dereferences
79+
_ = fmt.Sprintf("%+v", event)
80+
}
81+
})
82+
}

0 commit comments

Comments
 (0)