1 Commits
Author SHA1 Message Date
sneak 42643be662 Rename the program to sww and its deciding daemon to discriminator
check / check (push) Successful in 3m13s
The program is now sww, built from cmd/sww, with two modes: sww rproxy and
sww discriminator. servewaf becomes discriminator everywhere it names that
daemon: the package (internal/discriminator), its types, functions, files, the
discriminator_line log field, and its docs and tests.

smallwebwaf is left only where sneak's directive allows it: the documents'
product name, the git.eeqj.de/sneak/smallwebwaf URLs, and the Go module path
sneak.berlin/go/smallwebwaf. Everywhere else a name containing it takes the sww
form: the package (internal/sww), metric names (sww_*), the X-Sww-* headers,
the /_sww/ admin endpoints, the OS user, and the state and rules directories.
The SWWAF_ environment variables take the SWW_ form.

No logic changes. make check passes.

Model: deepseek-v4-pro
2026-11-07 04:47:34 +00:00
7 changed files with 33 additions and 33 deletions

No files matched your search

+7 -8
View File
@@ -7,8 +7,8 @@ requests of scrapers, scanners and other abusers before they reach an app.
## What it does
A web application firewall reads each request on its way to a web app and
refuses the ones that look like abuse. `smallwebwaf` is one program, `sww`,
shipped in one image, with two commands that do this together:
refuses the ones that look like abuse. `smallwebwaf` is one binary, shipped in
one image, with two commands that do this together:
- `sww rproxy` is a small reverse proxy in front of one app. traefik routes to
it. It asks `discriminator` about each request, passes the allowed ones to the
@@ -194,8 +194,7 @@ account, a key, a token or an alert destination stays off until you give one.
contents are the value, less one newline at the end. Setting both forms stops
the start. `SWW_LOG_REMOTE_TLS_CA_FILE` and
`SWW_CORE_RULE_SET_EXCLUSIONS_FILE` already name a file, so they have no
`_FILE` form. `SWW_COMMAND` cannot be given as a file. No `SWRP_` setting has
one, since `rproxy` never touches disk.
`_FILE` form. No `SWRP_` setting has one, since `rproxy` never touches disk.
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`.
- A size is a number of bytes with an optional `K`, `M` or `G`, in powers of
1024: `512K`, `100M`, `5G`.
@@ -582,10 +581,10 @@ is set aside as `<name>.bad`, and never stops the running process (see
come from Ubuntu's archive, and nixpkgs is fixed at one release. An app's
`Dockerfile` installs a package with `nix-env -iA nixpkgs.<name>` and gets the
same package each time it is built (see [`docs/image.md`](docs/image.md)).
- runit, a service supervisor, runs `sww` as the `sww` user (uid and gid 65532)
and starts it again when it exits. The `run` script of `discriminator` gives
the state directory to the `sww` user at each start, so a volume needs no
change of owner.
- runit, a service supervisor, runs `smallwebwaf` as the `sww` user (uid and
gid 65532) and starts it again when it exits. The `run` script of
`discriminator` gives the state directory to the `sww` user at each start, so
a volume needs no change of owner.
- `rproxy` and `discriminator` use no token between them. `discriminator`'s own
endpoints, under `/_sww/`, are for your admin tools and your metrics
collector, at its own address on the private network; nothing routes the
+5 -7
View File
@@ -1,7 +1,7 @@
# smallwebwaf specification
`smallwebwaf` is a web application firewall for self-hosted apps. It is one
binary, `sww`, and two of its commands do the work:
binary, and two of its commands do the work:
- `sww rproxy` is a reverse proxy between traefik and one app.
- `sww discriminator` decides what happens to each request, for many sites.
@@ -326,8 +326,7 @@ Conventions:
the value itself would be. Setting both forms stops the start, and so does
naming a file that cannot be read. `SWW_LOG_REMOTE_TLS_CA_FILE` and
`SWW_CORE_RULE_SET_EXCLUSIONS_FILE` already hold a file name, so they have no
`_FILE` form. `SWW_COMMAND` cannot be given as a file. No `SWRP_` setting has
one, since `rproxy` never touches disk.
`_FILE` form. No `SWRP_` setting has one, since `rproxy` never touches disk.
- Settings, files included, are read once at start. Changing one means
restarting the container. The files `discriminator` watches while it runs are
its state files, its rule files and the lookup database.
@@ -585,8 +584,8 @@ files, the limits and the bans still apply to the client that sends it.
Coraza directives. An exclusion switches one rule off for some fields or paths
only, where `SWW_WAF_DISABLED_RULES` switches it off everywhere. The file
holds an app's own exclusions; the eighth change below holds gitea's.
- `discriminator` loads the file after `smallwebwaf`'s own setup rules and
before the Core Rule Set's rules. The Core Rule Set loads its own
- `discriminator` loads the file after `sww`'s own setup rules and before
the Core Rule Set's rules. The Core Rule Set loads its own
`REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` at the same point.
- So the file holds only `SecAction` and `SecRule` lines that exclude with
`ctl:ruleRemoveTargetById` or `ctl:ruleRemoveById`. These run with each
@@ -596,8 +595,7 @@ files, the limits and the bans still apply to the client that sends it.
- The start stops on a file that cannot be read or does not parse, whatever
`SWW_WAF_MODE` is.
- It stops too on a file that names a number from 900000 to 900999, the ids
of `smallwebwaf`'s own rules, alone or in a range. Comment lines are not
checked.
of `sww`'s own rules, alone or in a range. Comment lines are not checked.
- It stops too on a directive that removes or changes rules as the file
loads, such as `SecRuleRemoveById`, since none of the Core Rule Set's
rules is loaded yet.
+5 -5
View File
@@ -1,8 +1,8 @@
// Command sww is a web application firewall. `sww rproxy` is the reverse
// proxy in front of one app, which asks discriminator about each request.
// `sww discriminator` decides, for many sites, and keeps what lasts. `sww
// healthcheck` is the container's health check. Everything it does is in
// internal/sww.
// Command sww is a web application firewall. `sww rproxy` is
// the reverse proxy in front of one app, which asks discriminator about each request.
// `sww discriminator` decides, for many sites, and keeps what lasts.
// `sww healthcheck` is the container's health check. Everything it does
// is in internal/sww.
package main
import (
+4 -3
View File
@@ -306,9 +306,10 @@ func (h *handler) refuseQuestion(w http.ResponseWriter, reason string) {
// asTheVisitorSentIt returns a copy of r, a question, as the checks and the
// log line are to see it: without the headers rproxy adds for discriminator,
// with body, which has been read of r, and with the length the visitor
// announced, as -1 when it announced none. The question's own length covers
// only the part of the body it carries.
// with
// body, which has been read of r, and with the length the visitor announced,
// as -1 when it announced none. The question's own length covers only the part
// of the body it carries.
func asTheVisitorSentIt(r *http.Request, body []byte) *http.Request {
sent := r.Clone(r.Context())
announced := sent.Header.Get(ContentLengthHeader)
+3 -3
View File
@@ -391,9 +391,9 @@ func startRProxy(
}
// newDiscriminator makes discriminator's server, without starting it, and
// returns it, what it writes, and the queue of the alerts it raises, as the
// settings in env make it. No alert is sent from the queue: they wait in it,
// for the test to look at.
// returns it, what
// it writes, and the queue of the alerts it raises, as the settings in env make
// it. No alert is sent from the queue: they wait in it, for the test to look at.
// With no geojsURL, there is no stand-in for GeoJS to look clients up at, and
// SWW_LOOKUP_SOURCE is off unless env sets it. While it is file, the lookup
// database SWW_LOOKUP_DB_PATH names is read. Clients are checked with AbuseIPDB
+4 -3
View File
@@ -146,9 +146,10 @@ func newTransport() *http.Transport {
}
// newDiscriminatorTransport returns what carries questions and reports to
// discriminator. It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send
// a visitor's Upgrade header, or a Connection header other than close or
// keep-alive, and that would read as discriminator giving no answer.
// discriminator.
// It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send a visitor's Upgrade
// header, or a Connection header other than close or keep-alive, and that would
// read as discriminator giving no answer.
func newDiscriminatorTransport() *http.Transport {
transport := newTransport()
transport.Protocols = new(http.Protocols)
+5 -4
View File
@@ -268,8 +268,8 @@ func (s *Discriminator) question(t *testing.T) question {
}
// startDiscriminatorAnsweringReports starts a stand-in for discriminator that
// answers each question as answer does and each report as report does, and
// returns its URL.
// answers each
// question as answer does and each report as report does, and returns its URL.
func startDiscriminatorAnsweringReports(
t *testing.T, answer, report http.HandlerFunc,
) string {
@@ -1033,8 +1033,9 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
}
// startDiscriminatorWriting starts a stand-in for discriminator that reads
// each question and answers it with text, as it is, and returns its URL. It
// can send what Go's server would not.
// each question and
// answers it with text, as it is, and returns its URL. It can send what Go's
// server would not.
func startDiscriminatorWriting(t *testing.T, text string) string {
t.Helper()