{"record":{"id":"6b296c2eb866f8da","repo":"knadh/listmonk","slug":"invalid-sns-certificate-url-v","errorCode":null,"errorMessage":"invalid SNS certificate URL: %v","messagePattern":"invalid SNS certificate URL: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/ses.go","lineNumber":225,"sourceCode":"\n\tsign, err := base64.StdEncoding.DecodeString(n.Signature)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn cert.CheckSignature(x509.SHA1WithRSA, s.buildSignature(n), sign)\n}\n\n// getCert takes the SNS certificate URL and fetches it and caches it for the first time,\n// and returns the cached cert for subsequent calls.\nfunc (s *SES) getCert(certURL string) (*x509.Certificate, error) {\n\t// Ensure that the cert URL is Amazon's.\n\tu, err := url.Parse(certURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !sesRegCertURL.MatchString(certURL) {\n\t\treturn nil, fmt.Errorf(\"invalid SNS certificate URL: %v\", u.Host)\n\t}\n\n\t// Return if it's cached.\n\ts.mu.RLock()\n\tc, ok := s.certs[u.Path]\n\ts.mu.RUnlock()\n\tif ok {\n\t\treturn c, nil\n\t}\n\n\t// Fetch the certificate.\n\tresp, err := http.Get(certURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/ses.go#L207-L243","documentation":"getCert rejects certificate URLs that do not match the sesRegCertURL regular expression, which only allows Amazon SNS certificate hosts (sns.<region>.amazonaws.com). This is a security guard against SSRF and signature-forgery attacks where an attacker supplies their own SigningCertURL. The error reports the parsed URL's host.","triggerScenarios":"ProcessSubscription or ProcessBounce receives a notification whose SigningCertURL host is not an Amazon SNS domain — e.g. an attacker-crafted webhook, a test fixture pointing at a local URL, or an SES/SNS setup in a non-standard partition (sns.cn-north-1.amazonaws.com.cn, GovCloud, isolated regions) the regex doesn't cover.","commonSituations":"Spoofed webhook attempts (the guard working as intended); operating in AWS partitions the hard-coded regex doesn't recognize (China, GovCloud, FIPS endpoints); unit-test payloads with fake URLs; SNS message format changes moving the cert URL host.","solutions":["Inspect the logged u.Host — if it is not amazonaws.com, treat the request as malicious and drop it (do not retry).","If you legitimately operate in an uncovered AWS partition (GovCloud/China), extend sesRegCertURL to allow the correct Amazon host suffix.","Confirm the SNS subscription is delivering genuine messages: cross-check the TopicArn and message with an AWS-side confirmation.","Keep the regex restrictive — never replace it with a permissive URL check."],"exampleFix":"// before: partition hosts rejected\nsesRegCertURL = regexp.MustCompile(`^https://sns\\.[a-zA-Z0-9-]+\\.amazonaws\\.com/`)\n// after: allow AWS partition variants\nsesRegCertURL = regexp.MustCompile(`^https://sns\\.[a-zA-Z0-9-]+\\.amazonaws\\.com(\\.cn)?/`)","handlingStrategy":"validation","validationCode":"func isAmazonSNSCertURL(certURL string) bool {\n  re := regexp.MustCompile(`^https://sns\\.[a-zA-Z0-9-]+\\.amazonaws\\.com(\\.cn)?/`)\n  return re.MatchString(certURL)\n}\n// before calling the webhook:\nif !isAmazonSNSCertURL(notif.SigningCertURL) {\n  return errors.New(\"untrusted SigningCertURL: \" + notif.SigningCertURL)\n}","typeGuard":"func isTrustedSNSNotification(n sesNotif) bool {\n  u, err := url.Parse(n.SigningCertURL)\n  return err == nil && u.Scheme == \"https\" &&\n    (strings.HasSuffix(u.Host, \".amazonaws.com\") || strings.HasSuffix(u.Host, \".amazonaws.com.cn\"))\n}","tryCatchPattern":"if err := ses.ProcessBounce(notif); err != nil {\n  if strings.Contains(err.Error(), \"invalid SNS certificate URL\") {\n    // untrusted/malformed URL: drop the request, alert on possible spoofing\n    log.Warn(\"rejected notification with non-Amazon cert URL\", \"err\", err)\n    http.Error(w, \"forbidden\", http.StatusForbidden)\n    return\n  }\n  http.Error(w, \"bad request\", http.StatusBadRequest)\n}","preventionTips":["Never disable or loosen the cert URL hostname check","If using AWS GovCloud/China, explicitly update the allow-list regex for those partitions","Alert on occurrences — a spike indicates someone probing your webhook","Cross-check the notification's TopicArn against your known subscription ARNs"],"tags":["go","security","ssrf","sns","certificate","validation"],"backgroundTag":"untrusted-certificate-url","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}