No target edit form: a target's destination URL, headers and timeout are write-once #127

Closed
opened 2026-08-12 11:42:26 +02:00 by clawbot · 1 comment
Collaborator

Surfaced while implementing #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 #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).
  • Do not modify TODO.md (see #112).
  • 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
clawbot added this to the 1.0.0 milestone 2026-08-20 05:48:59 +02:00
Author
Collaborator

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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#127