mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-09-05 20:07:25 +00:00
Add fuzz target to make (#6828)
Co-authored-by: Claude <claude@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
### Release
|
||||
|
||||
- [ ] Test the latest container images to make sure they work as expected
|
||||
- [ ] Executed `FUZZ_TIME=90s make fuzz` to make sure we stay hardened against user input
|
||||
- [ ] Update `https://ci.woodpecker.org` to the latest version of `next` and verify that it works as expected
|
||||
- [ ] Merge documentation PR (shortly before release)
|
||||
- [ ] Merge the release PR to start the release pipeline
|
||||
|
||||
@@ -99,6 +99,17 @@ steps:
|
||||
when:
|
||||
- path: *when_path
|
||||
|
||||
test-fuzz:
|
||||
depends_on:
|
||||
- vendor
|
||||
image: *golang_image
|
||||
commands:
|
||||
- make fuzz
|
||||
environment:
|
||||
FUZZ_TIME: 2s
|
||||
when:
|
||||
- path: *when_path
|
||||
|
||||
sqlite:
|
||||
depends_on:
|
||||
- vendor
|
||||
|
||||
@@ -209,6 +209,16 @@ test-e2e: ## Test by running yaml config and compare expected result
|
||||
.PHONY: test
|
||||
test: test-agent test-server test-server-datastore test-cli test-lib test-e2e ## Run all tests
|
||||
|
||||
FUZZ_TIME ?= 30s
|
||||
|
||||
fuzz: ## Run all fuzz targets for FUZZ_TIME (default 30s) each
|
||||
@for pkg in $$(grep -rl --include='fuzz_test.go' 'func Fuzz' . | xargs -n1 dirname | sort -u); do \
|
||||
for target in $$(grep -h -o 'func Fuzz[A-Za-z0-9_]*' $$pkg/fuzz_test.go | cut -d' ' -f2); do \
|
||||
echo "fuzzing $$pkg $$target"; \
|
||||
go test -tags 'test $(TAGS)' -run 'XXX_NONE' -fuzz "^$$target"'$$' -fuzztime $(FUZZ_TIME) "./$$pkg" || exit 1; \
|
||||
done; \
|
||||
done
|
||||
|
||||
##@ Build
|
||||
|
||||
build-ui: ## Build UI
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 metadata_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
|
||||
)
|
||||
|
||||
// FuzzEnvVarSubst exercises the envsubst evaluation of untrusted yaml
|
||||
// (substitution expressions are attacker controlled) and checks that it
|
||||
// never panics. Values containing newlines take the quoting code path.
|
||||
func FuzzEnvVarSubst(f *testing.F) {
|
||||
f.Add("image: golang:${GO_VERSION}", "1.26")
|
||||
f.Add("cmd: ${CI_COMMIT_MESSAGE}", "line1\nline2")
|
||||
f.Add("x: ${VAR=default}", "")
|
||||
f.Add("y: ${VAR/./-}", "a.b.c")
|
||||
f.Add("z: ${VAR:0:3}", "abcdef")
|
||||
|
||||
f.Fuzz(func(_ *testing.T, yaml, value string) {
|
||||
environ := map[string]string{
|
||||
"GO_VERSION": value,
|
||||
"CI_COMMIT_MESSAGE": value,
|
||||
"VAR": value,
|
||||
}
|
||||
_, _ = metadata.EnvVarSubst(yaml, environ)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 settings
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
// FuzzParamsToEnv drives the reflection and recursion heavy plugin settings
|
||||
// conversion with untrusted structures with secrets decoded from yaml and checks that it never panics.
|
||||
func FuzzParamsToEnv(f *testing.F) {
|
||||
f.Add("string: stringz\nint: 1\nfloat: 1.2\nbool: true")
|
||||
f.Add("slice: [1, 2, 3]\nmap: { hello: world }")
|
||||
f.Add("my_secret:\n from_secret: secret_token")
|
||||
f.Add("nested:\n - a:\n from_secret: tok\n - b: [x, {c: d}]")
|
||||
|
||||
getSecret := func(name string) (string, error) {
|
||||
return "secret_" + name, nil
|
||||
}
|
||||
|
||||
f.Fuzz(func(_ *testing.T, data string) {
|
||||
from := map[string]any{}
|
||||
if err := yaml.Unmarshal([]byte(data), &from); err != nil {
|
||||
return
|
||||
}
|
||||
to := map[string]string{}
|
||||
_ = ParamsToEnv(from, to, "PLUGIN_", true, getSecret, nil)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 constraint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
// FuzzListMatch exercises doublestar glob matching with untrusted patterns
|
||||
// and values from pipeline `when` constraints and checks that it never
|
||||
// panics.
|
||||
func FuzzListMatch(f *testing.F) {
|
||||
f.Add("feat/**", "feat/a/b", "release/*")
|
||||
f.Add("{main,dev}", "main", "")
|
||||
f.Add("[a-z]*", "abc", "**")
|
||||
f.Add(`\{esc`, "{esc", "*")
|
||||
|
||||
f.Fuzz(func(_ *testing.T, include, value, exclude string) {
|
||||
c := List{
|
||||
Include: []string{include},
|
||||
Exclude: []string{exclude},
|
||||
}
|
||||
_ = c.Match(value)
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzWhenUnmarshal exercises the custom yaml unmarshalers of the `when`
|
||||
// constraint tree with untrusted yaml and checks that they never panic.
|
||||
func FuzzWhenUnmarshal(f *testing.F) {
|
||||
f.Add("event: push")
|
||||
f.Add("- event: [push, tag]\n branch: main")
|
||||
f.Add("evaluate: 'CI_COMMIT_MESSAGE contains \"x\"'")
|
||||
f.Add("path:\n include: ['src/**']\n on_empty: true")
|
||||
|
||||
f.Fuzz(func(_ *testing.T, data string) {
|
||||
when := When{}
|
||||
_ = yaml.Unmarshal([]byte(data), &when)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParseBytes exercises the whole workflow yaml parsing including all
|
||||
// custom UnmarshalYAML implementations (constraints, container lists, string-or-slice types and so on)
|
||||
// with untrusted input. The property checked is that parsing never panics.
|
||||
func FuzzParseBytes(f *testing.F) {
|
||||
f.Add([]byte(sampleYaml))
|
||||
f.Add([]byte(simpleYamlAnchors))
|
||||
f.Add([]byte("steps: { a: { image: alpine, commands: [ls] } }"))
|
||||
f.Add([]byte("when:\n - event: push\n branch: [main, 'feat/**']"))
|
||||
f.Add([]byte("matrix:\n GO: [1, 2]\nsteps:\n a:\n image: golang:${GO}"))
|
||||
f.Add([]byte("steps:\n a:\n image: alpine\n settings:\n s:\n from_secret: token"))
|
||||
|
||||
f.Fuzz(func(_ *testing.T, data []byte) {
|
||||
_, _ = ParseBytes(data)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 schema_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter/schema"
|
||||
)
|
||||
|
||||
// FuzzLintString feeds untrusted yaml through the json-schema based linter
|
||||
// (yaml -> json conversion + gojsonschema validation) and checks that it
|
||||
// never panics.
|
||||
func FuzzLintString(f *testing.F) {
|
||||
f.Add("steps: { a: { image: alpine, commands: [ls] } }")
|
||||
f.Add("when:\n event: push\nsteps:\n a:\n image: alpine")
|
||||
f.Add("skip_clone: true\nsteps: []")
|
||||
f.Add("{}")
|
||||
f.Add("- 1\n- 2")
|
||||
|
||||
f.Fuzz(func(_ *testing.T, data string) {
|
||||
_, _ = schema.LintString(data)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 matrix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParse exercises matrix axis parsing and permutation calculation with
|
||||
// untrusted yaml and checks that it never panics.
|
||||
func FuzzParse(f *testing.F) {
|
||||
f.Add([]byte("matrix:\n GO: [1, 2]\n OS: [linux, windows]"))
|
||||
f.Add([]byte("matrix:\n include:\n - GO: 1\n OS: linux"))
|
||||
f.Add([]byte("matrix: {}"))
|
||||
f.Add([]byte("matrix:\n A: [x]"))
|
||||
|
||||
f.Fuzz(func(_ *testing.T, data []byte) {
|
||||
_, _ = Parse(data)
|
||||
})
|
||||
}
|
||||
@@ -74,6 +74,11 @@ func calc(matrix Matrix) []Axis {
|
||||
var perm int
|
||||
var tags []string
|
||||
for k, v := range matrix {
|
||||
// an axis without values contributes no permutations and would
|
||||
// cause a division by zero below
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
perm *= len(v)
|
||||
if perm == 0 {
|
||||
perm = len(v)
|
||||
|
||||
@@ -37,6 +37,14 @@ func TestMatrixEmpty(t *testing.T) {
|
||||
assert.Empty(t, axis)
|
||||
}
|
||||
|
||||
func TestMatrixEmptyAxis(t *testing.T) {
|
||||
axis, err := ParseString("matrix:\n A: [a1, a2]\n EMPTY:")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, axis, 2)
|
||||
assert.Equal(t, "a1", axis[0]["A"])
|
||||
assert.Equal(t, "a2", axis[1]["A"])
|
||||
}
|
||||
|
||||
func TestMatrixIncluded(t *testing.T) {
|
||||
axis, err := ParseString(fakeMatrixInclude)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzImageMatching exercises container image reference normalization and
|
||||
// matching (privileged plugin matching, registry hostname matching) with
|
||||
// untrusted image names from pipeline configs and checks that it never
|
||||
// panics.
|
||||
func FuzzImageMatching(f *testing.F) {
|
||||
f.Add("golang", "docker.io/library/golang:latest", "docker.io")
|
||||
f.Add("codeberg.org/woodpecker-plugins/docker-buildx", "woodpecker-plugins/docker-buildx", "codeberg.org")
|
||||
f.Add("image:tag@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "image", "index.docker.io")
|
||||
f.Add("REGISTRY.example/Repo/Image:v1", "*", "registry.example")
|
||||
|
||||
f.Fuzz(func(_ *testing.T, from, to, hostname string) {
|
||||
_, _ = ParseNamed(from)
|
||||
_ = MatchImage(from, to)
|
||||
_ = MatchImageDynamic(from, to)
|
||||
_ = MatchHostname(from, hostname)
|
||||
})
|
||||
}
|
||||
+6
-1
@@ -82,7 +82,12 @@ func CalcNewNext(schedule, tzLoc string, now time.Time) (time.Time, error) {
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("cron parse schedule: %w", err)
|
||||
}
|
||||
return c.Next(now), nil
|
||||
|
||||
next := c.Next(now)
|
||||
if next.IsZero() {
|
||||
return time.Time{}, fmt.Errorf("cron schedule yields no future execution time")
|
||||
}
|
||||
return next, nil
|
||||
}
|
||||
|
||||
func runCron(ctx context.Context, store store.Store, cron *model.Cron, now time.Time) error {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 cron
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FuzzCalcNewNext feeds untrusted, user supplied cron schedule and timezone
|
||||
// strings into the schedule parser. The property checked is that parsing
|
||||
// never panics and that a successfully calculated execution time is never in
|
||||
// the past.
|
||||
func FuzzCalcNewNext(f *testing.F) {
|
||||
f.Add("@daily", "")
|
||||
f.Add("*/5 * * * *", "Europe/Berlin")
|
||||
f.Add("0 0 1 1 *", "UTC")
|
||||
f.Add("60 25 * * *", "Not/AZone")
|
||||
|
||||
now := time.Unix(1257894000, 0)
|
||||
|
||||
f.Fuzz(func(t *testing.T, schedule, tzLoc string) {
|
||||
next, err := CalcNewNext(schedule, tzLoc, now)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if next.Before(now) {
|
||||
t.Fatalf("next execution %v is before now %v for schedule %q tz %q", next, now, schedule, tzLoc)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 forgejo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParseHooks feeds untrusted webhook payloads into every payload level
|
||||
// hook parser (push, created, pull request and release). The property checked
|
||||
// is that parsing never panics, no matter how malformed the payload is.
|
||||
func FuzzParseHooks(f *testing.F) {
|
||||
fixtures, err := filepath.Glob(filepath.Join("fixtures", "*.json"))
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
for _, fixture := range fixtures {
|
||||
data, err := os.ReadFile(fixture)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
f.Add(data)
|
||||
}
|
||||
|
||||
f.Fuzz(func(_ *testing.T, data []byte) {
|
||||
_, _, _ = parsePushHook(bytes.NewReader(data))
|
||||
_, _, _ = parseCreatedHook(bytes.NewReader(data))
|
||||
_, _, _ = parsePullRequestHook(bytes.NewReader(data))
|
||||
_, _, _ = parseReleaseHook(bytes.NewReader(data))
|
||||
})
|
||||
}
|
||||
@@ -53,6 +53,9 @@ func toRepo(from *forgejo.Repository) *model.Repo {
|
||||
|
||||
// toPerm converts a Forgejo permission to a Woodpecker permission.
|
||||
func toPerm(from *forgejo.Permission) *model.Perm {
|
||||
if from == nil {
|
||||
return &model.Perm{}
|
||||
}
|
||||
return &model.Perm{
|
||||
Pull: from.Pull,
|
||||
Push: from.Push,
|
||||
|
||||
@@ -93,6 +93,9 @@ func parsePushHook(payload io.Reader) (repo *model.Repo, pipeline *model.Pipelin
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := push.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// ignore push events for tags
|
||||
if strings.HasPrefix(push.Ref, "refs/tags/") {
|
||||
@@ -116,6 +119,9 @@ func parseCreatedHook(payload io.Reader) (repo *model.Repo, pipeline *model.Pipe
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := push.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if push.RefType != refTag {
|
||||
return nil, nil, nil
|
||||
@@ -137,6 +143,9 @@ func parsePullRequestHook(payload io.Reader) (*model.Repo, *model.Pipeline, erro
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := pr.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Only trigger pipelines for supported event types
|
||||
if !supportedAction(pr.Action) {
|
||||
@@ -173,6 +182,9 @@ func parseReleaseHook(payload io.Reader) (*model.Repo, *model.Pipeline, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := release.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repo = toRepo(release.Repo)
|
||||
pipeline = pipelineFromRelease(release)
|
||||
|
||||
@@ -778,3 +778,40 @@ func TestForgejoParser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIncompleteHookPayloads(t *testing.T) {
|
||||
incomplete := []string{
|
||||
`{}`,
|
||||
`{"repository": {}}`,
|
||||
`{"repository": {"full_name": "noslash"}, "sender": {}}`,
|
||||
}
|
||||
for _, payload := range incomplete {
|
||||
t.Run(payload, func(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
_, _, err := parsePushHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parseCreatedHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parsePullRequestHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parseReleaseHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
incompletePullRequest := []string{
|
||||
`{"repository": {"full_name": "a/b", "owner": {}}, "sender": {}, "action": "opened", "pull_request": {}}`,
|
||||
`{"repository": {"full_name": "a/b", "owner": {}}, "sender": {}, "action": "opened", "pull_request": {"user": {}}}`,
|
||||
}
|
||||
for _, payload := range incompletePullRequest {
|
||||
t.Run(payload, func(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
_, _, err := parsePullRequestHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parseReleaseHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
|
||||
package forgejo
|
||||
|
||||
import "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3"
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3"
|
||||
)
|
||||
|
||||
type pushHook struct {
|
||||
Sha string `json:"sha"`
|
||||
@@ -49,3 +54,29 @@ type releaseHook struct {
|
||||
Sender *forgejo.User `json:"sender"`
|
||||
Release *forgejo.Release
|
||||
}
|
||||
|
||||
var errIncompleteHook = errors.New("incomplete webhook payload")
|
||||
|
||||
// validate checks that all objects dereferenced during hook conversion are
|
||||
// present, so malformed payloads are rejected instead of causing panics.
|
||||
func (h *pushHook) validate() error {
|
||||
if h.Repo == nil || h.Repo.Owner == nil || !strings.Contains(h.Repo.FullName, "/") || h.Sender == nil {
|
||||
return errIncompleteHook
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *pullRequestHook) validate() error {
|
||||
if h.Repo == nil || h.Repo.Owner == nil || !strings.Contains(h.Repo.FullName, "/") || h.Sender == nil ||
|
||||
h.PullRequest == nil || h.PullRequest.Poster == nil || h.PullRequest.Head == nil || h.PullRequest.Base == nil {
|
||||
return errIncompleteHook
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *releaseHook) validate() error {
|
||||
if h.Repo == nil || h.Repo.Owner == nil || !strings.Contains(h.Repo.FullName, "/") || h.Sender == nil || h.Release == nil {
|
||||
return errIncompleteHook
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParseHooks feeds untrusted webhook payloads into every payload level
|
||||
// hook parser (push, created, pull request and release). The property checked
|
||||
// is that parsing never panics, no matter how malformed the payload is.
|
||||
func FuzzParseHooks(f *testing.F) {
|
||||
fixtures, err := filepath.Glob(filepath.Join("fixtures", "*.json"))
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
for _, fixture := range fixtures {
|
||||
data, err := os.ReadFile(fixture)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
f.Add(data)
|
||||
}
|
||||
|
||||
f.Fuzz(func(_ *testing.T, data []byte) {
|
||||
_, _, _ = parsePushHook(bytes.NewReader(data))
|
||||
_, _, _ = parseCreatedHook(bytes.NewReader(data))
|
||||
_, _, _ = parsePullRequestHook(bytes.NewReader(data))
|
||||
_, _, _ = parseReleaseHook(bytes.NewReader(data))
|
||||
})
|
||||
}
|
||||
@@ -54,6 +54,9 @@ func toRepo(from *gitea.Repository) *model.Repo {
|
||||
|
||||
// toPerm converts a Gitea permission to a Woodpecker permission.
|
||||
func toPerm(from *gitea.Permission) *model.Perm {
|
||||
if from == nil {
|
||||
return &model.Perm{}
|
||||
}
|
||||
return &model.Perm{
|
||||
Pull: from.Pull,
|
||||
Push: from.Push,
|
||||
|
||||
@@ -95,6 +95,9 @@ func parsePushHook(payload io.Reader) (repo *model.Repo, pipeline *model.Pipelin
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := push.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// ignore push events for tags
|
||||
if strings.HasPrefix(push.Ref, "refs/tags/") {
|
||||
@@ -118,6 +121,9 @@ func parseCreatedHook(payload io.Reader) (repo *model.Repo, pipeline *model.Pipe
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := push.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if push.RefType != refTag {
|
||||
return nil, nil, nil
|
||||
@@ -139,6 +145,9 @@ func parsePullRequestHook(payload io.Reader) (*model.Repo, *model.Pipeline, erro
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := pr.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if pr.PullRequest == nil {
|
||||
// this should never have happened but it did - so we check
|
||||
@@ -180,6 +189,9 @@ func parseReleaseHook(payload io.Reader) (*model.Repo, *model.Pipeline, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := release.validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repo = toRepo(release.Repo)
|
||||
pipeline = pipelineFromRelease(release)
|
||||
|
||||
@@ -703,3 +703,40 @@ func Test_parsePullRequestDraft(t *testing.T) {
|
||||
assert.True(t, p.PullRequestDraft)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIncompleteHookPayloads(t *testing.T) {
|
||||
incomplete := []string{
|
||||
`{}`,
|
||||
`{"repository": {}}`,
|
||||
`{"repository": {"full_name": "noslash"}, "sender": {}}`,
|
||||
}
|
||||
for _, payload := range incomplete {
|
||||
t.Run(payload, func(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
_, _, err := parsePushHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parseCreatedHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parsePullRequestHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parseReleaseHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
incompletePullRequest := []string{
|
||||
`{"repository": {"full_name": "a/b", "owner": {}}, "sender": {}, "action": "opened", "pull_request": {}}`,
|
||||
`{"repository": {"full_name": "a/b", "owner": {}}, "sender": {}, "action": "opened", "pull_request": {"user": {}}}`,
|
||||
}
|
||||
for _, payload := range incompletePullRequest {
|
||||
t.Run(payload, func(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
_, _, err := parsePullRequestHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
_, _, err = parseReleaseHook(bytes.NewBufferString(payload))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
|
||||
package gitea
|
||||
|
||||
import "code.gitea.io/sdk/gitea"
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
type pushHook struct {
|
||||
Sha string `json:"sha"`
|
||||
@@ -50,3 +55,29 @@ type releaseHook struct {
|
||||
Sender *gitea.User `json:"sender"`
|
||||
Release *gitea.Release
|
||||
}
|
||||
|
||||
var errIncompleteHook = errors.New("incomplete webhook payload")
|
||||
|
||||
// validate checks that all objects dereferenced during hook conversion are
|
||||
// present, so malformed payloads are rejected instead of causing panics.
|
||||
func (h *pushHook) validate() error {
|
||||
if h.Repo == nil || h.Repo.Owner == nil || !strings.Contains(h.Repo.FullName, "/") || h.Sender == nil {
|
||||
return errIncompleteHook
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *pullRequestHook) validate() error {
|
||||
if h.Repo == nil || h.Repo.Owner == nil || !strings.Contains(h.Repo.FullName, "/") || h.Sender == nil ||
|
||||
h.PullRequest == nil || h.PullRequest.Poster == nil || h.PullRequest.Head == nil || h.PullRequest.Base == nil {
|
||||
return errIncompleteHook
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *releaseHook) validate() error {
|
||||
if h.Repo == nil || h.Repo.Owner == nil || !strings.Contains(h.Repo.FullName, "/") || h.Sender == nil || h.Release == nil {
|
||||
return errIncompleteHook
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func convertRepoHook(eventRepo *github.PushEventRepository) *model.Repo {
|
||||
func convertLabels(from []*github.Label) []string {
|
||||
labels := make([]string, len(from))
|
||||
for i, label := range from {
|
||||
labels[i] = *label.Name
|
||||
labels[i] = label.GetName()
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 github
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParseHookPayload feeds untrusted webhook payloads of arbitrary event
|
||||
// types into the payload parser. The property checked is that parsing never
|
||||
// panics, no matter how malformed the payload or event type is.
|
||||
func FuzzParseHookPayload(f *testing.F) {
|
||||
fixtures, err := filepath.Glob(filepath.Join("fixtures", "*.json"))
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
for _, fixture := range fixtures {
|
||||
data, err := os.ReadFile(fixture)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
// derive the event type from the fixture name (HookPush.json -> push)
|
||||
webhookType := "push"
|
||||
name := filepath.Base(fixture)
|
||||
switch {
|
||||
case strings.HasPrefix(name, "HookPullRequest"):
|
||||
webhookType = "pull_request"
|
||||
case strings.HasPrefix(name, "HookDeploy"):
|
||||
webhookType = "deployment"
|
||||
case strings.HasPrefix(name, "HookRelease"):
|
||||
webhookType = "release"
|
||||
}
|
||||
f.Add(webhookType, data, true)
|
||||
}
|
||||
|
||||
f.Fuzz(func(_ *testing.T, webhookType string, raw []byte, merge bool) {
|
||||
_, _, _, _, _, _ = parseHookPayload(webhookType, raw, merge)
|
||||
})
|
||||
}
|
||||
@@ -56,7 +56,7 @@ const (
|
||||
|
||||
// parseHook parses a GitHub hook from an http.Request request and returns
|
||||
// Repo and Pipeline detail. If a hook type is unsupported nil values are returned.
|
||||
func parseHook(r *http.Request, merge bool) (_ *github.PullRequest, _ *model.Repo, _ *model.Pipeline, currCommit, prevCommit string, _ error) {
|
||||
func parseHook(r *http.Request, merge bool) (*github.PullRequest, *model.Repo, *model.Pipeline, string, string, error) {
|
||||
var reader io.Reader = r.Body
|
||||
|
||||
if payload := r.FormValue(hookField); payload != "" {
|
||||
@@ -68,7 +68,14 @@ func parseHook(r *http.Request, merge bool) (_ *github.PullRequest, _ *model.Rep
|
||||
return nil, nil, nil, "", "", err
|
||||
}
|
||||
|
||||
payload, err := github.ParseWebHook(github.WebHookType(r), raw)
|
||||
return parseHookPayload(github.WebHookType(r), raw, merge)
|
||||
}
|
||||
|
||||
// parseHookPayload parses a raw GitHub hook payload of the given webhook type
|
||||
// and returns Repo and Pipeline detail. If a hook type is unsupported nil
|
||||
// values are returned.
|
||||
func parseHookPayload(webhookType string, raw []byte, merge bool) (_ *github.PullRequest, _ *model.Repo, _ *model.Pipeline, currCommit, prevCommit string, _ error) {
|
||||
payload, err := github.ParseWebHook(webhookType, raw)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", "", err
|
||||
}
|
||||
@@ -216,7 +223,7 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque
|
||||
hook.GetPullRequest().GetHead().GetRef(),
|
||||
hook.GetPullRequest().GetBase().GetRef(),
|
||||
),
|
||||
PullRequestLabels: convertLabels(hook.GetPullRequest().Labels),
|
||||
PullRequestLabels: convertLabels(hook.GetPullRequest().GetLabels()),
|
||||
PullRequestMilestone: hook.GetPullRequest().GetMilestone().GetTitle(),
|
||||
PullRequestDraft: hook.GetPullRequest().GetDraft(),
|
||||
FromFork: fromFork,
|
||||
|
||||
@@ -179,6 +179,8 @@ func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (mergeI
|
||||
return 0, 0, nil, nil, fmt.Errorf("target key expected in merge request hook")
|
||||
case source == nil:
|
||||
return 0, 0, nil, nil, fmt.Errorf("source key expected in merge request hook")
|
||||
case hook.User == nil:
|
||||
return 0, 0, nil, nil, fmt.Errorf("user key expected in merge request hook")
|
||||
}
|
||||
|
||||
if target.PathWithNamespace != "" {
|
||||
@@ -285,10 +287,15 @@ func convertPushHook(hook *gitlab.PushEvent) (*model.Repo, *model.Pipeline, erro
|
||||
// assume a capacity of 4 changed files per commit
|
||||
files := make([]string, 0, len(hook.Commits)*4)
|
||||
for _, cm := range hook.Commits {
|
||||
if cm == nil {
|
||||
continue
|
||||
}
|
||||
if hook.After == cm.ID {
|
||||
pipeline.Email = cm.Author.Email
|
||||
pipeline.Message = cm.Message
|
||||
pipeline.Timestamp = cm.Timestamp.Unix()
|
||||
if cm.Timestamp != nil {
|
||||
pipeline.Timestamp = cm.Timestamp.Unix()
|
||||
}
|
||||
if len(pipeline.Email) != 0 {
|
||||
pipeline.Avatar = getUserAvatar(pipeline.Email)
|
||||
}
|
||||
@@ -342,10 +349,15 @@ func convertTagHook(hook *gitlab.TagEvent) (*model.Repo, *model.Pipeline, string
|
||||
pipeline.ForgeURL = fmt.Sprintf("%s/-/tags/%s", repo.ForgeURL, pipeline.TagTitle)
|
||||
|
||||
for _, cm := range hook.Commits {
|
||||
if cm == nil {
|
||||
continue
|
||||
}
|
||||
if hook.After == cm.ID {
|
||||
pipeline.Email = cm.Author.Email
|
||||
pipeline.Message = cm.Message
|
||||
pipeline.Timestamp = cm.Timestamp.Unix()
|
||||
if cm.Timestamp != nil {
|
||||
pipeline.Timestamp = cm.Timestamp.Unix()
|
||||
}
|
||||
if len(pipeline.Email) != 0 {
|
||||
pipeline.Avatar = getUserAvatar(pipeline.Email)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 gitlab
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
gitlab "gitlab.com/gitlab-org/api/client-go/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/forge/gitlab/fixtures"
|
||||
)
|
||||
|
||||
// FuzzParseWebhook feeds untrusted webhook payloads of arbitrary event types
|
||||
// into the webhook decoding and the pure hook conversion functions. The
|
||||
// property checked is that neither decoding nor conversion ever panics.
|
||||
func FuzzParseWebhook(f *testing.F) {
|
||||
f.Add("Push Hook", fixtures.HookPush)
|
||||
f.Add("Tag Push Hook", fixtures.HookTag)
|
||||
f.Add("Merge Request Hook", fixtures.HookPullRequestOpened)
|
||||
f.Add("Merge Request Hook", fixtures.HookPullRequestMerged)
|
||||
f.Add("Release Hook", fixtures.WebhookReleaseBody)
|
||||
|
||||
f.Fuzz(func(_ *testing.T, eventType string, payload []byte) {
|
||||
parsed, err := gitlab.ParseWebhook(gitlab.EventType(eventType), payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch event := parsed.(type) {
|
||||
case *gitlab.MergeEvent:
|
||||
_, _, _, _, _ = convertMergeRequestHook(event, &http.Request{})
|
||||
case *gitlab.PushEvent:
|
||||
_, _, _ = convertPushHook(event)
|
||||
case *gitlab.TagEvent:
|
||||
_, _, _, _ = convertTagHook(event)
|
||||
case *gitlab.ReleaseEvent:
|
||||
_, _, _ = convertReleaseHook(event)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParseRepo feeds untrusted repository full names into the owner/name
|
||||
// splitter. The property checked is that parsing never panics and that a
|
||||
// successful parse returns non-empty owner and name.
|
||||
func FuzzParseRepo(f *testing.F) {
|
||||
f.Add("octocat/hello-world")
|
||||
f.Add("owner/group/repo")
|
||||
f.Add("/missing-owner")
|
||||
|
||||
f.Fuzz(func(t *testing.T, str string) {
|
||||
user, repo, err := ParseRepo(str)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if user == "" || repo == "" {
|
||||
t.Fatalf("ParseRepo(%q) returned empty owner (%q) or name (%q) without error", str, user, repo)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// 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 token
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParse feeds untrusted raw JWT strings into the token parser. The
|
||||
// property checked is that parsing never panics and that a returned token
|
||||
// always has one of the allowed types.
|
||||
func FuzzParse(f *testing.F) {
|
||||
const secret = "fuzz-secret"
|
||||
|
||||
allowedTypes := []Type{UserToken, SessToken, HookToken, CsrfToken, AgentToken, OAuthStateToken}
|
||||
|
||||
// seed with a validly signed token for each type so the fuzzer can reach
|
||||
// the code paths behind signature verification
|
||||
for _, tokenType := range allowedTypes {
|
||||
signed, err := New(tokenType).Sign(secret)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
f.Add(signed)
|
||||
}
|
||||
f.Add("eyJhbGciOiJIUzI1NiJ9.e30.")
|
||||
f.Add("not.a.jwt")
|
||||
|
||||
f.Fuzz(func(t *testing.T, raw string) {
|
||||
parsed, err := Parse(allowedTypes, raw, func(*Token) (string, error) {
|
||||
return secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, allowed := range allowedTypes {
|
||||
if parsed.Type == allowed {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("parsed token has disallowed type %q", parsed.Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user