Compare commits
1 Commits
67903257e8
...
84f5780884
| Author | SHA1 | Date | |
|---|---|---|---|
| 84f5780884 |
158
TODO.md
158
TODO.md
@@ -18,29 +18,17 @@ 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: 64 closed, 7 open. As of `37b59f8`, the commit this one
|
||||||
ahead of `main` and a strict fast-forward. No git tags exist yet.
|
follows, `next` is 72 commits ahead of `main` and a strict
|
||||||
|
fast-forward. No git tags exist yet.
|
||||||
|
|
||||||
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. A fix is in review
|
||||||
- `DATA_DIR` locking, so two instances cannot both deliver
|
(https://git.eeqj.de/sneak/webhooker/pulls/263). The rest of the open
|
||||||
(https://git.eeqj.de/sneak/webhooker/issues/201)
|
milestone lives on the tracker, which is the authoritative list; this
|
||||||
- shutdown on listener failure, rather than a live non-serving process
|
file does not duplicate it.
|
||||||
(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 +38,120 @@ 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`.
|
||||||
else gates it.
|
|
||||||
|
|
||||||
Post-1.0 follow-ups are open, none blocking the tag:
|
The milestone PR (https://git.eeqj.de/sneak/webhooker/pulls/111) is
|
||||||
https://git.eeqj.de/sneak/webhooker/issues/245,
|
`merge-ready` and assigned to sneak. Merging it is safe whenever sneak
|
||||||
https://git.eeqj.de/sneak/webhooker/issues/246,
|
wants it — the durability defect predates the branch and exists on
|
||||||
https://git.eeqj.de/sneak/webhooker/issues/247 and
|
`main` too — but merging it is not the tag.
|
||||||
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 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 +384,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