internal/server/routes.go registers only POST /source/{sourceID}/targets, .../targets/{targetID}/toggle and .../targets/{targetID}/delete. There is no target edit route, and templates/source_edit.html edits the webhook's name, description and retention only.
So a target's configuration cannot be changed after creation — to fix a typo in a destination URL, an operator deletes the target and recreates it. Today the stored value is at least readable on the source detail page; once #126 masks it, it becomes write-once and unrecoverable from the UI entirely.
That is the direct cost of the masking decision in #115, and this issue is what repays it.
Definition of done
GET and POST /source/{sourceID}/targets/{targetID}/edit, with a templates/target_edit.html that pre-fills the stored destination URL, headers and timeout.
Save reuses buildTargetConfig so SSRF validation applies on edit exactly as on create. An edit path that skips SSRF validation would reopen a closed hole.
A handler test covering the round trip: create a target, edit its destination URL, confirm the stored config changed and the new value is validated.
Editing must not resurrect the credential leak elsewhere — the pre-filled form is the one place the full value is intentionally shown.
Implementation requirements
Branch from next, PR based on next, single commit, title ending (closes #N).
Gate on make check plus the Docker lint path with the cache defeated (#119).
Surfaced while implementing https://git.eeqj.de/sneak/webhooker/pulls/126.
`internal/server/routes.go` registers only `POST /source/{sourceID}/targets`, `.../targets/{targetID}/toggle` and `.../targets/{targetID}/delete`. There is no target edit route, and `templates/source_edit.html` edits the webhook's name, description and retention only.
So a target's configuration cannot be changed after creation — to fix a typo in a destination URL, an operator deletes the target and recreates it. Today the stored value is at least readable on the source detail page; once https://git.eeqj.de/sneak/webhooker/pulls/126 masks it, it becomes write-once and unrecoverable from the UI entirely.
That is the direct cost of the masking decision in https://git.eeqj.de/sneak/webhooker/issues/115, and this issue is what repays it.
## Definition of done
- `GET` and `POST /source/{sourceID}/targets/{targetID}/edit`, with a `templates/target_edit.html` that pre-fills the stored destination URL, headers and timeout.
- Save reuses `buildTargetConfig` so SSRF validation applies on edit exactly as on create. An edit path that skips SSRF validation would reopen a closed hole.
- A handler test covering the round trip: create a target, edit its destination URL, confirm the stored config changed and the new value is validated.
- Editing must not resurrect the credential leak elsewhere — the pre-filled form is the one place the full value is intentionally shown.
## Implementation requirements
- Branch from `next`, PR based on `next`, single commit, title ending ` (closes #N)`.
- Do not modify `TODO.md` (see https://git.eeqj.de/sneak/webhooker/issues/112).
- Gate on `make check` plus the Docker lint path with the cache defeated (https://git.eeqj.de/sneak/webhooker/issues/119).
clawbot
self-assigned this 2026-08-12 11:42:26 +02:00
Milestoned to 1.0.0 on the 2026-08-20 deployability audit, with a correction to the framing above.
Write-once is the smaller half. HTTPTargetConfig has Headers map[string]string and Timeout int (internal/delivery/target_http.go:30-34) and both are honoured at delivery time (:402-409, :486-488), but buildURLTargetConfig (internal/handlers/source_management.go:1148-1195) only ever writes {"url":...}. There is no form field for either, on create or anywhere else — so headers and timeout are not write-once, they are unreachable from the UI entirely. A destination needing an Authorization header cannot be configured at all today.
So the edit form must also complete the create form: both paths set URL, headers and timeout, both run buildTargetConfig and therefore SSRF validation.
Also verified, and it bounds how urgent the edit path is: deleting a target does NOT lose delivery history. Deletes are soft and deliveries carry no FK to the target row, so the rows survive — but loadTargetMap excludes soft-deleted targets and the log then renders a blank name. Filed separately as #211.
Milestoned to `1.0.0` on the 2026-08-20 deployability audit, with a correction to the framing above.
Write-once is the smaller half. `HTTPTargetConfig` has `Headers map[string]string` and `Timeout int` (`internal/delivery/target_http.go:30-34`) and both are honoured at delivery time (`:402-409`, `:486-488`), but `buildURLTargetConfig` (`internal/handlers/source_management.go:1148-1195`) only ever writes `{"url":...}`. There is no form field for either, on create or anywhere else — so headers and timeout are not write-once, they are unreachable from the UI entirely. A destination needing an `Authorization` header cannot be configured at all today.
So the edit form must also complete the create form: both paths set URL, headers and timeout, both run `buildTargetConfig` and therefore SSRF validation.
Also verified, and it bounds how urgent the edit path is: deleting a target does NOT lose delivery history. Deletes are soft and deliveries carry no FK to the target row, so the rows survive — but `loadTargetMap` excludes soft-deleted targets and the log then renders a blank name. Filed separately as #211.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Surfaced while implementing #126.
internal/server/routes.goregisters onlyPOST /source/{sourceID}/targets,.../targets/{targetID}/toggleand.../targets/{targetID}/delete. There is no target edit route, andtemplates/source_edit.htmledits the webhook's name, description and retention only.So a target's configuration cannot be changed after creation — to fix a typo in a destination URL, an operator deletes the target and recreates it. Today the stored value is at least readable on the source detail page; once #126 masks it, it becomes write-once and unrecoverable from the UI entirely.
That is the direct cost of the masking decision in #115, and this issue is what repays it.
Definition of done
GETandPOST /source/{sourceID}/targets/{targetID}/edit, with atemplates/target_edit.htmlthat pre-fills the stored destination URL, headers and timeout.buildTargetConfigso SSRF validation applies on edit exactly as on create. An edit path that skips SSRF validation would reopen a closed hole.Implementation requirements
next, PR based onnext, single commit, title ending(closes #N).TODO.md(see #112).make checkplus the Docker lint path with the cache defeated (#119).Milestoned to
1.0.0on the 2026-08-20 deployability audit, with a correction to the framing above.Write-once is the smaller half.
HTTPTargetConfighasHeaders map[string]stringandTimeout int(internal/delivery/target_http.go:30-34) and both are honoured at delivery time (:402-409,:486-488), butbuildURLTargetConfig(internal/handlers/source_management.go:1148-1195) only ever writes{"url":...}. There is no form field for either, on create or anywhere else — so headers and timeout are not write-once, they are unreachable from the UI entirely. A destination needing anAuthorizationheader cannot be configured at all today.So the edit form must also complete the create form: both paths set URL, headers and timeout, both run
buildTargetConfigand therefore SSRF validation.Also verified, and it bounds how urgent the edit path is: deleting a target does NOT lose delivery history. Deletes are soft and deliveries carry no FK to the target row, so the rows survive — but
loadTargetMapexcludes soft-deleted targets and the log then renders a blank name. Filed separately as #211.clawbot referenced this issue2026-08-20 11:07:50 +02:00