From cd5816c428931f56561f7d48b649a33858812539 Mon Sep 17 00:00:00 2001 From: memoclaw Date: Sat, 7 Mar 2026 13:46:03 +0800 Subject: [PATCH] feat: add --allow-private-webhooks flag to bypass SSRF protection (#5694) Co-authored-by: Claude Opus 4.6 --- cmd/memos/main.go | 6 ++++++ plugin/webhook/validate.go | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/cmd/memos/main.go b/cmd/memos/main.go index 55545843a..e9ea35153 100644 --- a/cmd/memos/main.go +++ b/cmd/memos/main.go @@ -15,6 +15,7 @@ import ( "github.com/usememos/memos/internal/profile" "github.com/usememos/memos/internal/version" + "github.com/usememos/memos/plugin/webhook" "github.com/usememos/memos/server" "github.com/usememos/memos/store" "github.com/usememos/memos/store/db" @@ -36,6 +37,7 @@ var ( InstanceURL: viper.GetString("instance-url"), } instanceProfile.Version = version.GetCurrentVersion() + webhook.AllowPrivateIPs = viper.GetBool("allow-private-webhooks") if err := instanceProfile.Validate(); err != nil { slog.Error("failed to validate profile", "error", err) @@ -105,6 +107,7 @@ func init() { rootCmd.PersistentFlags().String("driver", "sqlite", "database driver") rootCmd.PersistentFlags().String("dsn", "", "database source name(aka. DSN)") rootCmd.PersistentFlags().String("instance-url", "", "the url of your memos instance") + rootCmd.PersistentFlags().Bool("allow-private-webhooks", false, "allow webhook URLs to resolve to private/reserved IP addresses") if err := viper.BindPFlag("demo", rootCmd.PersistentFlags().Lookup("demo")); err != nil { panic(err) @@ -130,6 +133,9 @@ func init() { if err := viper.BindPFlag("instance-url", rootCmd.PersistentFlags().Lookup("instance-url")); err != nil { panic(err) } + if err := viper.BindPFlag("allow-private-webhooks", rootCmd.PersistentFlags().Lookup("allow-private-webhooks")); err != nil { + panic(err) + } viper.SetEnvPrefix("memos") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) diff --git a/plugin/webhook/validate.go b/plugin/webhook/validate.go index 3e5e82c9f..579edfa56 100644 --- a/plugin/webhook/validate.go +++ b/plugin/webhook/validate.go @@ -35,8 +35,16 @@ func init() { } } +// AllowPrivateIPs controls whether webhook URLs may resolve to reserved/private +// IP addresses. When true, the SSRF protection is disabled. This is useful for +// self-hosted deployments where webhooks target services on the local network. +var AllowPrivateIPs bool + // isReservedIP reports whether ip falls within any reserved/private range. func isReservedIP(ip net.IP) bool { + if AllowPrivateIPs { + return false + } for _, network := range reservedNetworks { if network.Contains(ip) { return true