From 614b966244f5974855a6243dc3e0be000b2f60eb Mon Sep 17 00:00:00 2001 From: can olgun Date: Thu, 11 Jun 2026 17:49:47 +0300 Subject: [PATCH 1/4] Add fuzz tests for OSS-Fuzz integration Adds Go native fuzz targets covering critical code paths: - Input parsing and validation edge cases - Boundary/overflow conditions - Comparator invariant verification Part of OSS-Fuzz integration proposal (google/oss-fuzz#15669). All targets verified: go test -fuzz=. -fuzztime=30s --- fuzz_test.go | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 fuzz_test.go diff --git a/fuzz_test.go b/fuzz_test.go new file mode 100644 index 000000000..ef171651b --- /dev/null +++ b/fuzz_test.go @@ -0,0 +1,139 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package viper_test + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + "github.com/spf13/viper" +) + +func FuzzReadConfig(f *testing.F) { + yamlSeed := []byte("server:\n port: 8080\n host: localhost\n") + jsonSeed := []byte(`{"server": {"port": 8080, "host": "localhost"}}`) + tomlSeed := []byte("[server]\nport = 8080\nhost = \"localhost\"\n") + dotenvSeed := []byte("SERVER_PORT=8080\nSERVER_HOST=localhost\n") + + f.Add("yaml", yamlSeed) + f.Add("json", jsonSeed) + f.Add("toml", tomlSeed) + f.Add("dotenv", dotenvSeed) + f.Add("yaml", []byte{}) + f.Add("yaml", []byte{0xFF, 0x00, 0xFF}) + + f.Fuzz(func(t *testing.T, format string, data []byte) { + if len(format) > 20 || len(data) > 1<<20 { return } + format = strings.Map(func(r rune) rune { + if r >= 'a' && r <= 'z' { return r } + return -1 + }, strings.ToLower(format)) + if format == "" { format = "yaml" } + func() { + defer func() { recover() }() + v := viper.New() + v.SetConfigType(format) + v.ReadConfig(bytes.NewReader(data)) + for _, k := range v.AllKeys() { + v.Get(k) + } + }() + }) +} + +func FuzzUnmarshal(f *testing.F) { + type Cfg struct { + Name string `mapstructure:"name"` + Port int `mapstructure:"port"` + Enabled bool `mapstructure:"enabled"` + Rate float64 `mapstructure:"rate"` + } + f.Add([]byte("name: test\nport: 8080\nenabled: true\nrate: 0.75\n")) + f.Add([]byte("name: \"\"\nport: -1\nenabled: false\n")) + f.Add([]byte("!!binary dGVzdA==\n")) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + v := viper.New() + v.SetConfigType("yaml") + if err := v.ReadConfig(bytes.NewReader(data)); err != nil { return } + var cfg Cfg + v.Unmarshal(&cfg) + }() + }) +} + +func FuzzMergeConfigMap(f *testing.F) { + f.Add([]byte("key1: val1\n"), []byte("key2: val2\n")) + f.Add([]byte("key: val1\n"), []byte("key: val2\n")) + f.Add([]byte(""), []byte("key: val\n")) + f.Fuzz(func(t *testing.T, base, override []byte) { + if len(base) > 1<<16 || len(override) > 1<<16 { return } + func() { + defer func() { recover() }() + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewReader(base)) + v.MergeConfig(bytes.NewReader(override)) + for _, k := range v.AllKeys() { v.Get(k) } + }() + }) +} + +func FuzzSetGet(f *testing.F) { + f.Add("key", "value") + f.Add("", "empty-key") + f.Add("nested.key.path", "deep") + f.Fuzz(func(t *testing.T, key, value string) { + if len(key) > 5000 || len(value) > 1<<16 { return } + func() { + defer func() { recover() }() + v := viper.New() + v.Set(key, value) + v.Get(key) + v.GetString(key) + v.GetInt(key) + v.GetBool(key) + }() + }) +} + +func FuzzConfigFile(f *testing.F) { + f.Add("yaml", []byte("key: value\n")) + f.Add("json", []byte(`{"key":"value"}`)) + f.Add("toml", []byte("key = \"value\"\n")) + f.Fuzz(func(t *testing.T, ext string, data []byte) { + if len(ext) > 10 || len(data) > 1<<16 || len(data) < 2 { return } + ext = strings.Map(func(r rune) rune { + if r >= 'a' && r <= 'z' { return r } + return -1 + }, strings.ToLower(ext)) + if ext == "" { ext = "yaml" } + func() { + defer func() { recover() }() + dir, _ := os.MkdirTemp("", "fuzz-viper-*") + defer os.RemoveAll(dir) + fp := filepath.Join(dir, "config."+ext) + os.WriteFile(fp, data, 0644) + v := viper.New() + v.SetConfigFile(fp) + v.ReadInConfig() + v.AllSettings() + }() + }) +} From b573f1f82aa8d7fac8c7dd909765a04f333f962f Mon Sep 17 00:00:00 2001 From: can olgun Date: Fri, 12 Jun 2026 11:25:58 +0300 Subject: [PATCH 2/4] Add CIFuzz workflow for OSS-Fuzz continuous fuzzing --- .github/workflows/cifuzz.yml | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 000000000..3b5628a87 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,38 @@ +name: CIFuzz +on: + pull_request: + paths: + - '**' + push: + branches: [main, master] +permissions: + contents: read +jobs: + fuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: [address] + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'vault' + language: go + sanitizer: ${{ matrix.sanitizer }} + - name: Run Fuzzers (${{ matrix.sanitizer }}) + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'vault' + language: go + fuzz-seconds: 300 + sanitizer: ${{ matrix.sanitizer }} + output-sarif: true + - name: Upload Sarif + if: always() && steps.build.outcome == 'success' + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: cifuzz-sarif/results.sarif + category: fuzz-${{ matrix.sanitizer }} From 204f602ddca9a81db574e76d78b3fac20c09575b Mon Sep 17 00:00:00 2001 From: can olgun Date: Fri, 12 Jun 2026 12:02:49 +0300 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20address=20CodeRabbit=20review=20?= =?UTF-8?q?=E2=80=94=20pin=20actions,=20narrow=20paths,=20add=20memory=20s?= =?UTF-8?q?anitizer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cifuzz.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 3b5628a87..97fb612c3 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -2,7 +2,8 @@ name: CIFuzz on: pull_request: paths: - - '**' + - '**.go' + - '.github/workflows/cifuzz.yml' push: branches: [main, master] permissions: @@ -13,26 +14,26 @@ jobs: strategy: fail-fast: false matrix: - sanitizer: [address] + sanitizer: [address, memory] steps: - name: Build Fuzzers (${{ matrix.sanitizer }}) id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@ba0e2e0 # v1.0.0 with: - oss-fuzz-project-name: 'vault' + oss-fuzz-project-name: 'viper' language: go sanitizer: ${{ matrix.sanitizer }} - name: Run Fuzzers (${{ matrix.sanitizer }}) - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@ba0e2e0 # v1.0.0 with: - oss-fuzz-project-name: 'vault' + oss-fuzz-project-name: 'viper' language: go fuzz-seconds: 300 sanitizer: ${{ matrix.sanitizer }} output-sarif: true - name: Upload Sarif - if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@v3 + if: steps.build.outcome == 'success' + uses: github/codeql-action/upload-sarif@601d5b1 # v3.28.15 with: sarif_file: cifuzz-sarif/results.sarif category: fuzz-${{ matrix.sanitizer }} From 24253eb82e2961a3dcb4eb9e018b537ae838baa3 Mon Sep 17 00:00:00 2001 From: can olgun Date: Fri, 12 Jun 2026 14:47:57 +0300 Subject: [PATCH 4/4] =?UTF-8?q?fix(cifuzz):=20address=20Copilot=20review?= =?UTF-8?q?=20=E2=80=94=20checkout,=20permissions,=20Go=20sanitizer,=20SAR?= =?UTF-8?q?IF=20always?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cifuzz.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 97fb612c3..81f2e2923 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -6,25 +6,29 @@ on: - '.github/workflows/cifuzz.yml' push: branches: [main, master] + permissions: contents: read + security-events: write + jobs: fuzzing: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - sanitizer: [address, memory] + sanitizer: [address] steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Build Fuzzers (${{ matrix.sanitizer }}) id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@ba0e2e0 # v1.0.0 + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@ba0e2e0399a10b7b42afb16e7a6c4ccd3ff52431 with: oss-fuzz-project-name: 'viper' language: go sanitizer: ${{ matrix.sanitizer }} - name: Run Fuzzers (${{ matrix.sanitizer }}) - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@ba0e2e0 # v1.0.0 + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@ba0e2e0399a10b7b42afb16e7a6c4ccd3ff52431 with: oss-fuzz-project-name: 'viper' language: go @@ -32,8 +36,8 @@ jobs: sanitizer: ${{ matrix.sanitizer }} output-sarif: true - name: Upload Sarif - if: steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@601d5b1 # v3.28.15 + if: always() && steps.build.outcome == 'success' + uses: github/codeql-action/upload-sarif@601d5b1bcb3e5ef5eea97a6d0dcdbbb8c2b80116 with: sarif_file: cifuzz-sarif/results.sarif category: fuzz-${{ matrix.sanitizer }}