CVE-2026-26957
Description
Libredesk is a self-hosted customer support desk application. Versions prior to 1.0.2-0.20260215211005-727213631ce6 fail to validate destination URLs for webhooks, allowing an attacker posing as an authenticated "Application Admin" to force the server to make HTTP requests to arbitrary internal destinations. This could compromise the underlying cloud infrastructure or internal corporate network where the service is hosted. This issue has been fixed in version 1.0.2-0.20260215211005-727213631ce6.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/abhinavxd/libredeskGo | < 1.0.2-0.20260215211005-727213631ce6 | 1.0.2-0.20260215211005-727213631ce6 |
Affected products
1Patches
1727213631ce6SSRF protection to webhooks
5 files changed · +28 −0
cmd/init.go+1 −0 modified@@ -934,6 +934,7 @@ func initWebhook(db *sqlx.DB, i18n *i18n.I18n) *webhook.Manager { QueueSize: ko.MustInt("webhook.queue_size"), Timeout: ko.MustDuration("webhook.timeout"), EncryptionKey: ko.MustString("app.encryption_key"), + AllowedHosts: ko.Strings("webhook.allowed_hosts"), }) if err != nil { log.Fatalf("error initializing webhook manager: %v", err)
config.sample.toml+2 −0 modified@@ -119,6 +119,8 @@ workers = 5 queue_size = 10000 # HTTP timeout for webhook requests timeout = "15s" +# CIDR ranges allowed to bypass SSRF protection (e.g. ["10.0.0.0/8"]) +allowed_hosts = [] [conversation] # How often to check for conversations to unsnooze
go.mod+1 −0 modified@@ -3,6 +3,7 @@ module github.com/abhinavxd/libredesk go 1.25.0 require ( + github.com/abhinavxd/ssrfguard v0.1.0 github.com/casbin/casbin/v2 v2.99.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/disintegration/imaging v1.6.2
go.sum+2 −0 modified@@ -2,6 +2,8 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2Qx cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/abhinavxd/ssrfguard v0.1.0 h1:Ns/llAQ63uGFehxSvhCd+WGDKmBEEmIH+E1AW1CGgWM= +github.com/abhinavxd/ssrfguard v0.1.0/go.mod h1:eNVubb+m/r3KrKWYdG6hxzeAfj+t2ZmZss4V/x7D6Ws= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo=
internal/webhook/webhook.go+22 −0 modified@@ -14,6 +14,7 @@ import ( "io" "net" "net/http" + "net/netip" "sync" "time" @@ -22,6 +23,7 @@ import ( "github.com/abhinavxd/libredesk/internal/envelope" "github.com/abhinavxd/libredesk/internal/version" "github.com/abhinavxd/libredesk/internal/webhook/models" + "github.com/abhinavxd/ssrfguard" "github.com/jmoiron/sqlx" "github.com/knadh/go-i18n" "github.com/lib/pq" @@ -57,6 +59,7 @@ type Opts struct { QueueSize int Timeout time.Duration EncryptionKey string + AllowedHosts []string // CIDR prefixes allowed to bypass SSRF protection } // DeliveryTask represents a webhook delivery task @@ -86,6 +89,10 @@ func New(opts Opts) (*Manager, error) { return nil, err } + // Parse allowed host CIDRs for SSRF exceptions. + allowed := parseAllowedHosts(opts.AllowedHosts, opts.Lo) + guard := ssrfguard.New(allowed...) + return &Manager{ q: q, lo: opts.Lo, @@ -98,6 +105,7 @@ func New(opts Opts) (*Manager, error) { DialContext: (&net.Dialer{ Timeout: 3 * time.Second, KeepAlive: 30 * time.Second, + Control: guard.Control, }).DialContext, TLSHandshakeTimeout: 3 * time.Second, ResponseHeaderTimeout: 3 * time.Second, @@ -404,3 +412,17 @@ func (m *Manager) getWebhooksByEvent(event string) ([]models.Webhook, error) { return webhooks, nil } + +// parseAllowedHosts parses CIDR strings into netip.Prefix slices. +func parseAllowedHosts(hosts []string, lo *logf.Logger) []netip.Prefix { + var prefixes []netip.Prefix + for _, h := range hosts { + prefix, err := netip.ParsePrefix(h) + if err != nil { + lo.Warn("ignoring invalid webhook `allowed_hosts` entry", "entry", h, "error", err) + continue + } + prefixes = append(prefixes, prefix) + } + return prefixes +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.