Files
smallwebwaf/internal/coreruleset/forgejo_test.go
clawbot f9bec5146b
check / check (push) Waiting to run
Core Rule Set: Forgejo's searches at a branch or tag, of releases and of job logs pass (closes #350)
`testdata/forgejo-requests.txt` records these Forgejo 17.0.1 searches with the hard values, file names and plain words, and the code search with code and a page opened from each search. `forgejo.conf` lets them through:

- the code search at a branch or tag, where the home page's search box sends its search without a code indexer: `q` skips what it skips on gitea's code search, and `Referer` what it skips after gitea's searches;
- the release search, page and API: `q` skips 930120, 932120, 932160, 932250 and 932260, since it matches tag names;
- the job log search in the API: the same, without 932160.

In `gitea.conf`, `q` on a repository's branches page and an owner's packages page also skips 932160, so `/bin/sh` passes there. `SPEC.md` lists both.

Model: opus-5-5
2026-11-11 15:44:31 +01:00

116 lines
3.8 KiB
Go

package coreruleset_test
import (
"net/http"
"slices"
"testing"
)
// forgejoSearches are Forgejo's searches whose q skips some rules: those of
// a wiki, of releases and of a job's log, from the fifteenth change, and the
// searches for a name, from the eighth. The search of code at a branch or tag
// is not one of them: there, as on gitea's search of code, q lets these
// attacks through.
//
//nolint:gochecknoglobals // a constant cannot be a list
var forgejoSearches = []string{
"/alice/tools/wiki/search",
"/alice/tools/releases",
"/api/v1/repos/alice/tools/releases",
"/api/v1/repos/alice/tools/actions/jobs/1/logs",
"/explore/repos",
"/explore/users",
"/alice/tools/branches",
"/alice/-/packages",
"/api/v1/packages/alice",
"/alice",
}
// forgejoAttacks are an SQL injection, a path traversal, a script injection
// and a shell command after a ";", as query values.
//
//nolint:gochecknoglobals // a constant cannot be a list
var forgejoAttacks = []string{
sqlInjection, "..%2Fcustom%2Fconf%2Fapp.ini", scriptInjection, commandInjection,
}
func TestForgejoAttacksAreRefusedInItsSearches(t *testing.T) {
t.Parallel()
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
defaults, bodies := atDefaults(t), readingBodies(t)
for _, path := range forgejoSearches {
for _, attack := range forgejoAttacks {
if got := inspect(t, defaults, get(path+"?q="+attack)); got.Score < threshold {
t.Errorf("GET %s?q=%s let through: %+v", path, attack, got)
}
// The same field in a form data body.
r := request{http.MethodPost, path, []string{formData}}
if got, _ := inspectBody(t, bodies, r, "q="+attack); got.Score < threshold {
t.Errorf("POST %s q=%s let through: %+v", path, attack, got)
}
}
}
}
func TestForgejoExclusionsAreNarrow(t *testing.T) {
t.Parallel()
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
crs := atDefaults(t)
// /bin/sh and the hard values, which q lets through on the searches of
// the fifteenth change and on those of branches and packages, stay
// refused in q on paths near them and in another field on them.
// /alice/wiki/search would be the code search of a repository named
// wiki, where the eighth change lets q through. Forgejo has no owner
// named api, so the paths under /api/ here are not searches.
for _, target := range []string{
"/alice/tools/wiki/Home?q=",
"/alice/tools/wiki/search/x?q=",
"/api/wiki/search?q=",
"/alice/tools/wiki/search?other=",
"/alice/tools/search/branch?q=",
"/alice/tools/search/commit/0123abc?q=",
"/alice/tools/src/branch/main?q=",
"/api/tools/search/branch/main?q=",
"/alice/tools/search/branch/main?other=",
"/alice/tools/releases/tag/v1.0?q=",
"/alice/tools/releases/latest?q=",
"/api/v1/releases?q=",
"/alice/tools/releases?other=",
"/api/v1/repos/alice/tools/actions/jobs/1?q=",
"/api/v1/repos/alice/tools/actions/runs/1/logs?q=",
"/alice/tools/actions/runs/1/jobs/0/logs?q=",
"/api/v1/repos/alice/tools/actions/jobs/1/logs?other=",
"/alice/tools/branches/x?q=",
"/alice/-/packages/npm/x?q=",
"/alice/tools/branches?other=",
} {
for _, value := range slices.Concat([]string{"%2Fbin%2Fsh"}, hardValues) {
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
t.Errorf("%s%s let through: %+v", target, value, got)
}
}
}
// Referer keeps its rules on a page opened from a path near the code
// search at a branch or tag.
for _, path := range []string{
"/alice/tools/search/branch",
"/alice/tools/search/commit/0123abc",
"/api/tools/search/branch/main",
} {
for _, attack := range []string{scriptTag, injection} {
referer := "Referer: http://example.com" + path + "?q=" + attack
if got := inspect(t, crs, get("/", referer)); got.Score < threshold {
t.Errorf("%s let through: %+v", referer, got)
}
}
}
}