All checks were successful
check / check (push) Successful in 3m9s
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.
It is also bounded. database/sql exposes no incremental handle
on a SQLite blob, so scanning the column would materialise the
whole body whatever wraps it; instead the body is read in 64 KiB
ranges with substr over a blob cast and each range is written
straight to the ResponseWriter, never through renderTemplate.
Peak resident body bytes is the chunk, not the payload, which is
the memory profile the render cap was introduced to protect.
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.
9.4 KiB
9.4 KiB