feat(fuzz): add fuzz harnesses for jwt, secrets and yaml parsing (#340)

* refactor(modules): extract parseYAMLModuleBytes from ParseYAMLModule

split the byte-parsing and validation core out of the file-reading
wrapper so module definitions can be parsed from memory. behavior is
unchanged: ParseYAMLModule reads the file then delegates.

* test(fuzz): add harnesses for parsers and extractors

cover the untrusted-input parsers that had no fuzz coverage: jwt
analysis and segment decode, js secret scanning, yaml module parsing,
openapi spec parsing, html title extraction and js endpoint extraction.
the secrets, openapi and endpoint harnesses assert invariants (match is
a substring of input, ok implies non-nil spec, results non-empty and
sorted); the rest are crash-only.

---------

Co-authored-by: vmfunc <vmfunc.lc@gmail.com>
This commit is contained in:
Tigah
2026-07-22 22:24:43 +00:00
committed by GitHub
co-authored by vmfunc
parent 7d206ad033
commit 1d7e219656
6 changed files with 210 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package modules
import "testing"
func FuzzParseYAMLModule(f *testing.F) {
f.Add([]byte("id: t\ntype: http\n"))
f.Add([]byte("id: x\ntype: http\nhttp:\n matchers:\n - type: word\n words: [foo]\n"))
f.Add([]byte("type: http\n"))
f.Add([]byte("id: x\ntype: dns\n"))
f.Add([]byte(""))
f.Add([]byte("id: x\ntype: http\nhttp:\n matchers-condition: xor\n"))
f.Fuzz(func(t *testing.T, data []byte) {
ParseYAMLModuleBytes(data)
})
}
+39
View File
@@ -0,0 +1,39 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package js
import (
"slices"
"testing"
)
func FuzzExtractEndpoints(f *testing.F) {
f.Add(`fetch("/api/users")`, "https://example.com/app.js")
f.Add(`url: "https://cdn.example.com/v1/data.json"`, "")
f.Add(`const p = "../relative/path"`, "https://example.com/a/b/")
f.Add(`"text/html"`, "https://example.com")
f.Add("", "")
f.Add(`axios.get("/x").then()`, "not a url")
f.Fuzz(func(t *testing.T, content, baseURL string) {
got := ExtractEndpoints(content, baseURL)
for _, e := range got {
if e == "" {
t.Fatal("ExtractEndpoints returned an empty endpoint")
}
}
if !slices.IsSorted(got) {
t.Fatalf("ExtractEndpoints result not sorted: %v", got)
}
})
}
+41
View File
@@ -0,0 +1,41 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package js
import (
"strings"
"testing"
)
func FuzzScanSecrets(f *testing.F) {
f.Add(`const key = "AKIAIOSFODNN7EXAMPLE"`, "https://example.com/app.js")
f.Add(`apikey: "sk-1234567890abcdefghij"`, "")
f.Add("ghp_0123456789abcdefghijklmnopqrstuvwxyz01", "src")
f.Add("-----BEGIN RSA PRIVATE KEY-----", "")
f.Add(`var token = ""`, "")
f.Add("", "")
f.Add("aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "js")
f.Fuzz(func(t *testing.T, content, srcURL string) {
for _, m := range ScanSecrets(content, srcURL) {
// a reported match must be a non-empty run lifted verbatim from the
// input; anything else means the capture-group indexing is off
if m.Match == "" {
t.Fatalf("empty Match for rule %q", m.Rule)
}
if !strings.Contains(content, m.Match) {
t.Fatalf("Match %q (rule %q) not found in input", m.Match, m.Rule)
}
}
})
}
+41
View File
@@ -0,0 +1,41 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import "testing"
func FuzzAnalyzeJWT(f *testing.F) {
f.Add("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.sig")
f.Add("eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9.")
f.Add("a.b.c")
f.Add("..")
f.Add("")
f.Add("not-a-jwt")
f.Fuzz(func(t *testing.T, raw string) {
analyzeJWT("fuzz", raw)
})
}
func FuzzDecodeJWTSegment(f *testing.F) {
f.Add("eyJhbGciOiJIUzI1NiJ9")
f.Add("eyJzdWIiOiIxMjM0In0")
f.Add("bm90LWpzb24")
f.Add("!!!!")
f.Add("")
f.Add("eyJhIjp7ImIiOnsiYyI6MX19fQ")
f.Fuzz(func(t *testing.T, seg string) {
decodeJWTSegment(seg)
})
}
+32
View File
@@ -0,0 +1,32 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import "testing"
func FuzzParseOpenAPISpec(f *testing.F) {
f.Add([]byte(`{"openapi":"3.0.0","paths":{"/x":{"get":{}}}}`))
f.Add([]byte(`{"swagger":"2.0","paths":{"/y":{}}}`))
f.Add([]byte("openapi: 3.0.0\npaths:\n /z:\n get: {}\n"))
f.Add([]byte(`{"paths":{}}`))
f.Add([]byte("not a spec"))
f.Add([]byte(""))
f.Add([]byte("{"))
f.Fuzz(func(t *testing.T, body []byte) {
spec, ok := parseOpenAPISpec(body)
if ok && spec == nil {
t.Fatal("parseOpenAPISpec returned ok with a nil spec")
}
})
}
+29
View File
@@ -0,0 +1,29 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import "testing"
func FuzzExtractTitle(f *testing.F) {
f.Add([]byte("<html><head><title>Hi</title></head></html>"))
f.Add([]byte("<TITLE class=x> spaced </TITLE>"))
f.Add([]byte("<title>unclosed"))
f.Add([]byte("<title></title>"))
f.Add([]byte("no title here"))
f.Add([]byte(""))
f.Add([]byte("<title>a</title><title>b</title>"))
f.Fuzz(func(t *testing.T, body []byte) {
extractTitle(body)
})
}