Frontend Labs
⋯

Day 87 — Fuzz Testing

Learning Objectives

  • Write fuzz tests for WAF normalization and JSON schema validator
  • Find crashes and panics with go test -fuzz
  • Fix all panics before deploying security-critical code

Exercise: Fuzz the WAF

// fuzz_test.go
package waf_test

import (
    "testing"
    "unicode/utf8"
)

// FuzzNormalizationPipeline finds inputs that panic the normalizer
func FuzzNormalizationPipeline(f *testing.F) {
    // Seed corpus — known interesting inputs
    seeds := []string{
        "",
        "\x00",
        "%",
        "%%",
        "%2e%2e%2f",
        "%252e%252e%252f",
        "' OR '1'='1",
        "<script>alert(1)</script>",
        strings.Repeat("A", 10000),  // very long input
        "\xff\xfe",                   // invalid UTF-8
        "&#x0000;",                   // null HTML entity
        "javascript:%0a%0dalert(1)", // newline-encoded JS
    }
    for _, s := range seeds {
        f.Add(s)
    }

    f.Fuzz(func(t *testing.T, input string) {
        // Must not panic on any input
        result := NormalizationPipeline(input)

        // Must return valid UTF-8 (so downstream string operations are safe)
        if !utf8.ValidString(result) {
            t.Errorf("normalization returned invalid UTF-8 for input %q", input)
        }

        // Must not return longer than input (normalization should not expand)
        if len(result) > len(input)*10 {
            t.Errorf("normalization expanded input suspiciously: %d → %d", len(input), len(result))
        }
    })
}

// FuzzJSONSchemaValidator finds inputs that panic the schema validator
func FuzzJSONSchemaValidator(f *testing.F) {
    seeds := [][]byte{
        []byte(`{}`),
        []byte(`{"username":"alice","email":"a@b.com","role":"admin","password":"secret123"}`),
        []byte(`null`),
        []byte(`[]`),
        []byte(`{{{`),                   // malformed JSON
        []byte(`{"x":` + strings.Repeat("[", 1000)),  // deeply nested
    }
    for _, s := range seeds {
        f.Add(s)
    }

    schema := &Schema{
        Type:     "object",
        Required: []string{"username", "email"},
        Properties: map[string]*Schema{
            "username": {Type: "string", MinLength: ptr(3), MaxLength: ptr(64)},
            "email":    {Type: "string"},
        },
    }

    f.Fuzz(func(t *testing.T, body []byte) {
        // Must not panic
        var data interface{}
        json.Unmarshal(body, &data)
        _ = schema.Validate(data, "$")
    })
}

Run the fuzzer:

# Fuzz for 30 seconds
go test -fuzz=FuzzNormalizationPipeline -fuzztime=30s ./...
go test -fuzz=FuzzJSONSchemaValidator -fuzztime=30s ./...

# If a crash is found, it's saved to testdata/fuzz/
# Run to reproduce:
go test -run=FuzzNormalizationPipeline/testdata/fuzz/...

Real-World Context

Go's built-in fuzzer finds bugs in security code that hand-written tests miss. In 2022, fuzzing found ~30 vulnerabilities in the Go standard library. For security-critical code (WAF, parsers, crypto), fuzzing for 10 minutes before each release is best practice. The testdata/fuzz/ directory stores crash-inducing inputs as regression tests.


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