Commit Graph
21 Commits
Author SHA1 Message Date
clawbot 638f355080 Re-vendor the canonical files from sneak/prompts at dd4027b (closes #65)
check / check (push) Successful in 4m10s
The vendored files are fetched from sneak/prompts commit dd4027b, with
this repository's /bin carried forward in .dockerignore; the
test-support deny list has no entries of its own. The lint phase moves
to golangci-lint v2.14.0. The build stage now takes the version from
git describe on the .git the build context carries, unless VERSION is
passed, and fails when .git is present but no version comes out. The
test phase drops -count=1, which the policy says it does not need, and
keeps its tmpfs build cache. One test calls Header.Get with X-Real-IP,
as canonicalheader asks.

Deviation: the Go lines of .gitignore and .editorconfig are dropped;
they are not exempt from byte-identity.

Model: opus-5-5
2026-10-05 23:59:29 +00:00
clawbot df4cf769e0 Derive the proxy tests' short timeout setting from the timeout (closes #61)
check / check (push) Successful in 4m14s
shortTimeoutSetting is now shortTimeout.String(), so changing waitLimit
changes both together.

Model: opus-5-5
2026-10-04 11:50:08 +02:00
clawbot 8410cf05e6 Quiet the useradd and Perl warnings in the image build (closes #58)
check / check (push) Failing after 3s
useradd --system warns when the uid it is given is above SYS_UID_MAX,
999 on Ubuntu; --key raises that limit for this one call, so the uid
stays 65532.

minsysusers, which runit's install runs to create its _runit-log user,
prints a Perl warning because runit's sysusers line leaves out the
shell. It reads /etc/sysusers.d/runit.conf in place of runit's file, so
the image writes a copy of that line there, naming the shell minsysusers
gives anyway; the user it creates is unchanged.

The runsvinit warning stays, since it needs a change to runsvinit: its
reaper and its own wait on runsvdir race for the same exited process.

Model: opus-5-5
2026-10-04 11:38:14 +02:00
clawbot e7fb88af9e Spend less of make test writing the image and waiting (closes #56)
check / check (push) Failing after 45s
The test phase spends most of its time compiling with the race
detector from an empty build cache; then come writing the test image
and the internal/proxy tests.

- Go's build cache is on a tmpfs in the test phase, so its 137 MB are
  no longer written into the test image.
- TestUpgradedConnectionOutlastsTheTimeouts waits until just past
  shortTimeout after the answer to the upgrade was read, by when every
  timeout has started, rather than 7.5 s, so it ends with the other
  timing tests.
- shortTimeout is written as waitLimit / 2, as its comment says it is.

Model: opus-5-5
2026-10-04 11:13:32 +02:00
clawbot d4f90dba37 The image apps build FROM, with its health check (closes #45)
check / check (push) Failing after 3s
The Dockerfile's last stage is now the image of "Deployment" in SPEC.md:
Ubuntu 26.04 with ca-certificates, nix-bin and runit from a dated
snapshot whose InRelease files are checked by hash, nixpkgs from its
release file checked by SHA-256, runsvinit built at a fixed commit, and
smallwebwaf as a runit service. smallwebwaf answers
/_smallwebwaf/healthz, and `smallwebwaf healthcheck`, which takes no
further argument, is the image's HEALTHCHECK. script/example-app builds
an app on the image and checks it end to end.

The Nix profile comes last on the PATH: first, busybox from nixpkgs
replaced runit's own runsvdir and sv. SPEC.md is corrected to match
what was built.

Model: opus-5-5
2026-10-04 10:05:30 +02:00
clawbot 0750879e58 Country allow and deny lists, looked up through GeoJS (closes #44)
check / check (push) Failing after 3s
SWWAF_DENIED_COUNTRIES and SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES refuse a
request with 403 before its body is read or rate-limited, logged as
country_denied. internal/lookup asks GeoJS only while a list is set, 200
clients per request, one at a time, keeping answers 7 days. Failures, a
redirect or an answer leaving an address out included, are logged without
addresses; GeoJS is then left alone a second, doubling to five minutes.
Private, loopback and link-local clients are never sent.

Deviation, per the issue: no SWWAF_LOOKUP_SOURCE or SWWAF_LOOKUP_TIMEOUT; 403, not SWWAF_BAN_RESPONSE.
Deviation: GeoJS's country endpoint, not geo.json.
Judgement call: an IPv6 /64 is asked about by its first address; at most 10,000 clients wait.
Judgement call: config.go lists the ISO 3166-1 codes; no widely used library holds them.

Model: opus-5-5
2026-10-04 08:29:41 +02:00
clawbot 6977ff73df Proxy timing tests outlast a hold-up of the test process (closes #53)
check / check (push) Successful in 1m53s
smallwebwaf starts each timeout as the request arrives, before the step
a test needs first: the upgrade answered, the app's buffers full, the
first part of an answer passed on. A hold-up of the test process longer
than the 300 ms timeout ran it out before that step. No test can make
that step come first, and in the "waiting on the app" cases it cannot
see which side smallwebwaf was waiting on, so the timeout is now 5 s,
the hold-up wantTimedOut already allows. The timeout tests set when it
must not run out goes from 10 s to 1 m to stay clear of it. The upgrade
test waits 7.5 s past the upgrade.

Judgement call: one shared value; the proxy tests take about 8 s, not 2 s.

Model: opus-5-5
2026-10-04 07:53:18 +02:00
clawbot d730fcb57d Point the unreachable-app test at port 1 (closes #51)
check / check (push) Successful in 2m21s
TestAnswers502WhenTheAppCannotBeReached closed a listener and pointed
smallwebwaf at its port, which another test running in parallel could
open in between, so the test sometimes got that server's answer instead
of a 502. It now uses 127.0.0.1:1: nothing listens there, and a test
listening on port 0 is always given a port from 32768 up, so no test
can take it. No other test reuses a closed listener's port.

Model: opus-5-5
2026-10-04 06:39:07 +02:00
clawbot 6bd5f620f6 TestRequestTimeouts expects 504 when none of the body reached the app (closes #49)
check / check (push) Successful in 2m18s
In the cases where the app reads and the client stops sending halfway,
smallwebwaf waits on the client only once it has passed the first bytes
of the body to the app. A test process held up for the whole 300 ms
timeout before then rightly gets 504, as SPEC.md asks, so the test was
wrong to expect 408 every time.

The app in those cases now records whether it received any of the body.
Once the app has finished with the request, the case expects 408 and its
log line if it did, and 504 and its log line if not.

Model: opus-5-5
2026-10-04 06:09:22 +02:00
clawbot f51459fbfe Per-client request rate limits over a minute, an hour and a day (closes #43)
check / check (push) Successful in 2m50s
Each client, one IPv4 address or one IPv6 /64, is counted in two buckets
per window, the earlier weighted by how much of it the window covers; at
most 20,000 clients are kept, least recently seen dropped first. A
request over SWWAF_RATE_LIMIT_PER_MINUTE, _HOUR or _DAY (1000, 10000,
50000, or off) gets 429 before reaching the app. Refused requests count,
413s included. A clock set back over a second behind a bucket's start
restarts that window. The log line gains limit_hit and the action
rate_limited.

Deviation from SPEC.md, per the issue: the 20,000 bound and /64 are fixed.
Judgement call: golang-lru/v2 holds the table; httprate does not count refused requests.
Deviation: go.mod and go.sum hand-written; no make target tidies them.

Model: opus-5-5
2026-10-04 04:24:34 +02:00
clawbot bedd324f3c Add the MIT licence (closes #15)
check / check (push) Successful in 3m8s
LICENSE with the MIT text, the README naming MIT in its opening line and a
License section, and the licence field in package.json.

Model: opus-5-5
2026-10-04 03:07:52 +02:00
clawbot a399601c77 Settle the spec's open points after milestones 1 and 2 (closes #36)
check / check (push) Successful in 1m43s
The header size and the idle time stay fixed at their defaults through
milestone 2 and become settings in the first stage after it, which also
brings SWWAF_ALLOW_NETS and with it the allow-only country list's refusal
of private addresses. While a request body is on its way, a request
timeout answers 408 or 504 by the side smallwebwaf was waiting on, as
milestone 1's code does. The README says the state files, the GeoJS
answers among them, come in milestone 3 or later, and points at the
build order.

Model: opus-5-5
2026-10-04 02:57:24 +02:00
clawbot 2e35528c62 Settle the open points of the Ubuntu and nixpkgs image (closes #38)
check / check (push) Successful in 2m47s
ca-certificates, nix-bin and runit come from a dated Ubuntu snapshot no
older than the pinned Ubuntu image. The Dockerfile names the SHA-256 hash
of each snapshot InRelease file apt uses, and the build checks them before
apt-get install, so every package is checked against hashed files. That
install uses the Go image's CA certificate file. ca-certificates is
installed by name. The image writes build-users-group = to
/etc/nix/nix.conf so root can build without a daemon. nixpkgs comes from
its release file on releases.nixos.org, checked by SHA-256, and takes about
500 MiB of disk. runsvinit is archived upstream and is built at a fixed
commit with a go.mod written for the build. The example run scripts put
their code in a main function.

Model: opus-5-5
2026-10-04 02:42:56 +02:00
clawbot 983192ace3 Deploy model: listen port, token files, state directory owner (closes #33)
check / check (push) Successful in 2m37s
SWWAF_LISTEN_ADDR may set another port: the health check takes its port
from it, and traefik's port label must name the same one. Its address
part stays empty (:9000), so smallwebwaf keeps listening on every
address, where traefik and the health check on 127.0.0.1 both reach it.
A token file is made on the host owned by uid 65532 with mode 0400 and
its directory mounted read-only; through upaas, that directory is one of
the app's volume mounts. The run script of smallwebwaf makes the state
directory and every file in it belong to the smallwebwaf user.

Model: opus-5-5
2026-10-04 01:42:43 +02:00
clawbot d76715b0df Pass-through proxy with timeouts, size limits and a request log (closes #13)
check / check (push) Successful in 1m29s
Milestone 1, the repo's first code. smallwebwaf passes each request to the app and the answer back unchanged, streaming bodies and WebSocket upgrades, within four timeouts (client and app, request and response) and two size limits, and writes one JSON line per request to stdout. Every setting has an SWWAF_ name and a default, and an invalid value stops the start. The repo gets the standard layout: script/ entrypoints, make targets that call them, a Dockerfile that runs the checks, and the Gitea workflow.

Disclosure: SPEC.md changed. Go's server reads the request line and headers before smallwebwaf sees the request, so slow headers are closed without an answer, and neither slow nor oversized headers get a log line.
Disclosure: standard library only.

Model: opus-5-5
2026-10-03 17:24:34 +02:00
sneak fd77e76177 Merge pull request 'Spec: SPEC.md and README.md to sneak's rulings, deploy model, milestones and Ubuntu base' (#31) from next into main
Reviewed-on: #31
2026-09-29 02:57:57 +02:00
clawbot b821897db8 Build the smallwebwaf image on the latest Ubuntu LTS with nixpkgs (closes #34)
The smallwebwaf image is now built on the newest Ubuntu LTS release, 26.04 today, pinned by digest and moved to the next LTS when that ships, with Nix and nixpkgs installed, as sneak ruled. nixpkgs is pinned to one commit of its newest release branch with a hash the build checks, so an app's build gives the same packages each time. The example app Dockerfiles add a package from nixpkgs and create the app's user with useradd, and every run script is bash with set -euo pipefail, as the style guide asks. The new base settles two of the Alpine points of the deploy follow-up. Building the image stays with milestone 2.

Model: opus-5-5
2026-09-29 02:43:55 +02:00
clawbot 7be4314f55 SPEC follows milestones 1 and 2: build order, two size limits, GeoJS answers in memory (closes #16)
SPEC.md now follows sneak's milestones. The build order starts with milestone 1 and milestone 2, each described briefly and linked, then the earlier stages less what the two milestones build; milestone 2 carries the container image, runit and the health check on /_smallwebwaf/healthz. The four body-size settings become SWWAF_REQUEST_MAX_BYTES and SWWAF_RESPONSE_MAX_BYTES, since smallwebwaf passes bodies through unchanged; the four timeouts stay. GeoJS answers are kept in memory, and writing them to lookups.json comes in milestone 3 or later, as he ruled. README.md loses the two sentences this made wrong.

Model: opus-5-5
2026-09-29 02:10:34 +02:00
clawbot ba54ecb009 Deploy model: apps build FROM the smallwebwaf image, settings prefixed SWWAF_ (closes #12)
SPEC.md and README.md now describe sneak's recommended deploy: an app's Dockerfile builds FROM the smallwebwaf image, and runit, started by runsvinit, runs smallwebwaf on :8080 in front of the app on 127.0.0.1:8081, so no setting is required. The spec covers what the app's Dockerfile adds, the users each process runs as, restarts, the health check, ports, the state directory and its volume, the app trusting loopback for forwarded headers, and upaas needing no change. An example app Dockerfile replaces the docker-compose examples. Every setting carries the SWWAF_ prefix, the file form too, and "sidecar" no longer names the deploy shape.

Model: opus-5-5
2026-09-29 01:48:47 +02:00
clawbot 789895782e Rewrite SPEC and README to sneak's rulings and internet-ready defaults (closes #6)
SPEC.md and README.md now state the resolutions of the old questions section and sneak's later requirements: seven-day bans for clear signs of attack and short, tripling bans for broken limits; state files that follow memory and take in edits while running; ban notes and per-client history; size and time limits in both directions; country deny and allow-only lists; GeoJS as the default lookup source; one listener for everything. The defaults are chosen so that a sidecar with only UPSTREAM_URL set protects an app on the open internet; the Core Rule Set reads no request bodies by default. Choices made where his words left a gap are listed in the PR. Gitea requests the defaults may still refuse are collected in a follow-up issue.

Model: opus-5-5
2026-09-29 00:53:01 +02:00
sneak a0d2c21346 Add README, SPEC and tool evaluation, closes #1
Initial documents: what smallwebwaf is and why, the proposed feature list, the design spec with the rule file format and the open design questions, and the survey of existing tools.

Model: fable-5-1
2026-09-21 07:42:45 +00:00