check / check (push) Waiting to run
While SWW_WAF_BODY_LIMIT is set, these body fields skip only the rules that refused them, on their own paths: - the web editor's tree_path, commit_summary and content; - new_branch_name of a branch created on the web or the API; - from and to of a renamed branch, on gitea's and Forgejo's paths; - head of a pull request and message of a file, through the API. None applies when the query has a parameter of the same name. The requests were recorded from gitea 1.27.3 and Forgejo 17.0.1. Model: opus-5-5
217 lines
5.6 KiB
Go
217 lines
5.6 KiB
Go
//go:build recorded
|
|
|
|
// The tests that check the apps' recorded requests in testdata. The recorded
|
|
// tag keeps them out of the test phase's pass with the race detector, and
|
|
// its second pass, without the race detector, runs them.
|
|
|
|
package coreruleset_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// giteaRequests returns the recorded ordinary gitea requests in testdata.
|
|
// Each has the Referer of a page that names the same, as a page opened
|
|
// from it has.
|
|
func giteaRequests(t *testing.T) []request {
|
|
t.Helper()
|
|
|
|
text, err := os.ReadFile(filepath.Join("testdata", "gitea-requests.txt"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var requests []request
|
|
|
|
for line := range strings.Lines(string(text)) {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
target, header, _ := strings.Cut(line, " ")
|
|
|
|
headers := []string{"Referer: http://git.example" + target}
|
|
if header != "" {
|
|
headers = append(headers, header)
|
|
}
|
|
|
|
requests = append(requests, get(target, headers...))
|
|
}
|
|
|
|
return requests
|
|
}
|
|
|
|
func TestOrdinaryGiteaRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
|
|
crs := atDefaults(t)
|
|
|
|
for _, r := range giteaRequests(t) {
|
|
if got := inspect(t, crs, r); got.Score >= threshold {
|
|
t.Errorf("%s %q refused: %+v", r.target, r.headers[1:], got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// recordedRequest is a line of a file of recorded requests in testdata.
|
|
type recordedRequest struct {
|
|
Method string `json:"method"`
|
|
Target string `json:"target"`
|
|
Headers []string `json:"headers"`
|
|
Body string `json:"body"`
|
|
}
|
|
|
|
// recordedRequests returns the recorded ordinary requests in the file name
|
|
// in testdata, each with its body.
|
|
func recordedRequests(t *testing.T, name string) []recordedRequest {
|
|
t.Helper()
|
|
|
|
path := filepath.Join("testdata", name)
|
|
|
|
text, err := os.ReadFile(path) //nolint:gosec // a file in testdata
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var requests []recordedRequest
|
|
|
|
for line := range strings.Lines(string(text)) {
|
|
if strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
var r recordedRequest
|
|
|
|
err := json.Unmarshal([]byte(line), &r)
|
|
if err != nil {
|
|
t.Fatalf("%q: %v", line, err)
|
|
}
|
|
|
|
requests = append(requests, r)
|
|
}
|
|
|
|
return requests
|
|
}
|
|
|
|
// wantRecordedRequestsPass checks that none of the recorded ordinary
|
|
// requests in the file name in testdata is refused, at the defaults and
|
|
// while bodies are read. A request with neither a body nor a Content-Type
|
|
// header is checked only at the defaults: with neither, the set that reads
|
|
// bodies has nothing more to check. Each request runs as its own parallel
|
|
// subtest.
|
|
func wantRecordedRequestsPass(t *testing.T, name string) {
|
|
t.Helper()
|
|
|
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
|
|
defaults, bodies := atDefaults(t), readingBodies(t)
|
|
|
|
for _, recorded := range recordedRequests(t, name) {
|
|
t.Run(recorded.Method+" "+recorded.Target, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r := request{recorded.Method, recorded.Target, recorded.Headers}
|
|
|
|
got, _ := inspectBody(t, defaults, r, recorded.Body)
|
|
if got.Score >= threshold {
|
|
t.Errorf("%s %s refused: %+v", r.method, r.target, got)
|
|
}
|
|
|
|
typed := slices.ContainsFunc(recorded.Headers, func(header string) bool {
|
|
name, _, _ := strings.Cut(header, ":")
|
|
|
|
return strings.EqualFold(name, "Content-Type")
|
|
})
|
|
if recorded.Body == "" && !typed {
|
|
return
|
|
}
|
|
|
|
got, _ = inspectBody(t, bodies, r, recorded.Body)
|
|
if got.Score >= threshold {
|
|
t.Errorf("%s %s %.200q refused while bodies are read: %+v",
|
|
r.method, r.target, recorded.Body, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOrdinaryDrupalRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "drupal-requests.txt")
|
|
}
|
|
|
|
// TestOrdinaryForgejoRequestsPass checks Forgejo's recorded requests at the
|
|
// defaults only. While bodies are read, the Core Rule Set still refuses a new
|
|
// wiki page whose title and text name commands and files, as it does such text
|
|
// in gitea's comments (TestGiteaBodiesTheCoreRuleSetRefuses). The requests
|
|
// that name a file or a branch in a body are checked while bodies are read in
|
|
// TestForgejoNamesInBodiesPass.
|
|
func TestOrdinaryForgejoRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
|
|
crs := atDefaults(t)
|
|
|
|
for _, recorded := range recordedRequests(t, "forgejo-requests.txt") {
|
|
r := request{recorded.Method, recorded.Target, recorded.Headers}
|
|
if got, _ := inspectBody(t, crs, r, recorded.Body); got.Score >= threshold {
|
|
t.Errorf("%s %s refused: %+v", r.method, r.target, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestGiteaNamesInBodiesPass checks gitea's recorded requests that name a
|
|
// file or a branch in a body, while bodies are read too.
|
|
func TestGiteaNamesInBodiesPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "gitea-names-in-bodies.txt")
|
|
}
|
|
|
|
// TestForgejoNamesInBodiesPass is TestGiteaNamesInBodiesPass for Forgejo.
|
|
func TestForgejoNamesInBodiesPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "forgejo-names-in-bodies.txt")
|
|
}
|
|
|
|
func TestOrdinaryMastodonRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "mastodon-requests.txt")
|
|
}
|
|
|
|
func TestOrdinaryMediaWikiRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "mediawiki-requests.txt")
|
|
}
|
|
|
|
func TestOrdinaryNextcloudRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "nextcloud-requests.txt")
|
|
}
|
|
|
|
func TestOrdinaryVaultwardenRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "vaultwarden-requests.txt")
|
|
}
|
|
|
|
func TestOrdinaryWordPressRequestsPass(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wantRecordedRequestsPass(t, "wordpress-requests.txt")
|
|
}
|