Remove everything specific to a Gitea deployment (closes #209)
check / check (push) Successful in 3m20s

Delete the Gitea compose setup, its document, its two scripts and their
make targets. Drop the Core Rule Set exclusions written for Gitea: the
ten query parameters skipped by rules 930120, 932160 and 932260, and the
gitea_flash and redirect_to cookies. The remaining changes are
renumbered, five in all. Documents and tests use neutral examples.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-07 01:57:48 +00:00
1 parent f8676debda
commit 1c70104677
22 files changed
+142 -1419

No files matched your search

+10 -10
View File
@@ -39,7 +39,7 @@ const (
ntfyURL = "https://ntfy.example/smallwebwaf-alerts"
ntfyToken = "tk_0123456789abcdefghijklmnopq"
// instance is the instance name every alert gives.
instance = "fsn1app1/gitea"
instance = "fsn1app1/blog"
// started is when each test starts, as an alert gives it, and
// anHourOn an hour later.
started = "2000-01-01T00:00:00Z"
@@ -965,8 +965,8 @@ func TestEachDestinationIsSentOnlyTheAlertsWaitingForIt(t *testing.T) {
synctest.Wait()
wantBodies(t, standIns[alerts.DestinationSlack],
`{"text":"*fsn1app1/gitea: file_error*\nsecond"}`,
`{"text":"*fsn1app1/gitea: file_error*\nthird"}`)
`{"text":"*fsn1app1/blog: file_error*\nsecond"}`,
`{"text":"*fsn1app1/blog: file_error*\nthird"}`)
wantBodies(t, standIns[alerts.DestinationNtfy], "fourth")
// alerts.json then lists Slack and ntfy alone, with no alert waiting.
@@ -1265,7 +1265,7 @@ func anAlertForEachEvent() []eventMessage {
Reason: "requests per hour over the limit of 10000",
Detail: map[string]any{"mode": "observe"},
},
"fsn1app1/gitea: ban", "default no_entry",
"fsn1app1/blog: ban", "default no_entry",
"requests per hour over the limit of 10000\nclient: 203.0.113.9\n" +
"netblock: 203.0.113.0/24\ncountry: DE\nmode: observe",
},
@@ -1275,7 +1275,7 @@ func anAlertForEachEvent() []eventMessage {
Netblock: netip.MustParsePrefix("198.51.100.7/32"), Country: "FR",
Reason: "matched the rule env-file",
},
"fsn1app1/gitea: permanent_ban", "high no_entry",
"fsn1app1/blog: permanent_ban", "high no_entry",
"matched the rule env-file\nclient: 198.51.100.7\n" +
"netblock: 198.51.100.7/32\ncountry: FR",
},
@@ -1286,7 +1286,7 @@ func anAlertForEachEvent() []eventMessage {
Reason: "refused by the Core Rule Set",
Host: "app.example",
},
"fsn1app1/gitea: waf_block", "default shield",
"fsn1app1/blog: waf_block", "default shield",
"refused by the Core Rule Set\nhost: app.example\nclient: 192.0.2.1\n" +
"netblock: 192.0.2.1/32",
},
@@ -1295,7 +1295,7 @@ func anAlertForEachEvent() []eventMessage {
Event: alerts.EventAnomaly, Netblock: netip.MustParsePrefix("192.0.2.0/24"),
Reason: "requests per minute over the threshold of 5000",
},
"fsn1app1/gitea: anomaly", "high chart_with_upwards_trend",
"fsn1app1/blog: anomaly", "high chart_with_upwards_trend",
"requests per minute over the threshold of 5000\nnetblock: 192.0.2.0/24",
},
{
@@ -1304,7 +1304,7 @@ func anAlertForEachEvent() []eventMessage {
Netblock: netip.MustParsePrefix("192.0.2.2/32"),
Reason: "listed by a DNS blocklist",
},
"fsn1app1/gitea: reputation_hit", "low label",
"fsn1app1/blog: reputation_hit", "low label",
"listed by a DNS blocklist\nclient: 192.0.2.2\nnetblock: 192.0.2.2/32",
},
{
@@ -1312,7 +1312,7 @@ func anAlertForEachEvent() []eventMessage {
Event: alerts.EventSourceFailure, Reason: "asking GeoJS failed",
Detail: map[string]any{"source": "geojs"},
},
"fsn1app1/gitea: source_failure", "high warning",
"fsn1app1/blog: source_failure", "high warning",
"asking GeoJS failed\nsource: geojs",
},
{
@@ -1323,7 +1323,7 @@ func anAlertForEachEvent() []eventMessage {
"file": "/etc/smallwebwaf/rules.d/50-app.rules", "error": "line 2: no action",
},
},
"fsn1app1/gitea: file_error", "high warning",
"fsn1app1/blog: file_error", "high warning",
"a rule file has an error, and the rules stay as they were\n" +
"file: /etc/smallwebwaf/rules.d/50-app.rules\nerror: line 2: no action",
},
+1 -1
View File
@@ -409,7 +409,7 @@ var (
errNoCertificate = errors.New("holds no PEM certificate")
errNotFacility = errors.New("is not a syslog facility such as local0 or daemon")
errNotAppName = errors.New(
"is not 1 to 48 printable ASCII characters without a space, such as gitea")
"is not 1 to 48 printable ASCII characters without a space, such as blog")
errSetTwice = errors.New("set only one of them")
errNotWebhookURL = errors.New(
"is not an http or https URL without a user or a fragment, " +
+6 -6
View File
@@ -197,7 +197,7 @@ const (
// instance is an SWWAF_INSTANCE_NAME that is a valid app name too, and
// remoteURL an SWWAF_LOG_REMOTE_URL, for the tests that send the lines.
const (
instance = "fsn1app1/gitea"
instance = "fsn1app1/blog"
remoteURL = "syslog+udp://192.0.2.1:514"
)
@@ -650,11 +650,11 @@ func TestInstanceNameAndLoggedHeadersAsSet(t *testing.T) {
t.Parallel()
cfg := fromEnvironment(t, environment{
instanceName: "fsn1app1/gitea",
instanceName: "fsn1app1/blog",
logRequestHeaders: " Accept , X-Custom",
})
if cfg.InstanceName != "fsn1app1/gitea" ||
if cfg.InstanceName != "fsn1app1/blog" ||
!slices.Equal(cfg.LogRequestHeaders, []string{"accept", "x-custom"}) {
t.Errorf("%s is %q and %s gives %v", instanceName, cfg.InstanceName,
logRequestHeaders, cfg.LogRequestHeaders)
@@ -797,7 +797,7 @@ func TestInstanceNameNotAnAppNameStopsTheStartOnlyWhileSending(t *testing.T) {
want := logRemoteAppName + `: is unset, and ` + instanceName +
` "fsn1 app1", its default, is not 1 to 48 printable ASCII characters ` +
`without a space, such as gitea`
`without a space, such as blog`
if err == nil || err.Error() != want {
t.Errorf("error %v, want %s", err, want)
}
@@ -826,7 +826,7 @@ func TestAppNameSetStopsTheStartWhileSending(t *testing.T) {
}.lookupEnv)
want := logRemoteAppName + `: "my app" is not 1 to 48 printable ASCII ` +
`characters without a space, such as gitea`
`characters without a space, such as blog`
if err == nil || err.Error() != want {
t.Errorf("error %v, want %s", err, want)
}
@@ -2166,7 +2166,7 @@ func TestSettingFromFileIsCheckedAsTheSettingItself(t *testing.T) {
}.lookupEnv)
want = logRemoteAppName + `: "my app" is not 1 to 48 printable ASCII ` +
`characters without a space, such as gitea`
`characters without a space, such as blog`
if err == nil || err.Error() != want {
t.Errorf("error %v, want %s", err, want)
}
+4 -4
View File
@@ -44,7 +44,7 @@ import (
const (
// started is the time a record made as a test starts gives.
started = "2000-01-01T00:00:00.000Z"
appName = "fsn1app1/gitea"
appName = "fsn1app1/blog"
// local0 is the number of the default facility, and local0Info the
// priority of its records.
local0 = 16
@@ -94,7 +94,7 @@ func TestRecordsOverTCPAreOctetCountedWithTheirFacility(t *testing.T) {
endpoint := listen(t)
endpointParams := params(remotelog.SchemeTCP, endpoint.Addr())
endpointParams.Facility = 19 // local3
endpointParams.AppName = "gitea"
endpointParams.AppName = "blog"
sender, _, _ := run(t, endpointParams)
_, _ = sender.Write([]byte("first\nsecond\n"))
@@ -103,8 +103,8 @@ func TestRecordsOverTCPAreOctetCountedWithTheirFacility(t *testing.T) {
wantCounts(t, sender, 2, 0, 0)
frames := bufio.NewReader(accept(t, endpoint))
wantFrame(t, frames, record(t, "<158>", "gitea", "first"))
wantFrame(t, frames, record(t, "<158>", "gitea", "second"))
wantFrame(t, frames, record(t, "<158>", "blog", "first"))
wantFrame(t, frames, record(t, "<158>", "blog", "second"))
})
}
+3 -3
View File
@@ -94,7 +94,7 @@ func TestProcessLinesAreMarkedProcessAndGiveTheInstance(t *testing.T) {
var out bytes.Buffer
requestlog.NewProcessLogger(&out, "fsn1app1/gitea", slog.LevelInfo).Info("starting",
requestlog.NewProcessLogger(&out, "fsn1app1/blog", slog.LevelInfo).Info("starting",
"version", "v1")
var fields map[string]any
@@ -104,7 +104,7 @@ func TestProcessLinesAreMarkedProcessAndGiveTheInstance(t *testing.T) {
t.Fatalf("decode %q: %v", out.String(), err)
}
if fields["type"] != "process" || fields["instance"] != "fsn1app1/gitea" ||
if fields["type"] != "process" || fields["instance"] != "fsn1app1/blog" ||
fields["msg"] != "starting" || fields["level"] != "INFO" ||
fields["version"] != "v1" {
t.Errorf("process line %v", fields)
@@ -133,7 +133,7 @@ func TestProcessLoggerWritesTheMessagesAtItsLevelOrMoreSevere(t *testing.T) {
var out bytes.Buffer
processLog := requestlog.NewProcessLogger(&out, "fsn1app1/gitea", level)
processLog := requestlog.NewProcessLogger(&out, "fsn1app1/blog", level)
for _, at := range levels {
processLog.Log(t.Context(), at, "message")
}
+1 -1
View File
@@ -18,7 +18,7 @@ const (
alertWebhookURL = "SWWAF_ALERT_WEBHOOK_URL"
alertMaxPerHour = "SWWAF_ALERT_MAX_PER_HOUR"
// alertInstance is the instance every alert of these tests gives.
alertInstance = "fsn1app1/gitea"
alertInstance = "fsn1app1/blog"
)
func TestBanForABrokenLimitRaisesABanAlertWithItsNotes(t *testing.T) {
+1 -1
View File
@@ -179,7 +179,7 @@ func TestAnomalyThreshold(t *testing.T) {
s.request(client, sqlInjection, http.StatusForbidden, requestlog.ActionWAFBlocked)
}
func TestDisabledRulesSwitchOffWhatGiteaWouldBeRefused(t *testing.T) {
func TestDisabledRulesSwitchOffWhatACodeForgeWouldBeRefused(t *testing.T) {
t.Parallel()
for _, request := range []struct {
+1 -1
View File
@@ -19,7 +19,7 @@ const (
// requestIDHeader carries the request's id.
requestIDHeader = "X-Request-ID"
// instance is the SWWAF_INSTANCE_NAME a test sets.
instance = "fsn1app1/gitea"
instance = "fsn1app1/blog"
// ipv6Client is a client on IPv6, and ipv6Group the netblock the rate
// limits count it as.
ipv6Client = "2001:db8::7"
+14 -14
View File
@@ -49,7 +49,7 @@ const (
adminSecret = "fedcba9876543210fedcba9876543210"
// instance is the SWWAF_INSTANCE_NAME the tests set where they look at
// it.
instance = "fsn1app1/gitea"
instance = "fsn1app1/blog"
// greeting is what the tests' app answers.
greeting = "hello from the app"
// placed is the client the tests' lookup databases place.
@@ -341,8 +341,8 @@ func TestEveryLogLineAndMetricCarriesTheInstanceName(t *testing.T) {
// the comments.
for line := range strings.Lines(metrics) {
if !strings.HasPrefix(line, "#") &&
!strings.Contains(line, `instance="fsn1app1/gitea"`) {
t.Errorf("series %q, without instance=\"fsn1app1/gitea\"", line)
!strings.Contains(line, `instance="fsn1app1/blog"`) {
t.Errorf("series %q, without instance=\"fsn1app1/blog\"", line)
}
}
}
@@ -610,9 +610,9 @@ func TestLookupDatabaseReplacedWhileRunningTakesEffect(t *testing.T) {
rename(t, replacement, path)
metrics := metricsWith(t, url+"_smallwebwaf/metrics", token,
`smallwebwaf_lookup_database_read_failures_total{instance="fsn1app1/gitea"} 1`)
`smallwebwaf_lookup_database_read_failures_total{instance="fsn1app1/blog"} 1`)
lastRead = seriesValue(t, metrics,
`smallwebwaf_lookup_database_last_read_timestamp_seconds{instance="fsn1app1/gitea"}`)
`smallwebwaf_lookup_database_last_read_timestamp_seconds{instance="fsn1app1/blog"}`)
wantStatus(t, url, placed, http.StatusOK)
})
@@ -668,7 +668,7 @@ func TestBlocklistTriesAndCopiesKeptInReputationJSONAcrossRestarts(t *testing.T)
// too.
rateLimitExemptNets: placed + "," + localhost,
}
failures := `smallwebwaf_reputation_failures_total{instance="fsn1app1/gitea",` +
failures := `smallwebwaf_reputation_failures_total{instance="fsn1app1/blog",` +
`source="` + torURL + `"} `
// tor.txt cannot be fetched at first, which is counted.
@@ -909,8 +909,8 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
// last.
metrics := metricsText(t, url+"_smallwebwaf/metrics", token)
for _, series := range []string{
`smallwebwaf_remote_log_lines_sent_total{instance="fsn1app1/gitea"} 0`,
`smallwebwaf_remote_log_buffer_depth{instance="fsn1app1/gitea"} 1`,
`smallwebwaf_remote_log_lines_sent_total{instance="fsn1app1/blog"} 0`,
`smallwebwaf_remote_log_buffer_depth{instance="fsn1app1/blog"} 1`,
} {
if !strings.Contains(metrics, "\n"+series+"\n") {
t.Errorf("no %q in the metrics:\n%s", series, metrics)
@@ -918,7 +918,7 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
}
const dropped = "\nsmallwebwaf_remote_log_lines_dropped_total" +
`{instance="fsn1app1/gitea"} `
`{instance="fsn1app1/blog"} `
if strings.Contains(metrics, dropped+"0\n") || !strings.Contains(metrics, dropped) {
t.Errorf("no line dropped in the metrics:\n%s", metrics)
}
@@ -1011,7 +1011,7 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
runUntilStopped(t, env, func(url string) {
webhook.waitFor(t, "permanent_ban", true)
const ofWebhook = `{destination="webhook",instance="fsn1app1/gitea"}`
const ofWebhook = `{destination="webhook",instance="fsn1app1/blog"}`
metrics := metricsWith(t, url+"_smallwebwaf/metrics", token,
"\nsmallwebwaf_alerts_sent_total"+ofWebhook+" 1\n")
@@ -1062,13 +1062,13 @@ func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
slackPost := slack.firstPost(t)
err := json.Unmarshal([]byte(slackPost.body), &message)
if err != nil || !strings.HasPrefix(message.Text, "*fsn1app1/gitea: ban*\n") ||
if err != nil || !strings.HasPrefix(message.Text, "*fsn1app1/blog: ban*\n") ||
!strings.Contains(message.Text, "\nclient: "+client+"\n") {
t.Errorf("Slack was sent %s", slackPost.body)
}
ntfyPost := ntfy.firstPost(t)
if ntfyPost.header.Get("Title") != "fsn1app1/gitea: ban" ||
if ntfyPost.header.Get("Title") != "fsn1app1/blog: ban" ||
ntfyPost.header.Get("Authorization") != "Bearer "+ntfyToken ||
!strings.Contains(ntfyPost.body, "\nclient: "+client+"\n") {
t.Errorf("ntfy was sent %s, with the headers %v", ntfyPost.body,
@@ -1080,9 +1080,9 @@ func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
// test process cannot fail the test.
sent := []string{
"\nsmallwebwaf_alerts_sent_total{destination=\"slack\"," +
"instance=\"fsn1app1/gitea\"} 1\n",
"instance=\"fsn1app1/blog\"} 1\n",
"\nsmallwebwaf_alerts_sent_total{destination=\"ntfy\"," +
"instance=\"fsn1app1/gitea\"} 1\n",
"instance=\"fsn1app1/blog\"} 1\n",
}
metrics := metricsWith(t, url+"_smallwebwaf/metrics", token, sent...)
+3 -3
View File
@@ -143,7 +143,7 @@ const filledAlertsJSON = `{
"waiting": {
"webhook": [
{
"instance": "fsn1app1/gitea",
"instance": "fsn1app1/blog",
"time": "2026-10-06T00:00:00Z",
"event": "ban",
"client": "203.0.113.9",
@@ -158,7 +158,7 @@ const filledAlertsJSON = `{
"suppressed_repeats": 0
},
{
"instance": "fsn1app1/gitea",
"instance": "fsn1app1/blog",
"time": "2026-10-06T00:00:00Z",
"event": "file_error",
"client": "",
@@ -1721,7 +1721,7 @@ func newParams(dir string) state.Params {
Events: alerts.Events(),
Cooldown: 15 * time.Minute,
MaxPerHour: 2,
Instance: "fsn1app1/gitea",
Instance: "fsn1app1/blog",
Now: midnight,
})
+12 -57
View File
@@ -1,6 +1,6 @@
// Package waf runs the OWASP Core Rule Set 4.25.0, through Coraza, on the
// method, the URL with its query and the headers of a request, and on its
// body while SWWAF_WAF_BODY_LIMIT is set, with the six changes smallwebwaf
// body while SWWAF_WAF_BODY_LIMIT is set, with the five changes smallwebwaf
// makes to it, as "Attack detection" under "Settings" in SPEC.md describes
// them. It reads no response.
//
@@ -15,7 +15,6 @@ import (
"io"
"net/http"
"net/netip"
"slices"
"strconv"
"strings"
@@ -69,35 +68,19 @@ SecArgumentsLimit 1000
SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" "id:900300,phase:2,pass,\
severity:'CRITICAL',setvar:'tx.inbound_anomaly_score_pl1=+5'"
# The sixth: only the rules for requests are loaded, and no response is
# The fifth: only the rules for requests are loaded, and no response is
# inspected.
Include @owasp_crs/REQUEST-*.conf
# The third: redirect_uri is not checked for a URL naming an IP address or
# localhost. Coraza matches a parameter name here, and in the fourth,
# without regard to case. ARGS holds the fields of a form data or multipart
# body Coraza reads as well as the query parameters, so a field of one of
# these names is left out too.
# localhost. Coraza matches a parameter name without regard to case. ARGS
# holds the fields of a form data or multipart body Coraza reads as well as
# the query parameters, so a field of this name is left out too.
SecRuleUpdateTargetById 931100 "!ARGS:redirect_uri"
SecRuleUpdateTargetById 934110 "!ARGS:redirect_uri"
# The fourth: the query parameters in which gitea sends names within a
# repository or its own records, or a page of its own site, are not
# checked against the lists of system files, shell paths and command
# names. Coraza takes one rule id per directive.
SecRuleUpdateTargetById 930120 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
!ARGS:sub_path|!ARGS:ref|!ARGS:sha|!ARGS:branch|!ARGS:workflow|\
!ARGS:artifactName|!ARGS:redirect_to"
SecRuleUpdateTargetById 932160 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
!ARGS:sub_path|!ARGS:ref|!ARGS:sha|!ARGS:branch|!ARGS:workflow|\
!ARGS:artifactName|!ARGS:redirect_to"
SecRuleUpdateTargetById 932260 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
!ARGS:sub_path|!ARGS:ref|!ARGS:sha|!ARGS:branch|!ARGS:workflow|\
!ARGS:artifactName|!ARGS:redirect_to"
# The fifth, for Referer: it is not checked for a Unix command without
# arguments, or for Java starting a process. The cookies are left out in
# Inspect.
# The fourth: Referer is not checked for a Unix command without arguments,
# or for Java starting a process. Coraza takes one rule id per directive.
SecRuleUpdateTargetById 932340 "!REQUEST_HEADERS:Referer"
SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
`
@@ -144,12 +127,6 @@ SecRule MULTIPART_STRICT_ERROR "!@eq 0" "id:900450,phase:2,pass,\
severity:'CRITICAL',setvar:'tx.inbound_anomaly_score_pl1=+5'"
`
// cookiesNotRead are the cookies the Core Rule Set reads a request
// without, the rest of the fifth change.
//
//nolint:gochecknoglobals // a constant cannot be a list
var cookiesNotRead = []string{"gitea_flash", "redirect_to"}
// Params are what New needs.
type Params struct {
// ParanoiaLevel is SWWAF_WAF_PARANOIA_LEVEL, from 1 to 4.
@@ -169,7 +146,7 @@ type CoreRuleSet struct {
bodyLimit int64
}
// New returns the Core Rule Set with the six changes, at params'
// New returns the Core Rule Set with the five changes, at params'
// paranoia level, without the rules it switches off, and reading request
// bodies up to params' limit.
func New(params Params) (*CoreRuleSet, error) {
@@ -209,10 +186,10 @@ type Result struct {
}
// Inspect runs the Core Rule Set on r, a request from client: on its
// method, its URL with the query, and its headers, the Cookie header
// without the cookies in cookiesNotRead, and on body, r's body as the
// caller has it, as readBody reads it. It returns what it found, what it
// read of body, and the error that ended the reading early, if one did.
// method, its URL with the query, and its headers, and on body, r's body
// as the caller has it, as readBody reads it. It returns what it found,
// what it read of body, and the error that ended the reading early, if
// one did.
func (c *CoreRuleSet) Inspect(
r *http.Request, client netip.Addr, body io.Reader,
) (Result, []byte, error) {
@@ -225,13 +202,6 @@ func (c *CoreRuleSet) Inspect(
for name, values := range r.Header {
for _, value := range values {
if name == "Cookie" {
value = withoutCookiesNotRead(value)
if value == "" {
continue // it held those cookies alone
}
}
tx.AddRequestHeader(name, value)
}
}
@@ -318,18 +288,3 @@ func score(tx types.Transaction) int {
return n
}
// withoutCookiesNotRead returns value, a Cookie header's, without the
// cookies in cookiesNotRead.
func withoutCookiesNotRead(value string) string {
var kept []string
for cookie := range strings.SplitSeq(value, ";") {
name, _, _ := strings.Cut(strings.TrimSpace(cookie), "=")
if !slices.Contains(cookiesNotRead, name) {
kept = append(kept, cookie)
}
}
return strings.Join(kept, ";")
}
+25 -76
View File
@@ -118,8 +118,8 @@ func atDefaults(t *testing.T) *waf.CoreRuleSet {
return newCoreRuleSet(t, 1, defaultDisabledRules...)
}
// wantChange checks that crs lets through passes, a gitea request one of
// the six changes is for, and still finds result in refused, a request
// wantChange checks that crs lets through passes, a request one of the
// five changes is for, and still finds result in refused, a request
// like it that the change is not for.
func wantChange(
t *testing.T, crs *waf.CoreRuleSet, passes, refused request, result waf.Result,
@@ -241,71 +241,25 @@ func TestRedirectURIMayNameALocalAddress(t *testing.T) {
get(oauth+"next=http://localhost:52341/"), matched(934110))
}
func TestParametersGiteaSendsNamesInSkipTheListsOfFilesPathsAndCommands(t *testing.T) {
t.Parallel()
crs := atDefaults(t)
for _, value := range []struct {
name string
// result is what a parameter that is not one of gitea's gets.
result waf.Result
}{
// A file on the list of system files.
{".gitignore", matched(930120)},
// A command's name, after a directory on the list of shell paths.
{"bin/docker-entrypoint", matched(932260, 932160)},
} {
for _, parameter := range []string{
"path", "files", "skip-to", "sub_path", "ref", "sha", "branch", "workflow",
"artifactName", "redirect_to",
} {
wantChange(t, crs, get("/?"+parameter+"="+value.name),
get("/?q="+value.name), value.result)
}
}
// What only those rules refuse gets through there too, but path
// traversal and SQL injection are still refused.
wantChange(t, crs, get("/?path=|cat%20/etc/passwd"), get("/?q=|cat%20/etc/passwd"),
matched(930120, 932160))
wantResult(t, crs, get("/?path=../../etc/passwd"),
waf.Result{RuleIDs: []int{930100, 930110}, Score: 20})
wantResult(t, crs, get("/?path=1'%20OR%20'1'='1"), matched(942100))
}
func TestParameterNamesAreMatchedWithoutRegardToCase(t *testing.T) {
t.Parallel()
crs := atDefaults(t)
wantChange(t, crs, get("/?Path=.gitignore"), get("/?q=.gitignore"), matched(930120))
wantChange(t, crs, get("/?REDIRECT_URI=http://127.0.0.1:52341/"),
wantChange(t, atDefaults(t), get("/?REDIRECT_URI=http://127.0.0.1:52341/"),
get("/?next=http://127.0.0.1:52341/"), matched(931100, 934110))
}
func TestCookiesGiteaFlashAndRedirectToAreNotRead(t *testing.T) {
func TestCookiesAreRead(t *testing.T) {
t.Parallel()
const (
flash = "success%3DFile%2Bpackage.json%2Bdeleted"
redirectTo = "%2Fowner%2Frepo%2Fsrc%2Fbranch%2Fmain%2Fpackage.json"
)
// A file on the list of system files, in any cookie.
const file = "%2Fowner%2Frepo%2Fsrc%2Fbranch%2Fmain%2Fpackage.json"
crs := atDefaults(t)
wantChange(t, crs, get("/owner/repo", "Cookie: gitea_flash="+flash),
get("/owner/repo", "Cookie: flash="+flash), matched(930120))
wantChange(t, crs, get("/", "Cookie: redirect_to="+redirectTo),
get("/", "Cookie: redirect="+redirectTo), matched(930120))
// Among other cookies, which are read.
wantChange(t, crs,
get("/", "Cookie: lang=en-US; gitea_flash="+flash+"; redirect_to="+redirectTo+
"; i_like_gitea=abc"),
get("/", "Cookie: lang=en-US; gitea_flash="+flash+"; redirect="+redirectTo+
"; i_like_gitea=abc"),
matched(930120))
for _, name := range []string{"flash", "redirect_to"} {
wantResult(t, crs, get("/", "Cookie: lang=en-US; "+name+"="+file),
matched(930120))
}
}
func TestRefererIsNotCheckedForACommandOrJavaStartingAProcess(t *testing.T) {
@@ -538,37 +492,32 @@ func TestContentEncodingIsRefusedOnTheKindsOfBodyTheCoreRuleSetReads(t *testing.
wantBody(t, atDefaults(t), post(formData, gzip), "a", waf.Result{}, "")
}
func TestParametersGiteaSendsNamesInAreLeftOutAmongFormFieldsToo(t *testing.T) {
func TestRedirectURIIsLeftOutAmongFormFieldsToo(t *testing.T) {
t.Parallel()
const local = "http://127.0.0.1:52341/"
crs := readingBodies(t)
local := url.QueryEscape("http://127.0.0.1:52341/")
for _, tc := range []struct {
body string
want waf.Result
header, body string
want waf.Result
}{
{"path=.gitignore", waf.Result{}},
{"q=.gitignore", matched(930120)},
{"redirect_uri=" + local, waf.Result{}},
{"next=" + local, matched(931100, 934110)},
{formData, "redirect_uri=" + url.QueryEscape(local), waf.Result{}},
{formData, "next=" + url.QueryEscape(local), matched(931100, 934110)},
{multipart, field("redirect_uri", local) + end, waf.Result{}},
{multipart, field("next", local) + end, matched(931100, 934110)},
} {
wantBody(t, crs, post(formData), tc.body, tc.want, tc.body)
wantBody(t, crs, post(tc.header), tc.body, tc.want, tc.body)
}
body := field("path", ".gitignore") + end
wantBody(t, crs, post(multipart), body, waf.Result{}, body)
body = field("q", ".gitignore") + end
wantBody(t, crs, post(multipart), body, matched(930120), body)
// A JSON body's field is named by its path, here json.path, and is
// checked.
body = `{"path":".gitignore"}`
wantBody(t, crs, post(jsonBody), body, matched(930120), body)
// A JSON body's field is named by its path, here json.redirect_uri, and
// is checked.
body := `{"redirect_uri":"` + local + `"}`
wantBody(t, crs, post(jsonBody), body, matched(931100, 934110), body)
}
func TestGiteaBodiesTheCoreRuleSetRefuses(t *testing.T) {
func TestCommentsAndAttachmentsTheCoreRuleSetRefuses(t *testing.T) {
t.Parallel()
crs := readingBodies(t)