Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Fuzz

on:
pull_request:
push:
workflow_dispatch:

permissions:
contents: read

jobs:
fuzz-short:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '^1.22'
check-latest: true
cache: true
- name: Run short fuzzing session
run: |
# Run all fuzz targets briefly; -run=^$ disables non-fuzz tests
go test ./... -run=^$ -fuzz=Fuzz -fuzztime=30s

10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ If you have questions regarding Cobra, feel free to ask it in the community
1. Since this is golang project, ensure the new code is properly formatted to
ensure code consistency. Run `make all`.

### Fuzz testing

Go 1.18+ fuzz tests are included. For a short local fuzz run:

```
go test ./... -run=^$ -fuzz=Fuzz -fuzztime=30s
```

CI runs a brief fuzz pass on each push/PR. See `site/content/fuzzing.md` for details and OSS-Fuzz onboarding pointers.

### Quick steps to contribute

1. Fork the project.
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ For complete details on using the Cobra-CLI generator, please read [The Cobra Ge

For complete details on using the Cobra library, please read [The Cobra User Guide](site/content/user_guide.md).

# Fuzz testing

Cobra includes Go fuzz tests (Go 1.18+). To run a short fuzzing session locally:

```
go test ./... -run=^$ -fuzz=Fuzz -fuzztime=30s
```

We are exploring integration with Google's OSS-Fuzz to further harden Cobra. See Go's official fuzzing docs at [`https://go.dev/doc/security/fuzz/`](https://go.dev/doc/security/fuzz/).

# License

Cobra is released under the Apache 2.0 license. See [LICENSE.txt](LICENSE.txt)
88 changes: 88 additions & 0 deletions fuzz/cobra_fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//go:build go1.18
// +build go1.18

package cobra

import (
"strings"
"testing"
)

// FuzzLd fuzzes the Levenshtein distance implementation and checks basic invariants.
func FuzzLd(f *testing.F) {
// Seed corpus
f.Add("", "")
f.Add("a", "")
f.Add("", "a")
f.Add("kitten", "sitting")
f.Add("Saturday", "Sunday")
f.Add("MixedCase", "mixedcase")

f.Fuzz(func(t *testing.T, a string, b string) {
// Distance is always >= 0
d := ld(a, b, false)
if d < 0 {
t.Fatalf("ld returned negative distance: %d", d)
}

// Symmetry: ld(a,b) == ld(b,a)
d2 := ld(b, a, false)
if d != d2 {
t.Fatalf("ld not symmetric: ld(%q,%q)=%d ld(%q,%q)=%d", a, b, d, b, a, d2)
}

// Case-insensitive should be <= case-sensitive for inputs differing only by case
if strings.EqualFold(a, b) {
di := ld(a, b, true)
if di != 0 {
t.Fatalf("ld case-insensitive mismatch for equalFold inputs: %q %q => %d", a, b, di)
}
}

// Identity: distance is 0 when strings are equal
if a == b && d != 0 {
t.Fatalf("ld identity broken: ld(%q,%q)=%d", a, b, d)
}
})
}

// FuzzConfigEnvVar fuzzes configEnvVar to ensure it only produces A-Z0-9_ and is stable for allowed inputs.
func FuzzConfigEnvVar(f *testing.F) {
f.Add("prog", "ACTIVE_HELP")
f.Add("My-App", "COMPLETION_DESCRIPTIONS")
f.Add("", "X")
f.Fuzz(func(t *testing.T, name, suffix string) {
v := configEnvVar(name, suffix)
if v == "" {
t.Fatal("empty env var name")
}
// Must contain only A-Z0-9_
for i := 0; i < len(v); i++ {
c := v[i]
if !(c == '_' || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
t.Fatalf("invalid char %q in %q", c, v)
}
}
// Ensure uppercasing behavior for simple alnum inputs
cleanName := name
for i := 0; i < len(cleanName); i++ {
b := cleanName[i]
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')) {
cleanName = ""
break
}
}
if cleanName != "" {
upper := strings.ToUpper(cleanName + "_" + suffix)
// The sanitizer replaces non A-Z0-9_ with _, which shouldn't occur here.
if v != strings.Map(func(r rune) rune {
if (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
return r
}
return '_'
}, upper) {
t.Fatalf("unexpected mapping for simple input: got %q want %q", v, upper)
}
}
})
}
27 changes: 27 additions & 0 deletions site/content/fuzzing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Fuzz testing
weight: 90
---

This project includes Go fuzz tests (Go 1.18+). You can run a short fuzz session locally with:

```
go test ./... -run=^$ -fuzz=Fuzz -fuzztime=30s
```

Continuous Integration runs a short fuzz pass on each push and pull request.

OSS-Fuzz integration

- Cobra is prepared for OSS-Fuzz via in-repo `Fuzz*` targets (see `fuzz/`).
- To onboard to OSS-Fuzz, open a PR to the upstream `google/oss-fuzz` repo creating `projects/cobra/` with:
- `project.yaml` referencing Go as the language
- `Dockerfile` installing dependencies and building the fuzz targets
- `build.sh` that runs `compile_go_fuzzer` for each fuzz function (e.g., `FuzzLd`, `FuzzConfigEnvVar`)
- Reference: https://google.github.io/oss-fuzz/getting-started/new-project-guide/

Notes

- Keep fuzz targets small and deterministic with clear invariants.
- Prefer focusing on pure functions and parsers (e.g., helpers like `ld`, env var processing).