Frontend Labs
⋯

Day 117 — Fuzz Testing Critical Parsers

Learning objective Write production-grade fuzz tests for WAF normalization, JA3 parser, and binary protocol parser
Key packages testing/fuzz
Security context A panic in your WAF is a denial-of-service — fuzz testing is the most effective way to find panic paths

Key Concepts

What to fuzz:

  • Any code that parses input of arbitrary length/content
  • Any code with complex branching on input content (WAF rules, protocol parsers)
  • Any code where wrong behavior has security impact (auth, schema validator)

Exercise — Fuzz Tests

package fuzz_test

import (
    "testing"
    // Import your packages
)

// FuzzNormalizationPipeline — does the WAF normalizer panic on arbitrary input?
func FuzzNormalizationPipeline(f *testing.F) {
    // Seed corpus — known interesting inputs
    seeds := []string{
        "",
        "normal input",
        "%27 OR 1=1--",
        "<script>alert(1)</script>",
        "../../../../etc/passwd",
        string([]byte{0x00, 0x01, 0x02}), // null bytes
        string([]byte{0xff, 0xfe}),         // invalid UTF-8
        "%gg%hh",                            // invalid percent encoding
        "%%2527",                            // double-double encoded
        "a" + string(make([]byte, 10000)),  // very long input
        "a\r\n\r\nGET / HTTP/1.1",          // CRLF injection
    }
    for _, s := range seeds {
        f.Add(s)
    }

    f.Fuzz(func(t *testing.T, input string) {
        // Must not panic on any input
        result := normalize(input)
        // Result must be a valid string (non-nil, valid UTF-8 is not required)
        _ = result
        // Normalization must be idempotent: normalize(normalize(x)) == normalize(x)
        if normalize(result) != result {
            t.Errorf("normalization not idempotent for input %q: first=%q second=%q",
                input, result, normalize(result))
        }
    })
}

// FuzzJSONSchemaValidator — does malformed JSON panic the schema validator?
func FuzzJSONSchemaValidator(f *testing.F) {
    seeds := []string{
        `{}`,
        `{"name":"test"}`,
        `{"name":null}`,
        `{"name":true}`,
        `[1,2,3]`,
        `{"a":{"b":{"c":{"d":{}}}}}`, // deep nesting
        `{"\u0000":"value"}`,           // null byte in key
        `{"key":"` + string(make([]byte, 100000)) + `"}`, // huge value
        `{`,          // truncated
        `{"k":`,      // truncated
        `{} extra`,   // trailing garbage
    }
    for _, s := range seeds {
        f.Add(s)
    }

    schema := &JSONSchema{
        Type:       "object",
        Required:   []string{"name"},
        Properties: map[string]*JSONSchema{"name": {Type: "string", MaxLength: 255}},
        AdditionalProperties: boolPtr(false),
    }

    f.Fuzz(func(t *testing.T, input string) {
        // Must not panic
        errs := schema.Validate([]byte(input))
        // Just check it returns without panicking
        _ = errs
    })
}

// FuzzBinaryProtocolParser — does the binary parser panic on arbitrary bytes?
func FuzzBinaryProtocolParser(f *testing.F) {
    seeds := [][]byte{
        {},
        {0x00, 0x00, 0x00, 0x00},           // zero-length packet
        {0x00, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o'}, // valid packet
        {0xff, 0xff, 0xff, 0xff},             // max length
        {0x00, 0x00, 0x00, 0x01},             // length 1, no data
        make([]byte, 1024),                    // all zeros
    }
    for _, s := range seeds {
        f.Add(s)
    }

    f.Fuzz(func(t *testing.T, data []byte) {
        // Must not panic — parsePacket must return an error for invalid input
        payload, err := parsePacket(data)
        if err != nil {
            return // expected for most random input
        }
        // If no error: payload must be a valid slice
        if payload == nil {
            t.Error("parsePacket returned nil payload with nil error")
        }
        // Round-trip test: re-parsing the encoded payload should give same result
        // (only if your protocol has a serialization function)
    })
}

// Run fuzz tests:
// go test -fuzz=FuzzNormalizationPipeline -fuzztime=60s ./internal/waf/...
// go test -fuzz=FuzzJSONSchemaValidator -fuzztime=60s ./internal/validation/...
// go test -fuzz=FuzzBinaryProtocolParser -fuzztime=60s ./internal/protocol/...

Generated from week16.md · 8 Oct 2026 16:36