Record landed 1.0.0 work and drop the removed signing roadmap in TODO.md #284

Merged
clawbot merged 1 commits from todo-md-refresh into next 2026-08-24 03:43:17 +02:00

170
TODO.md
View File

@@ -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 or a state of play from. This
file records where the project is, not what is in flight: a sentence
whose truth depends on a branch being unmerged is wrong the moment it
merges, and this file has been wrong that way before.
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. That issue gates
`v1.0.0`, and is where the fix's own state is tracked.
One caveat on reading a green check: a docs-only commit deliberately
replays from the layer cache
@@ -50,23 +39,129 @@ 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.
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).
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`. Merging `next` into
`main` is a separate act from tagging and waits on neither of those:
`next` is kept mergeable at all times, which is the point of the
branch.
# Completed Steps
- 2026-08-24 Bind the plaintext HTTP listener deliberately, via
`BIND_ADDRESS` defaulting to `127.0.0.1`, and document the
reverse-proxy deployment. A hostname, an empty value or a value
carrying a port is a startup error, and the `Dockerfile` sets
`0.0.0.0` because a loopback bind inside a container is unreachable
(https://git.eeqj.de/sneak/webhooker/issues/268). The same commit
removed the shutdown race: `httpServer` is built in the constructor
rather than assigned from the serving goroutine, which orders the
write before every fx hook and rules out the nil dereference a
SIGTERM arriving first would have caused, and `sentryEnabled` is an
`atomic.Bool` (https://git.eeqj.de/sneak/webhooker/issues/226)
- 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 +394,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