Part of #165. The second of three units for the split; it follows servewaf (#170) and builds to the question, answer and report that unit wrote into SPEC.md.
What to build
smallwebwaf rproxy: a command of the same binary that stands in front of one app. It keeps nothing between requests and never touches disk. When servewaf is down, slow or overloaded, the site keeps answering.
For each request:
GET /_smallwebwaf/healthz is answered ok at once, without asking servewaf.
It works out the visitor's address as smallwebwaf does today, believing X-Forwarded-For only from SWRP_TRUSTED_PROXIES, and makes a fresh request id for the request as it arrives, which it sends servewaf and the app as X-Request-ID and writes in its log line (sneak, #165 (comment)).
It reads up to the first 64 KiB of the body into memory, within SWRP_MAX_REQUEST_TIME_MS, and sends servewaf the question as SPEC.md gives it.
On servewaf's allow, it passes the request to the app at SWRP_UPSTREAM_URL: the part it holds, then the rest of the body, streaming, and the app's answer back, within its size and time limits as today's limits work, with X-Forwarded-For, X-Request-ID and servewaf's X-Client-ASN and X-Client-Country added, and any of those two the visitor sent removed. WebSocket upgrades work as today.
On deny, the visitor gets servewaf's status, headers (less X-Smallwebwaf-Disposition) and body.
When servewaf does not answer within SWRP_TIMEOUT_MS, cannot be reached, or answers without a valid X-Smallwebwaf-Disposition, SWRP_DEFAULT_DISPOSITION decides: allow passes the request to the app, deny answers 503.
Once the answer has ended, for every request servewaf answered, it sends servewaf the report, from which servewaf writes its third, complete log line. The visitor never waits for it; a report that fails is dropped.
It writes one short JSON line per request on stdout: time, request id, client, host, method, the path and query as sent (query strings are logged verbatim: #177 (comment)), status, the bytes of each body, the request and response times, servewaf's answer, and whether SWRP_DEFAULT_DISPOSITION gave it.
Settings
Read in internal/config, the one place settings are read, with defaults equal to today's:
SWRP_WAF_BACKEND: servewaf's URL. Required, no default.
SWRP_TIMEOUT_MS (2000): how long rproxy waits for servewaf's answer.
SWRP_DEFAULT_DISPOSITION: allow (the default) or deny.
SWRP_MAX_REQUEST_SIZE (100M) and SWRP_MAX_RESPONSE_SIZE (1G): the largest bodies.
SWRP_MAX_REQUEST_TIME_MS (60000): how long the visitor may take to send its request. SWRP_MAX_RESPONSE_TIME_MS (1800000): how long the answer may take, from the end of the request to its last byte reaching the visitor. They take the place of four of today's five timeouts.
SWRP_LISTEN_ADDR (:8080), SWRP_UPSTREAM_URL (http://127.0.0.1:8081), SWRP_TRUSTED_PROXIES (the private address ranges).
The header size limit and the idle time of a kept-open connection keep today's defaults, with no setting.
Code
A new package, internal/rproxy, whose doc says it is the reverse proxy in front of one app that asks servewaf about each request. It takes what it needs of today's passing-on code in internal/proxy by copying, and leaves internal/proxy untouched; the replacement unit (#172) removes the one-process mode and the originals with it. Reviewers: that duplication is expected until then. The command is started from internal/smallwebwaf, as servewaf is.
Tests
Against a stand-in for servewaf: pass; deny with servewaf's status and body; servewaf down; servewaf too slow; fail open; fail closed (503); the first 64 KiB reaching servewaf and the whole body reaching the app; the report sent after the answer has ended, without the visitor waiting; the health check answered while servewaf is down.
Against a real servewaf, in internal/smallwebwaf's tests: one servewaf serving two sites through two rproxies, a ban made through one refusing the client on the other; servewaf stopped and started again while rproxy runs, the site answering normally throughout.
Documents
SPEC.md gets a section on rproxy, docs/settings.md its settings, docs/request-log.md its log line, README.md the command in a line. The full rewrite comes with the replacement unit.
Model: opus-5-5
Part of https://git.eeqj.de/sneak/smallwebwaf/issues/165. The second of three units for the split; it follows `servewaf` (https://git.eeqj.de/sneak/smallwebwaf/issues/170) and builds to the question, answer and report that unit wrote into `SPEC.md`.
## What to build
`smallwebwaf rproxy`: a command of the same binary that stands in front of one app. It keeps nothing between requests and never touches disk. When servewaf is down, slow or overloaded, the site keeps answering.
For each request:
1. `GET /_smallwebwaf/healthz` is answered `ok` at once, without asking servewaf.
2. It works out the visitor's address as `smallwebwaf` does today, believing `X-Forwarded-For` only from `SWRP_TRUSTED_PROXIES`, and makes a fresh request id for the request as it arrives, which it sends servewaf and the app as `X-Request-ID` and writes in its log line (sneak, https://git.eeqj.de/sneak/smallwebwaf/issues/165#issuecomment-167908).
3. It reads up to the first 64 KiB of the body into memory, within `SWRP_MAX_REQUEST_TIME_MS`, and sends servewaf the question as `SPEC.md` gives it.
4. On servewaf's `allow`, it passes the request to the app at `SWRP_UPSTREAM_URL`: the part it holds, then the rest of the body, streaming, and the app's answer back, within its size and time limits as today's limits work, with `X-Forwarded-For`, `X-Request-ID` and servewaf's `X-Client-ASN` and `X-Client-Country` added, and any of those two the visitor sent removed. WebSocket upgrades work as today.
5. On `deny`, the visitor gets servewaf's status, headers (less `X-Smallwebwaf-Disposition`) and body.
6. When servewaf does not answer within `SWRP_TIMEOUT_MS`, cannot be reached, or answers without a valid `X-Smallwebwaf-Disposition`, `SWRP_DEFAULT_DISPOSITION` decides: `allow` passes the request to the app, `deny` answers `503`.
7. Once the answer has ended, for every request servewaf answered, it sends servewaf the report, from which servewaf writes its third, complete log line. The visitor never waits for it; a report that fails is dropped.
8. It writes one short JSON line per request on stdout: time, request id, client, host, method, the path and query as sent (query strings are logged verbatim: https://git.eeqj.de/sneak/smallwebwaf/issues/177#issuecomment-167904), status, the bytes of each body, the request and response times, servewaf's answer, and whether `SWRP_DEFAULT_DISPOSITION` gave it.
## Settings
Read in `internal/config`, the one place settings are read, with defaults equal to today's:
- `SWRP_WAF_BACKEND`: servewaf's URL. Required, no default.
- `SWRP_TIMEOUT_MS` (`2000`): how long rproxy waits for servewaf's answer.
- `SWRP_DEFAULT_DISPOSITION`: `allow` (the default) or `deny`.
- `SWRP_MAX_REQUEST_SIZE` (`100M`) and `SWRP_MAX_RESPONSE_SIZE` (`1G`): the largest bodies.
- `SWRP_MAX_REQUEST_TIME_MS` (`60000`): how long the visitor may take to send its request. `SWRP_MAX_RESPONSE_TIME_MS` (`1800000`): how long the answer may take, from the end of the request to its last byte reaching the visitor. They take the place of four of today's five timeouts.
- `SWRP_LISTEN_ADDR` (`:8080`), `SWRP_UPSTREAM_URL` (`http://127.0.0.1:8081`), `SWRP_TRUSTED_PROXIES` (the private address ranges).
The header size limit and the idle time of a kept-open connection keep today's defaults, with no setting.
## Code
A new package, `internal/rproxy`, whose doc says it is the reverse proxy in front of one app that asks servewaf about each request. It takes what it needs of today's passing-on code in `internal/proxy` by copying, and leaves `internal/proxy` untouched; the replacement unit (https://git.eeqj.de/sneak/smallwebwaf/issues/172) removes the one-process mode and the originals with it. Reviewers: that duplication is expected until then. The command is started from `internal/smallwebwaf`, as `servewaf` is.
## Tests
Against a stand-in for servewaf: pass; deny with servewaf's status and body; servewaf down; servewaf too slow; fail open; fail closed (`503`); the first 64 KiB reaching servewaf and the whole body reaching the app; the report sent after the answer has ended, without the visitor waiting; the health check answered while servewaf is down.
Against a real servewaf, in `internal/smallwebwaf`'s tests: one servewaf serving two sites through two rproxies, a ban made through one refusing the client on the other; servewaf stopped and started again while rproxy runs, the site answering normally throughout.
## Documents
`SPEC.md` gets a section on `rproxy`, `docs/settings.md` its settings, `docs/request-log.md` its log line, `README.md` the command in a line. The full rewrite comes with the replacement unit.
Model: opus-5-5
clawbot
self-assigned this 2026-11-04 20:09:56 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #165. The second of three units for the split; it follows
servewaf(#170) and builds to the question, answer and report that unit wrote intoSPEC.md.What to build
smallwebwaf rproxy: a command of the same binary that stands in front of one app. It keeps nothing between requests and never touches disk. When servewaf is down, slow or overloaded, the site keeps answering.For each request:
GET /_smallwebwaf/healthzis answeredokat once, without asking servewaf.smallwebwafdoes today, believingX-Forwarded-Foronly fromSWRP_TRUSTED_PROXIES, and makes a fresh request id for the request as it arrives, which it sends servewaf and the app asX-Request-IDand writes in its log line (sneak, #165 (comment)).SWRP_MAX_REQUEST_TIME_MS, and sends servewaf the question asSPEC.mdgives it.allow, it passes the request to the app atSWRP_UPSTREAM_URL: the part it holds, then the rest of the body, streaming, and the app's answer back, within its size and time limits as today's limits work, withX-Forwarded-For,X-Request-IDand servewaf'sX-Client-ASNandX-Client-Countryadded, and any of those two the visitor sent removed. WebSocket upgrades work as today.deny, the visitor gets servewaf's status, headers (lessX-Smallwebwaf-Disposition) and body.SWRP_TIMEOUT_MS, cannot be reached, or answers without a validX-Smallwebwaf-Disposition,SWRP_DEFAULT_DISPOSITIONdecides:allowpasses the request to the app,denyanswers503.SWRP_DEFAULT_DISPOSITIONgave it.Settings
Read in
internal/config, the one place settings are read, with defaults equal to today's:SWRP_WAF_BACKEND: servewaf's URL. Required, no default.SWRP_TIMEOUT_MS(2000): how long rproxy waits for servewaf's answer.SWRP_DEFAULT_DISPOSITION:allow(the default) ordeny.SWRP_MAX_REQUEST_SIZE(100M) andSWRP_MAX_RESPONSE_SIZE(1G): the largest bodies.SWRP_MAX_REQUEST_TIME_MS(60000): how long the visitor may take to send its request.SWRP_MAX_RESPONSE_TIME_MS(1800000): how long the answer may take, from the end of the request to its last byte reaching the visitor. They take the place of four of today's five timeouts.SWRP_LISTEN_ADDR(:8080),SWRP_UPSTREAM_URL(http://127.0.0.1:8081),SWRP_TRUSTED_PROXIES(the private address ranges).The header size limit and the idle time of a kept-open connection keep today's defaults, with no setting.
Code
A new package,
internal/rproxy, whose doc says it is the reverse proxy in front of one app that asks servewaf about each request. It takes what it needs of today's passing-on code ininternal/proxyby copying, and leavesinternal/proxyuntouched; the replacement unit (#172) removes the one-process mode and the originals with it. Reviewers: that duplication is expected until then. The command is started frominternal/smallwebwaf, asservewafis.Tests
Against a stand-in for servewaf: pass; deny with servewaf's status and body; servewaf down; servewaf too slow; fail open; fail closed (
503); the first 64 KiB reaching servewaf and the whole body reaching the app; the report sent after the answer has ended, without the visitor waiting; the health check answered while servewaf is down.Against a real servewaf, in
internal/smallwebwaf's tests: one servewaf serving two sites through two rproxies, a ban made through one refusing the client on the other; servewaf stopped and started again while rproxy runs, the site answering normally throughout.Documents
SPEC.mdgets a section onrproxy,docs/settings.mdits settings,docs/request-log.mdits log line,README.mdthe command in a line. The full rewrite comes with the replacement unit.Model: opus-5-5
Built as #181. Its body is the record of what changed and the judgement calls.
Model: opus-5-5