Files
webhooker/templates/source_logs.html
clawbot b1cf0de216
Some checks failed
check / check (push) Has been cancelled
Serve an event's full stored body over HTTP (closes #157)
Capping the event log page at 8 KB of body per event left no
in-app way to see a larger one: storage keeps it, but no route
served it, so a payload over the cap was reachable only by an
operator with filesystem access. GitHub pull_request and
multi-commit push payloads, expanded Stripe events and Shopify
orders all routinely clear 8 KB, which is exactly when the tool
is supposed to be useful.

GET /source/{sourceID}/logs/{eventID}/body now serves one whole
body, and the truncation marker links to it when — and only
when — a body was actually cut.

The response is deliberately inert. Its bytes are chosen by
whoever can reach the public receiver and it hands them back
inside the operator's own authenticated origin, so it goes out
as application/octet-stream with Content-Disposition: attachment
and nosniff, and the filename is built from a parsed uuid rather
than from anything in the request. The application CSP is no
help on this path: script-src allows 'unsafe-inline' from
'self', so a document served from this origin could run its own
script.

The body is read in one query and held whole while it is
written. There is no cheaper bound to take. database/sql
exposes no incremental handle on a SQLite blob, and reading
byte ranges with substr does not avoid the cost either: SQLite
materialises the entire column value to evaluate each substr
call, so range reads pay for the whole body once per range
rather than once per download. Measured over a 1 MiB body,
64 KiB ranges cost 11-15x a single read to move the same bytes.
The bound is therefore the one the issue asks for: the route is
owner-authenticated and ingest is capped at 1 MB, so peak is
one body per concurrent download. Nothing goes through
renderTemplate, which buffers a whole response before writing
it.

Reading the body before the first header is written also means
an event reaped mid-request cannot produce a torn response: it
is either served whole or 404s cleanly, and both are tested.

The ownership check the log page applies is extracted as
ownedWebhook and shared with the download, so the two cannot
drift apart. A webhook owned by someone else and one that does
not exist are the same 404.

The route registration and the link the template emits are
covered end to end through the production router, so a typo in
either fails the suite rather than leaving the feature dead
behind green handler tests.
2026-08-17 21:49:24 +00:00

3.4 KiB