From 265835542468846484cd53e98cb4c64aee376f6d Mon Sep 17 00:00:00 2001 From: TBX3D <88289044+TBX3D@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:28:39 -0700 Subject: [PATCH 1/2] 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. --- internal/modules/yaml.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/modules/yaml.go b/internal/modules/yaml.go index d4042392..d311bb83 100644 --- a/internal/modules/yaml.go +++ b/internal/modules/yaml.go @@ -88,7 +88,12 @@ func ParseYAMLModule(path string) (*YAMLModule, error) { if err != nil { return nil, fmt.Errorf("read module file: %w", err) } + return parseYAMLModuleBytes(data) +} +// parseYAMLModuleBytes unmarshals and validates a module definition from raw +// bytes. ParseYAMLModule wraps it after reading the file off disk. +func parseYAMLModuleBytes(data []byte) (*YAMLModule, error) { var ym YAMLModule if err := yaml.Unmarshal(data, &ym); err != nil { return nil, fmt.Errorf("parse yaml: %w", err) From f16f6aaf2bf1230a229b8e4428b87cf3aedd2ba0 Mon Sep 17 00:00:00 2001 From: TBX3D <88289044+TBX3D@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:28:47 -0700 Subject: [PATCH 2/2] 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. --- internal/modules/yaml_fuzz_test.go | 28 +++++++++++++++++ internal/scan/js/endpoints_fuzz_test.go | 39 +++++++++++++++++++++++ internal/scan/js/secrets_fuzz_test.go | 41 +++++++++++++++++++++++++ internal/scan/jwt_fuzz_test.go | 41 +++++++++++++++++++++++++ internal/scan/openapi_fuzz_test.go | 32 +++++++++++++++++++ internal/scan/probe_fuzz_test.go | 29 +++++++++++++++++ 6 files changed, 210 insertions(+) create mode 100644 internal/modules/yaml_fuzz_test.go create mode 100644 internal/scan/js/endpoints_fuzz_test.go create mode 100644 internal/scan/js/secrets_fuzz_test.go create mode 100644 internal/scan/jwt_fuzz_test.go create mode 100644 internal/scan/openapi_fuzz_test.go create mode 100644 internal/scan/probe_fuzz_test.go diff --git a/internal/modules/yaml_fuzz_test.go b/internal/modules/yaml_fuzz_test.go new file mode 100644 index 00000000..f92e6329 --- /dev/null +++ b/internal/modules/yaml_fuzz_test.go @@ -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) + }) +} diff --git a/internal/scan/js/endpoints_fuzz_test.go b/internal/scan/js/endpoints_fuzz_test.go new file mode 100644 index 00000000..bfa2530f --- /dev/null +++ b/internal/scan/js/endpoints_fuzz_test.go @@ -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) + } + }) +} diff --git a/internal/scan/js/secrets_fuzz_test.go b/internal/scan/js/secrets_fuzz_test.go new file mode 100644 index 00000000..1c5e57f9 --- /dev/null +++ b/internal/scan/js/secrets_fuzz_test.go @@ -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) + } + } + }) +} diff --git a/internal/scan/jwt_fuzz_test.go b/internal/scan/jwt_fuzz_test.go new file mode 100644 index 00000000..9d99fbce --- /dev/null +++ b/internal/scan/jwt_fuzz_test.go @@ -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) + }) +} diff --git a/internal/scan/openapi_fuzz_test.go b/internal/scan/openapi_fuzz_test.go new file mode 100644 index 00000000..cb83f49c --- /dev/null +++ b/internal/scan/openapi_fuzz_test.go @@ -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") + } + }) +} diff --git a/internal/scan/probe_fuzz_test.go b/internal/scan/probe_fuzz_test.go new file mode 100644 index 00000000..585b20f1 --- /dev/null +++ b/internal/scan/probe_fuzz_test.go @@ -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("