Slack webhook credential still leaks via delivery errors, SSRF logs, and the source_logs template data #118
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found during the review of #114, which masked the credential in the source detail page. These three sibling paths leak the same secret and were outside that PR's scope. Milestoned 1.0.0 for the same reason #113 was: the milestone is internet-facing readiness.
The first one is the most serious, because it writes the credential to disk.
internal/delivery/target_slack.go:128,142andinternal/delivery/target_http.go:337wrap the*url.Errorfrom the HTTP client intoDeliveryResult.Error. Go embeds the request URL in that error, so any transport failure — DNS, TLS, timeout, SSRF dial block — storesPost "https://hooks.slack.com/services/T../B../XXXX": dial tcp ...into the per-webhook SQLite database. It is not rendered today, but the field is taggedjson:"error,omitempty", so the REST API on the roadmap serializes it the moment it exists.internal/handlers/source_management.go:1019—h.log.Warn("target URL blocked by SSRF protection", "url", targetURL, ...)writes the full URL to the application log.source_logs.htmlstill receives raw config.internal/handlers/source_management.go:757attaches rawdatabase.Targetvalues, config included, to that page's template data. Nothing renders it today, so it is one{{.Target.Config}}away from reintroducing the exact bug #113 just fixed — the view projection does not cover this page.Definition of done
*url.Errorinto a message carrying onlymaskURL(...)before it reachesDeliveryResult.Error.source_management.go:1019logs the masked URL.loadTargetMaphandssource_logs.htmlaTargetViewrather than a rawdatabase.Target, so the raw config is unreachable from that template too.DeliveryResult.Errorcontains no path segment of the configured webhook URL. This is the load-bearing one.Implementation requirements
maskURLininternal/delivery/target_config_view.go; do not write a second masker.next, PR based onnext, single commit, title ending(closes #N).make checkplus the Docker lint path with the cache defeated — a fully cachedscript/cibuildis not evidence.Plan:
maskURLintointernal/delivery/url_mask.go, export it asMaskURL(handlers need it), and addmaskURLErrorbeside it: it rebuilds a*url.ErrorwithURLreplaced byMaskURL(...)andOp/Errpreserved, so the operation and the underlying transport error (DNS, TLS, refused, timeout) survive and only path/query/userinfo are dropped. One masker, one set of semantics.maskURLErrorat the two points where a transport or request-construction error is born:executeHTTPRequest(shared by the Slack and HTTP targets) and thehttp.NewRequestWithContexterror paths. Masking at birth means every wrapping site downstream is safe by construction. Also applied toValidateTargetURL'surl.Parsebranch, which embeds the raw URL in the error the SSRF log line prints.loadTargetMapreturnsmap[string]delivery.TargetView, andEventWithDeliveries.Deliveriesbecomes a[]DeliveryView(status plusTargetView), sosource_logs.htmlhas no path to a stored config blob at all.DeliveryResult.Errorcontains no path segment of the webhook URL while still naming the failure class, plus a rendered-page test for the logs template.Implemented in #121 (
issue-118-mask-credential-leaks, commitb6529f4).All three leaks are closed with the one masker:
maskURLmoved tointernal/delivery/url_mask.go, exported asMaskURL, joined bymaskURLError, which rebuilds a*url.Errorwith the URL masked andOpplus the wrapped cause preserved. Masking happens where the errors are raised —executeHTTPRequest(shared by both targets), the request-construction paths, andValidateTargetURL'surl.Parsebranch, whose error is logged and shown — so the stored error keeps its diagnostic value:loadTargetMapreturnsmap[string]delivery.TargetViewandEventWithDeliveries.Deliveriesis now a[]DeliveryView, sosource_logs.htmlhas no reachable path to a stored config blob.Verified: the load-bearing test asserts the stored
DeliveryResult.Errorholds no path segment of the webhook URL while still naming the operation, the masked host and the transport failure. Removing the mask fromexecuteHTTPRequestmakes it fail on all five segments and on the missing masked host; restoring it makes it pass.make checkgreen, and the Docker path with the cache defeated ranmake lint(0 issues) andmake test(all packages with real durations, none cached) after rebasing onto currentnext.