# Workflow One issue per unit of work, one branch and one PR per issue: * ensure a tracked issue exists with a definition of done * branch from `next` (never from `main`) * do the work; open a PR based on `next` (never on `main`) * pass an independent review, then the manager squash-merges into `next` * push; nothing stays local-only `next` is the branch for the next milestone and must stay green and mergeable to `main` without notice. One `next` -> `main` PR accumulates the milestone; releases are cut from `main` separately. Issue branches do NOT touch this file — the manager maintains it on `next`. Every branch editing `TODO.md` conflicts with every other (#112). # Status pre-1.0. No git tags exist. `main` (4f5ecb1) is a working webhook proxy with auth, CSRF/SSRF protections, login rate limiting, Slack target, event retention (#63), the database archiving target (#43), the admin password change flow (#65), policy compliance (#6), pinned lint tooling (#55), and fail-loud configuration parsing (#80). `next` (9bfd033) holds the completed 1.0.0 milestone: every issue in it is closed, and it is verified green by cache-defeated container runs rather than by the CI badge, which can pass without executing anything (#119). Note: TODO.md was deliberately deleted from this repo in f9a9569 (2026-03-01, #6); its content was folded into the README TODO section, which this draft reconstructs as of 2026-07-06. # Next Step Tag 1.0.0 from `main` once the milestone PR merges, then repair the CI gate (#119) before the next cycle's work lands — a gate that can report success without running is the one thing every other guarantee here rests on. # Completed Steps - 2026-08-12 Bound the `X-Forwarded-For` scan's allocation to the hop cap: the reverse walk cuts entries with `strings.LastIndexByte` instead of joining and splitting, so a 1 MB header allocates 16 bytes rather than 1.6 MB per request on the unauthenticated receiver. Semantics proven unchanged by differential testing against the previous implementation (#133) - 2026-08-12 Cap the `X-Forwarded-For` hop walk at 64 entries, so an attacker-supplied chain cannot burn unbounded CPU in the rate-limit key function; running off the end falls back to the peer address (#124) - 2026-08-12 Gate forwarded-header trust behind a `TRUSTED_PROXIES` CIDR list: all three rate limiters key on the connection's own address unless the direct peer is a configured proxy, in which case `X-Forwarded-For` is walked right to left for the first non-proxy hop. Default trusts nothing, and a set-but-unparseable value aborts startup. Before this, any client could mint a fresh bucket or drain another's by rotating a spoofed header (#88) - 2026-08-11 Web UI cleanup: nav terminology unified on Webhooks, the Profile settings placeholder removed, a progressive-enhancement copy button for the entrypoint URL, and retention form copy that states the actual policy (deletion by the reaper, 0 retains forever) (#57) - 2026-08-09 Inactivity-based session timeout: sliding idle expiry (`SESSION_IDLE_TIMEOUT`, default `24h`) refreshed on authenticated requests, with the 7-day absolute cap kept as an independent backstop that activity never extends (#66) - 2026-08-09 Restart recovery and the 60s retry sweep terminally fail an orphaned `retrying` delivery whose target type no longer supports retries, recording a `DeliveryResult` with the reason instead of leaving the delivery stuck forever (#82) - 2026-08-09 Root the delivery engine's worker pool and the retention reaper's sweep loop at `context.Background()` rather than the fx `OnStart` hook context (#97), which carries fx's 15s start timeout and killed both roughly fifteen seconds after boot: the proxy silently stopped delivering webhooks entirely, and the reaper never ran a single sweep under its default one-hour interval - 2026-08-09 Archive writer lifecycle (#89): deleting a webhook (or its last `database` target) evicts the cached archive writer and closes its handle while deliberately leaving `archive-{webhookID}.db` on disk, and a new `ArchiveSweeper` prunes idle archives on the existing `RETENTION_SWEEP_INTERVAL` without ever creating an archive file - 2026-08-09 Configuration parsing fails loudly on set-but-unparseable environment values: `envInt` removed in favour of `envPositiveInt` plus a `PORT` range check, `envBool` now parses with `strconv.ParseBool`, and defaults apply only to unset variables (#80) - 2026-08-07 Automatic event retention cleanup based on `retention_days`, deleting expired events, deliveries, and delivery results from each per-webhook event database (#63) - 2026-08-07 Update golangci-lint to v2.12.2 (Docker image digest in `Dockerfile`, release-archive sha256 pins in `script/bootstrap`), adopt the canonical `.golangci.yml` (v2 `linters.settings` layout so `lll`/`funlen`/`cyclop`/`dupl` thresholds actually apply), and fix all newly surfaced lint findings - 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, Makefile shims, README Entrypoints section - 2026-03-25 pin golangci-lint Docker image for linting (#55) - 2026-03-18 CSRF middleware detects TLS per-request, fixing login over plain HTTP and behind reverse proxies (#54) - 2026-03-17 root path redirects based on auth state (#52) - 2026-03-17 CSRF protection, SSRF prevention for HTTP delivery targets with DNS rebinding defense, and per-IP login rate limiting (#42) - 2026-03-17 Slack target type for incoming webhook notifications (#47) - 2026-03-17 Dockerfile absolute paths and static linking (#49); absolute dev DATA_DIR default and clarified env docs (#46) - 2026-03-05 security headers middleware, session regeneration on login, request body size limits (#41) - 2026-03-04 tests for delivery, middleware, and session packages (#32); removed globals.Buildarch (#31) - 2026-03-04 1.0 MVP merge: Webhook/Entrypoint/Target rename, core delivery engine with bounded worker pool and circuit breaker, parallel fan-out, per-webhook event databases, management UI (#16) - 2026-03-01 repo brought to REPO_POLICIES standards; TODO.md folded into README (#6) # Future Steps - Delivery status and retry management UI - Per-webhook rate limiting in the receiver handler (per-webhook config plus handler enforcement; global limits must not apply to receiver endpoints) - Webhook signature verification for GitHub and Stripe HMAC formats - API key authentication for programmatic access (APIKey model exists; Bearer token middleware does not) - REST API v1 - CRUD for webhooks, entrypoints, targets - event viewing and filtering endpoints - event redelivery endpoint - OpenAPI specification - Analytics dashboard: success rates, response times, volume - A remember-me option at login - Password change and reset flow - Later, nice to have - email delivery target type - SNS and S3 delivery targets - data transformations (e.g. webhook to Slack message formatting) - JSONL file delivery with periodic S3 upload - webhook event search and filtering - multi-user with role-based access control