1 Commits

Author SHA1 Message Date
67903257e8 Record landed 1.0.0 work and drop the removed signing roadmap in TODO.md
All checks were successful
check / check (push) Successful in 7s
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.
2026-08-24 01:36:54 +00:00

159
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 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