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

+42 -76
View File
@@ -81,7 +81,7 @@ to them.
of bans is full.
- `internal/rules`: reads the rule files at start and again when they
change, and tells which rules a request matches.
- `internal/waf`: the Core Rule Set, with the six changes listed under
- `internal/waf`: the Core Rule Set, with the five changes listed under
"Attack detection", as Coraza directives.
- `internal/lookup`: looks clients up through GeoJS or in the lookup
database.
@@ -402,10 +402,10 @@ scrapers that send many requests slowly over hours or days. The hour limit is
the main one.
- Per minute: 120, 2 requests a second. It leaves room for a person opening two
pages at once with an empty cache. A gitea page opened with an empty cache
pages at once with an empty cache. A web app's page opened with an empty cache
loads 20 to 60 files, and a handful once cached.
- Per hour: 1,500. A person reading code in gitea for a whole hour, a page every
20 seconds, makes about 900 requests. A scraper making one request every 2
- Per hour: 1,500. A person reading a site for a whole hour, a page every 20
seconds, makes about 900 requests. A scraper making one request every 2
seconds makes 1,800 an hour, more than the limit.
- Per day: 6,000, four hours at the hour limit. A scraper making one request
every 10 seconds makes 8,640 a day, more than the limit.
@@ -417,7 +417,7 @@ the blocklists. An abuser that trips none of them is not caught.
Some legitimate clients pass the limits:
- An Actions runner asks gitea for work all day and can pass the limits.
- A CI runner asks its code forge for work all day and can pass the limits.
- Everyone behind one shared address, such as an office, shares one client's
limits.
@@ -550,12 +550,11 @@ service's abuse comes from.
By default the Core Rule Set reads the method, the URL with its query string,
and the headers of every request. That is where automated attacks show:
injection in query strings, path traversal, attacks carried in headers,
scanners' user agents. It reads no request body. In front of a code forge such
as gitea, bodies carry what people write and publish, such as issue text and
files saved in the web editor. The Core Rule Set takes the shell commands and
code in them for attacks. The default gives up refusing an attack carried in a
body. The rule files, the limits and the bans still apply to the client that
sends it.
scanners' user agents. It reads no request body. In front of a code forge,
bodies carry what people write and publish, such as issue text and files saved
in the web editor. The Core Rule Set takes the shell commands and code in them
for attacks. The default gives up refusing an attack carried in a body. The rule
files, the limits and the bans still apply to the client that sends it.
- The Core Rule Set skips a path under `SWWAF_WAF_EXEMPT_PATHS`.
- `SWWAF_WAF_PARANOIA_LEVEL` and `SWWAF_WAF_ANOMALY_THRESHOLD` are the Core Rule
@@ -591,15 +590,14 @@ sends it.
data, where binary content such as a git push trips rules written for text. A
body the Core Rule Set cannot parse adds 5 to the score, and so does a
multipart body cut by the limit in the middle of a part's header. Body
inspection suits apps whose forms carry no code; `docs/gitea.md` records what
it refuses in front of gitea.
inspection suits apps whose forms carry no code.
- `SWWAF_TRAP_PATHS` names paths the app never serves and only scanners ask for,
such as `/wp-login.php` in front of gitea. This is the short form of a `path`
rule with the `ban` action, for deployments that mount no rule files. A trap
path is matched whole, without the query, against the path as sent and once
percent-decoded: `/wp-login.php` matches `/wp-login.php?x=1` and
`/%77p-login.php`, and does not match `/wp-login.php/` or
`/blog/wp-login.php`.
such as `/wp-login.php` in front of an app that is not WordPress. This is the
short form of a `path` rule with the `ban` action, for deployments that mount
no rule files. A trap path is matched whole, without the query, against the
path as sent and once percent-decoded: `/wp-login.php` matches
`/wp-login.php?x=1` and `/%77p-login.php`, and does not match `/wp-login.php/`
or `/blog/wp-login.php`.
- `SWWAF_ERROR_BURST_THRESHOLD` counts a client's refusals per minute of these
kinds, and of no other:
- a rule file match;
@@ -609,12 +607,12 @@ sends it.
More than this breaks a limit (see "Bans"). A client trying one attack after
another is refused many times a minute; a person rarely more than a few
times. Answers the app gives are not counted, since they do not tell a
scanner from an ordinary client. In front of gitea, registry and git clients
get `404` and `401` by design, hundreds of times a minute.
scanner from an ordinary client. In front of a code forge, registry and git
clients get `404` and `401` by design, hundreds of times a minute.
`smallwebwaf` changes the Core Rule Set 4.25.0 in six ways, since in front of
gitea it would otherwise refuse ordinary requests. The changes hold in front of
every app, and no setting undoes them:
`smallwebwaf` changes the Core Rule Set 4.25.0 in five ways, since in front of a
code forge it would otherwise refuse ordinary requests. The changes hold in
front of every app, and no setting undoes them:
1. `PUT`, `PATCH` and `DELETE` are allowed methods besides `GET`, `HEAD`, `POST`
and `OPTIONS`. APIs, container image pushes and package uploads use them.
@@ -627,52 +625,20 @@ every app, and no setting undoes them:
body cannot be inspected. The other headers the Core Rule Set refuses, such
as `Proxy`, stay refused.
3. The `redirect_uri` parameter is not checked for a URL naming an IP address or
`localhost` (931100, 934110). Git Credential Manager, git-credential-oauth
and tea, which gitea registers for OAuth sign-in, ask to be sent back to
`http://127.0.0.1` on the user's own machine. The server never fetches that
address.
4. Ten query parameters are not checked against the Core Rule Set's lists of
system files (930120), shell paths (932160) and command names (932260):
- `path`, `files`, `skip-to` and `sub_path`;
- `ref`, `sha`, `branch` and `workflow`;
- `artifactName` and `redirect_to`.
gitea puts into them file paths, branch and workflow names, and the page to
return to after signing in. It reads these values as names in a repository
or in its own records, or as a page of its own site, never as a command. Any
name on those lists can be an ordinary file or branch, such as `.gitignore`
or `docker-build`. In these parameters, what only these three rules would
catch reaches the app, such as the command `|cat /etc/passwd`. Path
traversal, SQL and script injection, and PHP, Java and Node.js code are
still refused there. Every other parameter keeps all three rules. An app
that uses one of these parameters as a file on the server, or passes it to a
shell, must check the value itself. A rule file can refuse it instead.
5. Two cookies are left out, and `Referer` keeps fewer rules:
- The Core Rule Set reads the request without the `gitea_flash` and
`redirect_to` cookies. gitea puts a message naming what was just done into
`gitea_flash`. It puts into `redirect_to` the page to return to after
signing in. The Core Rule Set takes file names in them, such as
`package.json`, for attacks. A browser sends such a cookie with every
request until gitea replaces it. gitea cannot replace it while those
requests are refused, so one refusal would keep the browser out of the
whole site. gitea shows the message with any script removed and returns
only to its own pages. Every other cookie is read in full. An app that
reads a cookie of either name must check it itself.
- `Referer` is not checked for a Unix command given without arguments
(932340) or for Java starting a process (944110). `Referer` names the page
a request comes from, on everything that page loads and on every link
followed from it. The Core Rule Set takes some page addresses for attacks,
such as a search for `env`, or OpenJDK's `Runtime.java`, whose path holds
both `runtime` and `java.`. All of those requests would be refused.
`Referer` keeps the other rules. An app that passes `Referer` to a shell
must check it itself.
6. Responses are not inspected. A raw file from a repository, such as a shell
`localhost` (931100, 934110). OAuth clients that run on the user's own
machine, such as Git Credential Manager, ask to be sent back to
`http://127.0.0.1` there. The server never fetches that address. The
parameter's name is matched without regard to case, as Coraza matches it.
4. `Referer` is not checked for a Unix command given without arguments (932340)
or for Java starting a process (944110). `Referer` names the page a request
comes from, on everything that page loads and on every link followed from it.
The Core Rule Set takes some page addresses for attacks, such as a search for
`env`, or OpenJDK's `Runtime.java`, whose path holds both `runtime` and
`java.`. All of those requests would be refused. `Referer` keeps the other
rules. An app that passes `Referer` to a shell must check it itself.
5. Responses are not inspected. A raw file from a repository, such as a shell
script, looks to the response rules like source code leaking from the server.
The parameter and cookie names in these changes are matched without regard to
case, as Coraza matches them.
### Ban lengths, response and scope
`SWWAF_BAN_RESPONSE` is `403` by default, which makes a ban easy to recognise
@@ -836,7 +802,7 @@ and the default file.
runs. It reads the files again once the directory has had no change for 2
seconds, so that a file still being written is read whole. A file whose name
starts with `.`, such as an editor's lock file, is not a rule file.
- Files are read in name order (`00-default.rules` before `50-gitea.rules`),
- Files are read in name order (`00-default.rules` before `50-site.rules`),
rules in line order.
- A rule's regex is Go's RE2 syntax. It has no backreferences or lookaround; in
exchange matching time is linear in the input, so no rule can be made to stall
@@ -865,14 +831,15 @@ and the default file.
- The image ships a default file, kept short and limited to patterns that are
wrong for every app, with its path rules anchored at the site root. In front
of gitea, `/.env` is a probe, while
of a code forge, `/.env` is a probe, while
`/<owner>/<repo>/src/branch/main/.env.example` is a file in a repository that
any visitor may open. Anchor the paths of your own `ban` rules at the site
root too. Anything else, such as a rule banning WordPress probes in front of
gitea, belongs in a file the deployer adds to the image `servewaf` runs from,
or mounts. A rule has one target, so a `path` rule applies on every site
behind that `servewaf`. A directory mounted over `SWWAF_RULES_DIR` replaces
the default file; single files mounted into it add to it.
an app that is not WordPress, belongs in a file the deployer adds to the image
`servewaf` runs from, or mounts. A rule has one target, so a `path` rule
applies on every site behind that `servewaf`. A directory mounted over
`SWWAF_RULES_DIR` replaces the default file; single files mounted into it add
to it.
- Rules meant to be edited while `servewaf` runs belong in a mounted directory,
not a mounted file. Many editors save a file by replacing it, and a container
keeps seeing the old one when the file alone is mounted. The lookup database
@@ -1695,8 +1662,7 @@ with no change to upaas. upaas builds the app's Dockerfile and runs the one
container as it runs any app. The traefik labels route to port 8080, and the
`SWRP_` settings go with the app's environment variables. `servewaf` runs in a
container of its own, on a network the app's container can reach, with its state
volume and the directory of any token file. `docs/gitea.md` records what the
defaults do in front of gitea, and `script/gitea-requests` checks those notes.
volume and the directory of any token file.
## Planned, not built