From 67903257e80cd37c5b9cba94c52d5757607fc034 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 24 Aug 2026 01:34:06 +0000 Subject: [PATCH] Record landed 1.0.0 work and drop the removed signing roadmap in TODO.md Inbound signature verification was removed on next (#279), so the Future Step proposing a Stripe HMAC scheme "landed with inbound verification" proposed extending a subsystem that no longer exists, and the Status section still counted #67 among the deployability gaps it had closed. Both are gone. Status now states the milestone qualitatively: open, with work remaining, the tag held on #256 and a fix in review. It carries no issue counts and no commit-ahead count, and no longer enumerates the open follow-ups. Every one of those had to be maintained by hand against a tracker that moves faster than this file does, and each had already drifted: the milestone was recorded as complete with zero open issues, and one of the four listed follow-ups had closed. The milestone page is linked instead, as the authoritative list and the only place to read a count from. Completed Steps gains the nine units that landed since the file was last touched (#250, #254, #261, #221, #211, #262, #269, #253, #279) and the 2026-08-20 units that were only ever summarised in Status prose or missing entirely (#201, #200, #205, #206, #207, #234, #202, #203, #209, #210, #208, #204, #233, #127). #226 is deliberately NOT recorded as landed. Its fix exists only on an unmerged branch; no commit on next addresses it, and cleanShutdown still reads s.httpServer and s.sentryEnabled unguarded. --- TODO.md | 159 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 121 insertions(+), 38 deletions(-) diff --git a/TODO.md b/TODO.md index 39b1a36..c933402 100644 --- a/TODO.md +++ b/TODO.md @@ -18,29 +18,18 @@ Issue branches do NOT touch this file — the manager maintains it on # Status -1.0.0 is complete: 55 closed, 0 open. `next` (6874059) is 62 commits -ahead of `main` and a strict fast-forward. No git tags exist yet. +1.0.0 is open, with work remaining. The milestone +(https://git.eeqj.de/sneak/webhooker/milestone/9) is the authoritative +list and the only place to read a count from; this file deliberately +carries neither, because both drift between the commits that touch it. +`next` is ahead of `main` and a strict fast-forward. No release has +been tagged yet. -The bar was not "the milestone is empty" but "sneak can deploy this and -use it in low-volume production". Every gap the deployability audit -named against that bar is now closed: - -- `DATA_DIR` locking, so two instances cannot both deliver - (https://git.eeqj.de/sneak/webhooker/issues/201) -- shutdown on listener failure, rather than a live non-serving process - (https://git.eeqj.de/sneak/webhooker/issues/200) -- inbound signature verification - (https://git.eeqj.de/sneak/webhooker/issues/67) -- per-attempt delivery detail in the event log - (https://git.eeqj.de/sneak/webhooker/issues/202) -- replay of a terminally failed delivery - (https://git.eeqj.de/sneak/webhooker/issues/203) -- `ALLOWED_EGRESS_CIDRS`, an allowlist escape hatch for the SSRF guard - (https://git.eeqj.de/sneak/webhooker/issues/204) -- the three credential exposures - (https://git.eeqj.de/sneak/webhooker/issues/205, - https://git.eeqj.de/sneak/webhooker/issues/206, - https://git.eeqj.de/sneak/webhooker/issues/207) +The tag is held on a durability defect +(https://git.eeqj.de/sneak/webhooker/issues/256): a concurrent reader +of a per-webhook event database strands delivered webhooks at +`pending`, and the next restart re-delivers them. A fix is in review +(https://git.eeqj.de/sneak/webhooker/pulls/263). One caveat on reading a green check: a docs-only commit deliberately replays from the layer cache @@ -50,23 +39,120 @@ commit invalidates the `COPY` layer and genuinely executes. # Next Step -Merge the milestone PR (https://git.eeqj.de/sneak/webhooker/pulls/111) -and tag `v1.0.0`. It is `merge-ready` and assigned to sneak; nothing -else gates it. +Land https://git.eeqj.de/sneak/webhooker/issues/256, then clear the +rest of the open 1.0.0 milestone and tag `v1.0.0`. -Post-1.0 follow-ups are open, none blocking the tag: -https://git.eeqj.de/sneak/webhooker/issues/245, -https://git.eeqj.de/sneak/webhooker/issues/246, -https://git.eeqj.de/sneak/webhooker/issues/247 and -https://git.eeqj.de/sneak/webhooker/issues/248. Also still open and -unmilestoned: https://git.eeqj.de/sneak/webhooker/issues/193 (a design -question, not a defect), https://git.eeqj.de/sneak/webhooker/issues/198 -(`make test` is past the org 20s target) and -https://git.eeqj.de/sneak/webhooker/issues/212 (encrypting target config -at rest). +The milestone PR (https://git.eeqj.de/sneak/webhooker/pulls/111) is +`merge-ready` and assigned to sneak. Merging it is safe whenever sneak +wants it — the durability defect predates the branch and exists on +`main` too — but merging it is not the tag. # Completed Steps +- 2026-08-24 Remove inbound request signature verification. The + entrypoint UUID is the authentication secret, so the per-entrypoint + shared secret, the `internal/signature` package, the receiver check, + the model fields and the forms are all gone. This reverses the + feature that landed earlier in the same milestone + (https://git.eeqj.de/sneak/webhooker/issues/67, + https://git.eeqj.de/sneak/webhooker/issues/279) +- 2026-08-24 Stamp the build version into the binary and render it in + the UI footer. `script/version` is the single source — `$VERSION`, + else `git describe --tags --always --dirty`, else `unknown` — so a + `make build` binary and a `make docker` image from one checkout + report the same thing, and nothing in it varies between two builds + of the same commit, which the release gate's byte-identical + assertion would catch + (https://git.eeqj.de/sneak/webhooker/issues/253) +- 2026-08-24 Derive cookie `Secure` and CSRF strictness from the + request transport rather than from `WEBHOOKER_ENVIRONMENT`. Behind a + real TLS proxy with the environment left at its `dev` default, the + session cookie silently lost `Secure` while the CSRF cookie on the + same response kept it. `X-Forwarded-Proto` is now matched + case-insensitively on its first comma-separated element, so `HTTPS` + and `https, http` no longer fall to the relaxed CSRF path + (https://git.eeqj.de/sneak/webhooker/issues/269) +- 2026-08-24 Roll back a failed webhook deletion instead of committing + it. A failing delete committed whatever had already succeeded, + hard-deleted the per-webhook event database anyway, and redirected as + though it had worked — orphaned config plus permanently destroyed + history, reported as success. All three delete positions now roll + back with the event database intact + (https://git.eeqj.de/sneak/webhooker/issues/262) +- 2026-08-24 Name a deleted target on its historical deliveries, marked + `(deleted)`, rather than leaving the event log unable to say where a + delivery went. A deleted target's credentials stay masked exactly as + a live one's, and it cannot become deliverable again through the + receiver, resubmit, replay, the edit form or the toggle + (https://git.eeqj.de/sneak/webhooker/issues/211) +- 2026-08-24 Bound both request-controlled `/metrics` label dimensions, + so the unauthenticated receiver is no longer a memory-exhaustion + vector: `handler` carries the chi route pattern, and `method` folds + anything chi cannot route onto a single `(unmatched)` sentinel. Both + were reproduced before the fix — 300 random method tokens took the + series count from 106 to 7,631, and path flooding reached 62,532 — + and a label audit across a live scrape found no third unbounded + dimension (https://git.eeqj.de/sneak/webhooker/issues/254, + https://git.eeqj.de/sneak/webhooker/issues/261) +- 2026-08-24 Validate `max_retries` on both target forms. `abc`, `2.7` + and `-5` silently became 0 — fire-and-forget — including on the edit + path, where it destroyed a working value, and `999999999` stored + verbatim. The ceiling of 20 is the `max` both templates already + declared (https://git.eeqj.de/sneak/webhooker/issues/221) +- 2026-08-24 Resubmit a stored event as a new undelivered event, so a + backend under development can be tested against real captured + traffic. Per-delivery replay cannot serve that: it re-sends one + finished delivery to its own original target, and a target created + for a dev backend has no prior delivery to replay. Resubmit + re-injects the stored event at the top of the receiver path and fans + it out to whatever targets are active now + (https://git.eeqj.de/sneak/webhooker/issues/250) +- 2026-08-20 Take an exclusive lock on `DATA_DIR` at startup, so two + instances on one directory cannot both deliver + (https://git.eeqj.de/sneak/webhooker/issues/201) +- 2026-08-20 Shut down the app when the HTTP listener fails. The + `OnStart` hook returned as soon as the serving goroutine was + spawned, so a failed listen left fx reporting RUNNING and a live + process with nothing bound — invisible to systemd and Docker restart + policies (https://git.eeqj.de/sneak/webhooker/issues/200) +- 2026-08-20 Stop target credentials leaking into the per-webhook event + databases (https://git.eeqj.de/sneak/webhooker/issues/206), log SQL + with placeholders rather than bound values + (https://git.eeqj.de/sneak/webhooker/issues/207), and fail loudly on + half-set metrics auth credentials + (https://git.eeqj.de/sneak/webhooker/issues/205) +- 2026-08-20 Read queue depths with `Find`, not `Scan`. `Scan` swaps + GORM's own trace recorder in for the logging adapter, and that + recorder does not implement `gorm.ParamsFilter`, so those statements + logged their bound values interpolated and bypassed the suppression + above. The two units gated green against a `next` that lacked the + other, and `next` went red when both landed + (https://git.eeqj.de/sneak/webhooker/issues/234) +- 2026-08-20 Render per-attempt delivery detail in the event log + (https://git.eeqj.de/sneak/webhooker/issues/202) and add replay of a + terminally failed delivery + (https://git.eeqj.de/sneak/webhooker/issues/203) +- 2026-08-20 Expose delivery metrics on `/metrics` + (https://git.eeqj.de/sneak/webhooker/issues/209) and document the + backup, restore and upgrade procedures + (https://git.eeqj.de/sneak/webhooker/issues/210) +- 2026-08-20 Add a `webhooker resetpw` subcommand and a bootstrap + banner. The admin bootstrap password was printed once among roughly + 45 fx lines, and under `docker run -d` went to container logs subject + to rotation; there was no reset path at all, so recovery meant + hand-deleting the users row, documented nowhere. The password is read + from stdin or generated, never from argv where `/proc` would publish + it (https://git.eeqj.de/sneak/webhooker/issues/208) +- 2026-08-20 Add `ALLOWED_EGRESS_CIDRS`, an allowlist-only escape hatch + for the SSRF guard, so a self-hosted proxy can forward into the + operator's own network. The guard's always-blocked set cannot be + reopened by configuration + (https://git.eeqj.de/sneak/webhooker/issues/204) +- 2026-08-20 Harden operator-set target headers, which were carried + unsafely across a redirect + (https://git.eeqj.de/sneak/webhooker/issues/233) +- 2026-08-20 Add a target edit form with headers and timeout fields + (https://git.eeqj.de/sneak/webhooker/issues/127) - 2026-08-18 Raise `script/test`'s per-package timeout from 30s to 90s, matching the org-wide backstop. `go test` applies `-timeout` per package, and `internal/handlers` had grown past the old budget: a @@ -299,9 +385,6 @@ at rest). - Per-webhook rate limiting in the receiver handler (per-webhook config plus handler enforcement; global limits must not apply to receiver endpoints) -- Stripe HMAC signature verification. The GitHub and GitLab schemes - landed with inbound verification - (https://git.eeqj.de/sneak/webhooker/issues/67) - API key authentication for programmatic access (APIKey model exists; Bearer token middleware does not) - REST API v1