fix(controlplane): redact secrets and close SSRF gaps across config, delivery, and notifications#2706
Merged
Merged
Conversation
…delivery, and notifications - strip license/checkout secrets from GetConfiguration before it leaves the UI/portal route - gate token refresh on single-user-mode license, matching login (fail-closed on licenser error) - mask auth credentials in event-delivery and delivery-attempt responses (partial last-4, short values fully masked, webhook signatures kept visible for debugging), response-only so stored/dispatch values survive - redact endpoint response headers (e.g. Set-Cookie) on delivery attempts too - validate slack_webhook_url on endpoint create/update and route slack notifications through the netjail dispatcher client - validate meta-event URL after normalizing an empty type to http, closing the empty-type SSRF bypass; guard nil SSL on the project-update path
jirevwe
reviewed
Jul 7, 2026
…lers redact request/response headers only for portal-link (external) callers on event-delivery and delivery-attempt responses. api-key and authenticated dashboard (jwt) callers now see raw headers, matching meta-event visibility; portal holders remain masked. decision keyed on the auth credential type via canViewRawHeaders (fail-closed on nil), and the response models default to redaction so a bare literal cannot leak.
…hout license portal header redaction now fully masks sensitive/non-allowlisted values (no trailing bytes revealed), so customer-injected header names cannot leak secrets to portal viewers. api-key and dashboard callers still receive raw headers and webhook signatures stay visible. slack notifications now post through a dedicated client with a connect-time ssrf guard that blocks private/reserved destinations regardless of the IpRules license, closing the dns->private path that netjail only covered when licensed. this matches the existing unconditional write-time block on slack_webhook_url.
… tls verify the slack notification client cloned the webhook transport, which caused two regressions: - the dial-time ssrf guard also ran on the hop to a configured forward proxy, which is typically private, so notifications failed while webhook delivery used the same proxy fine. the guard now applies only when egress is direct; when a proxy is configured, egress control is delegated to the proxy as with webhook delivery. - the clone inherited dispatcher.insecure_skip_verify, disabling tls verification for notifications. the notification transport now always verifies tls (custom-ca RootCAs preserved), regardless of the webhook compat flag.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4987130. Configure here.
…ication ssrf guard per request login and token refresh mapped transient server-side failures (db lookups, license evaluation, token generation) to auth denials: refresh returned 401 "invalid token" and login returned 403 "invalid credentials". a transient outage was indistinguishable from a bad credential, and a license-lookup error could surface as a definitive "license expired". - add ErrCodeInternal and tag every server-side failure in the login and refresh services with it; handlers now return 503 for internal, 403 for license expired, and 401/403 only for real auth failures. - IsPrimaryInstanceAdmin now propagates IsMultiUserMode errors instead of swallowing them and falling through to the single-user admin checks. the notification ssrf guard also disabled itself globally whenever a forward proxy was configured, leaving NO_PROXY and hostname-only targets to dial directly with no guard. make the decision per request: the guard is always installed and only the actual proxy hop is bypassed via a request-scoped marker, so every direct dial stays guarded. tls verification is also forced on the notification transport regardless of the webhook insecure_skip_verify flag.
mekilis
added a commit
that referenced
this pull request
Jul 8, 2026
bump VERSION to v26.6.2 and backfill CHANGELOG.md through v26.6.2 with git-cliff. the changelog had frozen at 26.3.6 while tags advanced to v26.6.1, and VERSION had frozen at v26.2.2 (nothing in the release flow bumps it). regenerate the missing 26.3.7, 26.6.0, 26.6.1 sections and add 26.6.2 for the three commits since v26.6.1 (#2705, #2706, #2707).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Batch of security hardening fixes across the control plane and delivery paths.
GetConfigurationbefore the payload leaves the UI/portal route.Set-Cookie) are redacted too.slack_webhook_urlon endpoint create/update and route slack notifications through the netjail dispatcher client.http, closing the empty-type bypass. Guard nilSSLon the project-update path (create defaults it, update did not).Test plan
go test ./api/... ./services/... ./internal/pkg/middleware/... ./util/...GetConfigurationresponse contains no license/checkout secretsX-Convoy-SignatureNote
High Risk
Touches authentication, license gating, secret exposure, and SSRF egress for user-controlled URLs—areas where mistakes have direct security impact, though behavior is largely fail-closed with tests.
Overview
Security hardening across configuration exposure, auth, delivery APIs, and outbound HTTP.
Configuration:
GetConfigurationnow strips license keys, checkout nonces, and related billing secrets before responses reach UI/portal routes (billing still uses dedicated endpoints).Auth: Login and token refresh share
IsPrimaryInstanceAdminso refresh cannot bypass single-user license limits. Transient license/DB failures return 5xx (ErrCodeInternal) instead of “invalid credentials”; lapsed-license denials stay 403 on both paths.Delivery APIs: Event delivery and delivery-attempt JSON redacts sensitive request/response headers for portal-link callers; JWT/API-key callers see raw values. Redaction is response-only (shallow clone); stored data and webhook signatures stay visible where intended.
SSRF: Slack webhook URLs are validated on endpoint create/update; meta-event URLs are validated after defaulting empty type to HTTP (with nil-safe
SSL). Notifications use a dedicated HTTP client with connect-time private-IP blocking and enforced TLS (not webhookinsecure_skip_verify).Reviewed by Cursor Bugbot for commit 146ea8e. Bugbot is set up for automated code reviews on this repo. Configure here.