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
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 and Next Step now carry no statement whose truth depends on something being unmerged, in review or in progress. Out go the issue counts, the commit-ahead count and head SHA, the enumeration of open follow-ups, and the description of the milestone PR's labels and assignee. Every one of those had to be maintained by hand against a tracker and a branch that both move faster than this file does, and each had already drifted: the milestone was recorded as complete with zero open issues, one of the four listed follow-ups had closed, and a fix described as awaiting review merged while this commit was being written. The milestone page is linked instead. What remains is the durability defect that gates the tag, phrased to point at #256 rather than at the state of any branch. Completed Steps gains the units that landed since the file was last touched: #268 and #226, #250, #254, #261, #221, #211, #262, #269, #253 and #279, plus 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).
This commit is contained in:
170
TODO.md
170
TODO.md
@@ -18,29 +18,18 @@ Issue branches do NOT touch this file — the manager maintains it on
|
|||||||
|
|
||||||
# Status
|
# Status
|
||||||
|
|
||||||
1.0.0 is complete: 55 closed, 0 open. `next` (6874059) is 62 commits
|
1.0.0 is open, with work remaining. The milestone
|
||||||
ahead of `main` and a strict fast-forward. No git tags exist yet.
|
(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
|
The tag is held on a durability defect
|
||||||
use it in low-volume production". Every gap the deployability audit
|
(https://git.eeqj.de/sneak/webhooker/issues/256): a concurrent reader
|
||||||
named against that bar is now closed:
|
of a per-webhook event database strands delivered webhooks at
|
||||||
|
`pending`, and the next restart re-delivers them. That issue gates
|
||||||
- `DATA_DIR` locking, so two instances cannot both deliver
|
`v1.0.0`, and is where the fix's own state is tracked.
|
||||||
(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)
|
|
||||||
|
|
||||||
One caveat on reading a green check: a docs-only commit deliberately
|
One caveat on reading a green check: a docs-only commit deliberately
|
||||||
replays from the layer cache
|
replays from the layer cache
|
||||||
@@ -50,23 +39,129 @@ commit invalidates the `COPY` layer and genuinely executes.
|
|||||||
|
|
||||||
# Next Step
|
# Next Step
|
||||||
|
|
||||||
Merge the milestone PR (https://git.eeqj.de/sneak/webhooker/pulls/111)
|
Land https://git.eeqj.de/sneak/webhooker/issues/256, then clear the
|
||||||
and tag `v1.0.0`. It is `merge-ready` and assigned to sneak; nothing
|
rest of the open 1.0.0 milestone and tag `v1.0.0`. Merging `next` into
|
||||||
else gates it.
|
`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
|
||||||
Post-1.0 follow-ups are open, none blocking the tag:
|
branch.
|
||||||
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).
|
|
||||||
|
|
||||||
# Completed Steps
|
# 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,
|
- 2026-08-18 Raise `script/test`'s per-package timeout from 30s to 90s,
|
||||||
matching the org-wide backstop. `go test` applies `-timeout` per
|
matching the org-wide backstop. `go test` applies `-timeout` per
|
||||||
package, and `internal/handlers` had grown past the old budget: a
|
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
|
- Per-webhook rate limiting in the receiver handler (per-webhook config
|
||||||
plus handler enforcement; global limits must not apply to receiver
|
plus handler enforcement; global limits must not apply to receiver
|
||||||
endpoints)
|
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;
|
- API key authentication for programmatic access (APIKey model exists;
|
||||||
Bearer token middleware does not)
|
Bearer token middleware does not)
|
||||||
- REST API v1
|
- REST API v1
|
||||||
|
|||||||
Reference in New Issue
Block a user