CVE-2024-21527 (published 2024-07-19T05:15Z)

Versions of the package github.com/gotenberg/gotenberg/v8/pkg/gotenberg before 8.1.0; versions of the package github.com/gotenberg/gotenberg/v8/pkg/modules/chromium before 8.1.0; versions of the package github.com/gotenberg/gotenberg/v8/pkg/modules/webhook before 8.1.0 are vulnerable to Server-side Request Forgery (SSRF) via the /convert/html endpoint when a request is made to a file via localhost, such as <iframe src="\\\\localhost/etc/passwd">. By exploiting this vulnerability, an attacker can achieve local file inclusion, allowing of sensitive files read on the host system.\r\r Workaround\rAn alternative is using either or both --chromium-deny-list and --chromium-allow-list flags.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/gotenberg/gotenberg
commit_details: repo_url: https://github.com/gotenberg/gotenberg
author: Julien Neuhart
author_date: 2024-02-18 17:18:40+01:00
msg: fix(chromium): better default deny list regexp
repository: repo_url: https://github.com/gotenberg/gotenberg
repo_name: gotenberg/gotenberg
description: A developer-friendly API for converting numerous document formats into PDF files, and more!
repo_language: Go
owner: gotenberg
file_changes: filename: Makefile
change_type: ModificationType.MODIFY
diff: @@ -46,7 +46,7 @@ CHROMIUM_ALLOW_FILE_ACCESS_FROM_FILES=false
 CHROMIUM_HOST_RESOLVER_RULES=
 CHROMIUM_PROXY_SERVER=
 CHROMIUM_ALLOW_LIST=
-CHROMIUM_DENY_LIST="^file:///[^tmp].*"
+CHROMIUM_DENY_LIST=^file:(?!//\/tmp/).*
 CHROMIUM_CLEAR_CACHE=false
 CHROMIUM_CLEAR_COOKIES=false
 CHROMIUM_DISABLE_JAVASCRIPT=false
@@ -100,8 +100,8 @@ run: ## Start a Gotenberg container
 	--chromium-allow-file-access-from-files=$(CHROMIUM_ALLOW_FILE_ACCESS_FROM_FILES) \
 	--chromium-host-resolver-rules=$(CHROMIUM_HOST_RESOLVER_RULES) \
 	--chromium-proxy-server=$(CHROMIUM_PROXY_SERVER) \
-	--chromium-allow-list=$(CHROMIUM_ALLOW_LIST) \
-	--chromium-deny-list=$(CHROMIUM_DENY_LIST) \
+	--chromium-allow-list="$(CHROMIUM_ALLOW_LIST)" \
+	--chromium-deny-list="$(CHROMIUM_DENY_LIST)" \
 	--chromium-clear-cache=$(CHROMIUM_CLEAR_CACHE) \
 	--chromium-clear-cookies=$(CHROMIUM_CLEAR_COOKIES) \
 	--chromium-disable-javascript=$(CHROMIUM_DISABLE_JAVASCRIPT) \
@@ -120,8 +120,8 @@ run: ## Start a Gotenberg container
 	--prometheus-collect-interval=$(PROMETHEUS_COLLECT_INTERVAL) \
 	--prometheus-disable-route-logging=$(PROMETHEUS_DISABLE_ROUTE_LOGGING) \
 	--prometheus-disable-collect=$(PROMETHEUS_DISABLE_COLLECT) \
-	--webhook-allow-list=$(WEBHOOK_ALLOW_LIST) \
-	--webhook-deny-list=$(WEBHOOK_DENY_LIST) \
+	--webhook-allow-list="$(WEBHOOK_ALLOW_LIST)" \
+	--webhook-deny-list="$(WEBHOOK_DENY_LIST)" \
 	--webhook-error-allow-list=$(WEBHOOK_ERROR_ALLOW_LIST) \
 	--webhook-error-deny-list=$(WEBHOOK_ERROR_DENY_LIST) \
 	--webhook-max-retry=$(WEBHOOK_MAX_RETRY) \

programming_language: Shell
filename: flags.go
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,9 @@
 package gotenberg
 
 import (
-	"regexp"
 	"time"
 
+	"github.com/dlclark/regexp2"
 	"github.com/labstack/gommon/bytes"
 	flag "github.com/spf13/pflag"
 )
@@ -199,19 +199,19 @@ func (f *ParsedFlags) MustDeprecatedHumanReadableBytesString(deprecated string,
 
 // MustRegexp returns the regular expression of a flag given by name.
 // It panics if an error occurs.
-func (f *ParsedFlags) MustRegexp(name string) *regexp.Regexp {
+func (f *ParsedFlags) MustRegexp(name string) *regexp2.Regexp {
 	val, err := f.GetString(name)
 	if err != nil {
 		panic(err)
 	}
 
-	return regexp.MustCompile(val)
+	return regexp2.MustCompile(val, 0)
 }
 
 // MustDeprecatedRegexp returns the regular expression of a deprecated flag if
 // it was explicitly set or the regular expression of the new flag.
 // It panics if an error occurs.
-func (f *ParsedFlags) MustDeprecatedRegexp(deprecated string, newName string) *regexp.Regexp {
+func (f *ParsedFlags) MustDeprecatedRegexp(deprecated string, newName string) *regexp2.Regexp {
 	if f.Changed(deprecated) {
 		return f.MustRegexp(deprecated)
 	}

programming_language: Go
filename: regexp.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,53 @@
+package gotenberg
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/dlclark/regexp2"
+)
+
+// ErrFiltered happens if a value is filtered by the [FilterDeadline] function.
+var ErrFiltered = errors.New("value filtered")
+
+// FilterDeadline checks if given value is allowed and not denied according to
+// regex patterns. It returns a [context.DeadlineExceeded] if it takes too long
+// to process.
+func FilterDeadline(allowed, denied *regexp2.Regexp, s string, deadline time.Time) error {
+	// FIXME: not ideal to compile everytime, but is there another way to create a clone?
+	if allowed.String() != "" {
+		allow := regexp2.MustCompile(allowed.String(), 0)
+		allow.MatchTimeout = time.Until(deadline)
+
+		ok, err := allow.MatchString(s)
+		if err != nil {
+			if time.Now().After(deadline) {
+				return context.DeadlineExceeded
+			}
+			return fmt.Errorf("'%s' cannot handle '%s': %w", allow.String(), s, err)
+		}
+		if !ok {
+			return fmt.Errorf("'%s' does not match the expression from the allowed list: %w", s, ErrFiltered)
+		}
+	}
+
+	if denied.String() != "" {
+		deny := regexp2.MustCompile(denied.String(), 0)
+		deny.MatchTimeout = time.Until(deadline)
+
+		ok, err := deny.MatchString(s)
+		if err != nil {
+			if time.Now().After(deadline) {
+				return context.DeadlineExceeded
+			}
+			return fmt.Errorf("'%s' cannot handle '%s': %w", deny.String(), s, err)
+		}
+		if ok {
+			return fmt.Errorf("'%s' matches the expression from the denied list: %w", s, ErrFiltered)
+		}
+	}
+
+	return nil
+}

programming_language: Go
filename: regexp_test.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,83 @@
+package gotenberg
+
+import (
+	"context"
+	"errors"
+	"testing"
+	"time"
+
+	"github.com/dlclark/regexp2"
+)
+
+func TestFilterDeadline(t *testing.T) {
+	for _, tc := range []struct {
+		scenario      string
+		allowed       *regexp2.Regexp
+		denied        *regexp2.Regexp
+		s             string
+		deadline      time.Time
+		expectError   bool
+		expectedError error
+	}{
+		{
+			scenario:      "DeadlineExceeded (allowed)",
+			allowed:       regexp2.MustCompile("foo", 0),
+			denied:        regexp2.MustCompile("", 0),
+			s:             "foo",
+			deadline:      time.Now().Add(time.Duration(-1) * time.Hour),
+			expectError:   true,
+			expectedError: context.DeadlineExceeded,
+		},
+		{
+			scenario:      "ErrFiltered (allowed)",
+			allowed:       regexp2.MustCompile("foo", 0),
+			denied:        regexp2.MustCompile("", 0),
+			s:             "bar",
+			deadline:      time.Now().Add(time.Duration(5) * time.Second),
+			expectError:   true,
+			expectedError: ErrFiltered,
+		},
+		{
+			scenario:      "DeadlineExceeded (denied)",
+			allowed:       regexp2.MustCompile("", 0),
+			denied:        regexp2.MustCompile("foo", 0),
+			s:             "foo",
+			deadline:      time.Now().Add(time.Duration(-1) * time.Hour),
+			expectError:   true,
+			expectedError: context.DeadlineExceeded,
+		},
+		{
+			scenario:      "ErrFiltered (denied)",
+			allowed:       regexp2.MustCompile("", 0),
+			denied:        regexp2.MustCompile("foo", 0),
+			s:             "foo",
+			deadline:      time.Now().Add(time.Duration(5) * time.Second),
+			expectError:   true,
+			expectedError: ErrFiltered,
+		},
+		{
+			scenario:    "success",
+			allowed:     regexp2.MustCompile("", 0),
+			denied:      regexp2.MustCompile("", 0),
+			s:           "foo",
+			deadline:    time.Now().Add(time.Duration(5) * time.Second),
+			expectError: false,
+		},
+	} {
+		t.Run(tc.scenario, func(t *testing.T) {
+			err := FilterDeadline(tc.allowed, tc.denied, tc.s, tc.deadline)
+
+			if tc.expectError && err == nil {
+				t.Fatal("expected an error but got none")
+			}
+
+			if !tc.expectError && err != nil {
+				t.Fatalf("expected no error but got: %v", err)
+			}
+
+			if tc.expectedError != nil && !errors.Is(err, tc.expectedError) {
+				t.Fatalf("expected error %v but got: %v", tc.expectedError, err)
+			}
+		})
+	}
+}

programming_language: Go
filename: supervisor_test.go
change_type: ModificationType.MODIFY
diff: @@ -487,10 +487,10 @@ func TestProcessSupervisor_runWithDeadline(t *testing.T) {
 			ps := NewProcessSupervisor(zap.NewNop(), new(ProcessMock), 0, 0).(*processSupervisor)
 
 			ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
-			defer cancel()
-
 			if tc.ctxDone {
 				cancel()
+			} else {
+				defer cancel()
 			}
 
 			err := ps.runWithDeadline(ctx, func() error {

programming_language: Go
filename: browser.go
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"regexp"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -15,6 +14,7 @@ import (
 	"github.com/chromedp/cdproto/network"
 	"github.com/chromedp/cdproto/runtime"
 	"github.com/chromedp/chromedp"
+	"github.com/dlclark/regexp2"
 	"go.uber.org/zap"
 
 	"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
@@ -39,8 +39,8 @@ type browserArguments struct {
 	wsUrlReadTimeout         time.Duration
 
 	// Tasks specific.
-	allowList         *regexp.Regexp
-	denyList          *regexp.Regexp
+	allowList         *regexp2.Regexp
+	denyList          *regexp2.Regexp
 	clearCache        bool
 	clearCookies      bool
 	disableJavaScript bool
@@ -263,20 +263,17 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
 		return errors.New("browser not started, cannot handle tasks")
 	}
 
-	// We validate the "main" URL against our allow / deny lists.
-	if !b.arguments.allowList.MatchString(url) {
-		return fmt.Errorf("'%s' does not match the expression from the allowed list: %w", url, ErrUrlNotAuthorized)
-	}
-
-	if b.arguments.denyList.String() != "" && b.arguments.denyList.MatchString(url) {
-		return fmt.Errorf("'%s' matches the expression from the denied list: %w", url, ErrUrlNotAuthorized)
-	}
-
 	deadline, ok := ctx.Deadline()
 	if !ok {
 		return errors.New("context has no deadline")
 	}
 
+	// We validate the "main" URL against our allow / deny lists.
+	err := gotenberg.FilterDeadline(b.arguments.allowList, b.arguments.denyList, url, deadline)
+	if err != nil {
+		return fmt.Errorf("filter URL: %w", err)
+	}
+
 	b.ctxMu.RLock()
 	defer b.ctxMu.RUnlock()
 
@@ -310,7 +307,7 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
 		listenForEventExceptionThrown(taskCtx, logger, &consoleExceptions, &consoleExceptionsMu)
 	}
 
-	err := chromedp.Run(taskCtx, tasks...)
+	err = chromedp.Run(taskCtx, tasks...)
 	if err != nil {
 		errMessage := err.Error()
 

programming_language: Go
filename: browser_test.go
change_type: ModificationType.MODIFY
diff: @@ -5,11 +5,11 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"regexp"
 	"strings"
 	"testing"
 	"time"
 
+	"github.com/dlclark/regexp2"
 	"github.com/google/uuid"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
@@ -268,28 +268,24 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 			expectError: true,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized: main URL does not match the allowed list",
+			scenario: "context has no deadline",
 			browser: func() browser {
 				b := new(chromiumBrowser)
-				b.arguments = browserArguments{
-					allowList: regexp.MustCompile("^file:///[^tmp].*"),
-				}
 				b.isStarted.Store(true)
 				return b
 			}(),
-			fs:            gotenberg.NewFileSystem(),
-			noDeadline:    false,
-			start:         false,
-			expectError:   true,
-			expectedError: ErrUrlNotAuthorized,
+			fs:          gotenberg.NewFileSystem(),
+			noDeadline:  true,
+			start:       false,
+			expectError: true,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
+			scenario: "ErrFiltered: main URL does not match the allowed list",
 			browser: func() browser {
 				b := new(chromiumBrowser)
 				b.arguments = browserArguments{
-					allowList: regexp.MustCompile(""),
-					denyList:  regexp.MustCompile("^file:///tmp.*"),
+					allowList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
+					denyList:  regexp2.MustCompile("", 0),
 				}
 				b.isStarted.Store(true)
 				return b
@@ -298,23 +294,24 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 			noDeadline:    false,
 			start:         false,
 			expectError:   true,
-			expectedError: ErrUrlNotAuthorized,
+			expectedError: gotenberg.ErrFiltered,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
+			scenario: "ErrFiltered: main URL does match the denied list",
 			browser: func() browser {
 				b := new(chromiumBrowser)
 				b.arguments = browserArguments{
-					allowList: regexp.MustCompile(""),
-					denyList:  regexp.MustCompile(""),
+					allowList: regexp2.MustCompile("", 0),
+					denyList:  regexp2.MustCompile("^file:///tmp.*", 0),
 				}
 				b.isStarted.Store(true)
 				return b
 			}(),
-			fs:          gotenberg.NewFileSystem(),
-			noDeadline:  true,
-			start:       false,
-			expectError: true,
+			fs:            gotenberg.NewFileSystem(),
+			noDeadline:    false,
+			start:         false,
+			expectError:   true,
+			expectedError: gotenberg.ErrFiltered,
 		},
 		{
 			scenario: "a request does not match the allowed list",
@@ -322,8 +319,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile("^file:///tmp.*"),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("^file:///tmp.*", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -354,8 +351,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile("^file:///[^tmp].*"),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -386,8 +383,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -421,8 +418,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -454,8 +451,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -487,8 +484,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 					clearCache:       true,
 				},
 			),
@@ -520,8 +517,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 					clearCookies:     true,
 				},
 			),
@@ -553,8 +550,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:           os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout:  5 * time.Second,
-					allowList:         regexp.MustCompile(""),
-					denyList:          regexp.MustCompile(""),
+					allowList:         regexp2.MustCompile("", 0),
+					denyList:          regexp2.MustCompile("", 0),
 					disableJavaScript: true,
 				},
 			),
@@ -588,8 +585,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -625,8 +622,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -658,8 +655,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -694,8 +691,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -727,8 +724,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -762,8 +759,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -797,8 +794,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -832,8 +829,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -878,8 +875,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -913,8 +910,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -959,8 +956,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -995,8 +992,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1033,8 +1030,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1066,8 +1063,8 @@ func TestChromiumBrowser_pdf(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1202,28 +1199,28 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 			expectError: true,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized: main URL does not match the allowed list",
+			scenario: "context has not deadline",
 			browser: func() browser {
 				b := new(chromiumBrowser)
 				b.arguments = browserArguments{
-					allowList: regexp.MustCompile("^file:///[^tmp].*"),
+					allowList: regexp2.MustCompile("", 0),
+					denyList:  regexp2.MustCompile("", 0),
 				}
 				b.isStarted.Store(true)
 				return b
 			}(),
-			fs:            gotenberg.NewFileSystem(),
-			noDeadline:    false,
-			start:         false,
-			expectError:   true,
-			expectedError: ErrUrlNotAuthorized,
+			fs:          gotenberg.NewFileSystem(),
+			noDeadline:  true,
+			start:       false,
+			expectError: true,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
+			scenario: "ErrFiltered: main URL does not match the allowed list",
 			browser: func() browser {
 				b := new(chromiumBrowser)
 				b.arguments = browserArguments{
-					allowList: regexp.MustCompile(""),
-					denyList:  regexp.MustCompile("^file:///tmp.*"),
+					allowList: regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
+					denyList:  regexp2.MustCompile("", 0),
 				}
 				b.isStarted.Store(true)
 				return b
@@ -1232,23 +1229,24 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 			noDeadline:    false,
 			start:         false,
 			expectError:   true,
-			expectedError: ErrUrlNotAuthorized,
+			expectedError: gotenberg.ErrFiltered,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized: main URL does match the denied list",
+			scenario: "ErrFiltered: main URL does match the denied list",
 			browser: func() browser {
 				b := new(chromiumBrowser)
 				b.arguments = browserArguments{
-					allowList: regexp.MustCompile(""),
-					denyList:  regexp.MustCompile(""),
+					allowList: regexp2.MustCompile("", 0),
+					denyList:  regexp2.MustCompile("^file:///tmp.*", 0),
 				}
 				b.isStarted.Store(true)
 				return b
 			}(),
-			fs:          gotenberg.NewFileSystem(),
-			noDeadline:  true,
-			start:       false,
-			expectError: true,
+			fs:            gotenberg.NewFileSystem(),
+			noDeadline:    false,
+			start:         false,
+			expectError:   true,
+			expectedError: gotenberg.ErrFiltered,
 		},
 		{
 			scenario: "a request does not match the allowed list",
@@ -1256,8 +1254,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile("^file:///tmp.*"),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("^file:///tmp.*", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1288,8 +1286,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile("^file:///[^tmp].*"),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile(`^file:(?!//\/tmp/).*`, 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1320,8 +1318,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1355,8 +1353,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1388,8 +1386,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1421,8 +1419,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 					clearCache:       true,
 				},
 			),
@@ -1454,8 +1452,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 					clearCookies:     true,
 				},
 			),
@@ -1487,8 +1485,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:           os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout:  5 * time.Second,
-					allowList:         regexp.MustCompile(""),
-					denyList:          regexp.MustCompile(""),
+					allowList:         regexp2.MustCompile("", 0),
+					denyList:          regexp2.MustCompile("", 0),
 					disableJavaScript: true,
 				},
 			),
@@ -1522,8 +1520,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1559,8 +1557,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1594,8 +1592,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1627,8 +1625,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1662,8 +1660,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1697,8 +1695,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1732,8 +1730,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1778,8 +1776,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1813,8 +1811,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1859,8 +1857,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {
@@ -1900,8 +1898,8 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
 				browserArguments{
 					binPath:          os.Getenv("CHROMIUM_BIN_PATH"),
 					wsUrlReadTimeout: 5 * time.Second,
-					allowList:        regexp.MustCompile(""),
-					denyList:         regexp.MustCompile(""),
+					allowList:        regexp2.MustCompile("", 0),
+					denyList:         regexp2.MustCompile("", 0),
 				},
 			),
 			fs: func() *gotenberg.FileSystem {

programming_language: Go
filename: chromium.go
change_type: ModificationType.MODIFY
diff: @@ -20,10 +20,6 @@ func init() {
 }
 
 var (
-	// ErrUrlNotAuthorized happens if a URL is not acceptable according to the
-	// allowed/denied lists.
-	ErrUrlNotAuthorized = errors.New("URL not authorized")
-
 	// ErrInvalidEmulatedMediaType happens if the emulated media type is not
 	// "screen" nor "print". Empty value are allowed though.
 	ErrInvalidEmulatedMediaType = errors.New("invalid emulated media type")
@@ -291,7 +287,7 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor {
 			fs.String("chromium-host-resolver-rules", "", "Set custom mappings to the host resolver")
 			fs.String("chromium-proxy-server", "", "Set the outbound proxy server; this switch only affects HTTP and HTTPS requests")
 			fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression")
-			fs.String("chromium-deny-list", "^file:///[^tmp].*", "Set the denied URLs for Chromium using a regular expression")
+			fs.String("chromium-deny-list", `^file:(?!//\/tmp/).*`, "Set the denied URLs for Chromium using a regular expression")
 			fs.Bool("chromium-clear-cache", false, "Clear Chromium cache between each conversion")
 			fs.Bool("chromium-clear-cookies", false, "Clear Chromium cookies between each conversion")
 			fs.Bool("chromium-disable-javascript", false, "Disable JavaScript")

programming_language: Go
filename: events.go
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,6 @@ package chromium
 import (
 	"context"
 	"fmt"
-	"regexp"
 	"slices"
 	"sync"
 
@@ -13,14 +12,17 @@ import (
 	"github.com/chromedp/cdproto/page"
 	"github.com/chromedp/cdproto/runtime"
 	"github.com/chromedp/chromedp"
+	"github.com/dlclark/regexp2"
 	"go.uber.org/multierr"
 	"go.uber.org/zap"
 	"golang.org/x/sync/errgroup"
+
+	"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
 )
 
 // listenForEventRequestPaused listens for requests to check if they are
 // allowed or not.
-func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp.Regexp, denyList *regexp.Regexp) {
+func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp2.Regexp, denyList *regexp2.Regexp) {
 	chromedp.ListenTarget(ctx, func(ev interface{}) {
 		switch e := ev.(type) {
 		case *fetch.EventRequestPaused:
@@ -28,13 +30,15 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
 				logger.Debug(fmt.Sprintf("event EventRequestPaused fired for '%s'", e.Request.URL))
 				allow := true
 
-				if !allowList.MatchString(e.Request.URL) {
-					logger.Warn(fmt.Sprintf("'%s' does not match the expression from the allowed list", e.Request.URL))
-					allow = false
+				deadline, ok := ctx.Deadline()
+				if !ok {
+					logger.Error("context has no deadline, cannot filter URL")
+					return
 				}
 
-				if denyList.String() != "" && denyList.MatchString(e.Request.URL) {
-					logger.Warn(fmt.Sprintf("'%s' matches the expression from the denied list", e.Request.URL))
+				err := gotenberg.FilterDeadline(allowList, denyList, e.Request.URL, deadline)
+				if err != nil {
+					logger.Warn(err.Error())
 					allow = false
 				}
 
@@ -43,16 +47,15 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
 
 				if allow {
 					req := fetch.ContinueRequest(e.RequestID)
-					err := req.Do(executorCtx)
+					err = req.Do(executorCtx)
 					if err != nil {
 						logger.Error(fmt.Sprintf("continue request: %s", err))
 					}
-
 					return
 				}
 
 				req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
-				err := req.Do(executorCtx)
+				err = req.Do(executorCtx)
 				if err != nil {
 					logger.Error(fmt.Sprintf("fail request: %s", err))
 				}

programming_language: Go
filename: routes.go
change_type: ModificationType.MODIFY
diff: @@ -621,7 +621,7 @@ func handleChromiumError(err error, url string, options Options) error {
 		)
 	}
 
-	if errors.Is(err, ErrUrlNotAuthorized) {
+	if errors.Is(err, gotenberg.ErrFiltered) {
 		return api.WrapError(
 			err,
 			api.NewSentinelHttpError(

programming_language: Go
filename: routes_test.go
change_type: ModificationType.MODIFY
diff: @@ -1243,10 +1243,10 @@ func TestConvertUrl(t *testing.T) {
 			expectOutputPathsCount: 0,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized",
+			scenario: "ErrFiltered",
 			ctx:      &api.ContextMock{Context: new(api.Context)},
 			api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
-				return ErrUrlNotAuthorized
+				return gotenberg.ErrFiltered
 			}},
 			options:                DefaultPdfOptions(),
 			expectError:            true,
@@ -1503,10 +1503,10 @@ func TestScreenshotUrl(t *testing.T) {
 			expectOutputPathsCount: 0,
 		},
 		{
-			scenario: "ErrUrlNotAuthorized",
+			scenario: "ErrFiltered",
 			ctx:      &api.ContextMock{Context: new(api.Context)},
 			api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
-				return ErrUrlNotAuthorized
+				return gotenberg.ErrFiltered
 			}},
 			options:                DefaultScreenshotOptions(),
 			expectError:            true,

programming_language: Go
filename: middleware.go
change_type: ModificationType.MODIFY
diff: @@ -9,14 +9,15 @@ import (
 	"fmt"
 	"net/http"
 	"os"
-	"regexp"
 	"strconv"
 	"strings"
 	"time"
 
+	"github.com/dlclark/regexp2"
 	"github.com/hashicorp/go-retryablehttp"
 	"github.com/labstack/echo/v4"
 
+	"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
 	"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
 )
 
@@ -44,38 +45,38 @@ func webhookMiddleware(w *Webhook) api.Middleware {
 						)
 					}
 
+					deadline, ok := ctx.Deadline()
+					if !ok {
+						return errors.New("context has no deadline")
+					}
+
 					// Let's check if the webhook URLs are acceptable according to our
 					// allowed/denied lists.
-					filter := func(URL, header string, allowList, denyList *regexp.Regexp) error {
-						if !allowList.MatchString(URL) {
-							return api.WrapError(
-								fmt.Errorf("'%s' does not match the expression from the allowed list", URL),
-								api.NewSentinelHttpError(
-									http.StatusForbidden,
-									fmt.Sprintf("Invalid '%s' header value: '%s' does not match the authorized URLs", header, URL),
-								),
-							)
+					filter := func(url, header string, allowList, denyList *regexp2.Regexp, deadline time.Time) error {
+						err := gotenberg.FilterDeadline(allowList, denyList, url, deadline)
+						if err == nil {
+							return nil
 						}
 
-						if denyList.String() != "" && denyList.MatchString(URL) {
+						if errors.Is(err, gotenberg.ErrFiltered) {
 							return api.WrapError(
-								fmt.Errorf("'%s' matches the expression from the denied list", URL),
+								err,
 								api.NewSentinelHttpError(
 									http.StatusForbidden,
-									fmt.Sprintf("Invalid '%s' header value: '%s' does not match the authorized URLs", header, URL),
+									fmt.Sprintf("Invalid '%s' header value: '%s' does not match the authorized URL", header, url),
 								),
 							)
 						}
 
-						return nil
+						return err
 					}
 
-					err := filter(webhookUrl, "Gotenberg-Webhook-Url", w.allowList, w.denyList)
+					err := filter(webhookUrl, "Gotenberg-Webhook-Url", w.allowList, w.denyList, deadline)
 					if err != nil {
 						return fmt.Errorf("filter webhook URL: %w", err)
 					}
 
-					err = filter(webhookErrorUrl, "Gotenberg-Webhook-Error-Url", w.errorAllowList, w.errorDenyList)
+					err = filter(webhookErrorUrl, "Gotenberg-Webhook-Error-Url", w.errorAllowList, w.errorDenyList, deadline)
 					if err != nil {
 						return fmt.Errorf("filter webhook error URL: %w", err)
 					}

programming_language: Go
filename: middleware_test.go
change_type: ModificationType.MODIFY
diff: @@ -11,11 +11,11 @@ import (
 	"mime/multipart"
 	"net/http"
 	"net/http/httptest"
-	"regexp"
 	"strings"
 	"testing"
 	"time"
 
+	"github.com/dlclark/regexp2"
 	"github.com/labstack/echo/v4"
 	"go.uber.org/zap"
 
@@ -47,10 +47,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 
 	buildWebhookModule := func() *Webhook {
 		return &Webhook{
-			allowList:      regexp.MustCompile(""),
-			denyList:       regexp.MustCompile(""),
-			errorAllowList: regexp.MustCompile(""),
-			errorDenyList:  regexp.MustCompile(""),
+			allowList:      regexp2.MustCompile("", 0),
+			denyList:       regexp2.MustCompile("", 0),
+			errorAllowList: regexp2.MustCompile("", 0),
+			errorDenyList:  regexp2.MustCompile("", 0),
 			maxRetry:       0,
 			retryMinWait:   0,
 			retryMaxWait:   0,
@@ -63,6 +63,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 		request          *http.Request
 		mod              *Webhook
 		next             echo.HandlerFunc
+		noDeadline       bool
 		expectError      bool
 		expectHttpError  bool
 		expectHttpStatus int
@@ -76,6 +77,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 					return nil
 				}
 			}(),
+			noDeadline:      false,
 			expectError:     false,
 			expectHttpError: false,
 		},
@@ -87,10 +89,23 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 				return req
 			}(),
 			mod:              buildWebhookModule(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusBadRequest,
 		},
+		{
+			scenario: "context has no deadline",
+			request: func() *http.Request {
+				req := buildMultipartFormDataRequest()
+				req.Header.Set("Gotenberg-Webhook-Url", "foo")
+				req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
+				return req
+			}(),
+			mod:         buildWebhookModule(),
+			noDeadline:  true,
+			expectError: true,
+		},
 		{
 			scenario: "webhook URL is not allowed",
 			request: func() *http.Request {
@@ -101,9 +116,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 			}(),
 			mod: func() *Webhook {
 				mod := buildWebhookModule()
-				mod.allowList = regexp.MustCompile("bar")
+				mod.allowList = regexp2.MustCompile("bar", 0)
 				return mod
 			}(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusForbidden,
@@ -118,9 +134,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 			}(),
 			mod: func() *Webhook {
 				mod := buildWebhookModule()
-				mod.denyList = regexp.MustCompile("foo")
+				mod.denyList = regexp2.MustCompile("foo", 0)
 				return mod
 			}(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusForbidden,
@@ -135,9 +152,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 			}(),
 			mod: func() *Webhook {
 				mod := buildWebhookModule()
-				mod.errorAllowList = regexp.MustCompile("foo")
+				mod.errorAllowList = regexp2.MustCompile("foo", 0)
 				return mod
 			}(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusForbidden,
@@ -152,9 +170,10 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 			}(),
 			mod: func() *Webhook {
 				mod := buildWebhookModule()
-				mod.errorDenyList = regexp.MustCompile("bar")
+				mod.errorDenyList = regexp2.MustCompile("bar", 0)
 				return mod
 			}(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusForbidden,
@@ -169,6 +188,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 				return req
 			}(),
 			mod:              buildWebhookModule(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusBadRequest,
@@ -183,6 +203,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 				return req
 			}(),
 			mod:              buildWebhookModule(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusBadRequest,
@@ -198,6 +219,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 				return req
 			}(),
 			mod:              buildWebhookModule(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusBadRequest,
@@ -213,6 +235,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 				return req
 			}(),
 			mod:              buildWebhookModule(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusBadRequest,
@@ -228,6 +251,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 				return req
 			}(),
 			mod:              buildWebhookModule(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusBadRequest,
@@ -242,6 +266,7 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 				return req
 			}(),
 			mod:              buildWebhookModule(),
+			noDeadline:       false,
 			expectError:      true,
 			expectHttpError:  true,
 			expectHttpStatus: http.StatusBadRequest,
@@ -254,15 +279,20 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
 
 			c := srv.NewContext(tc.request, httptest.NewRecorder())
 
-			ctx := &api.ContextMock{Context: &api.Context{}}
-			ctx.SetEchoContext(c)
-
-			c.Set("context", ctx.Context)
-			c.Set("cancel", func() context.CancelFunc {
-				return func() {
-					return
-				}
-			}())
+			if tc.noDeadline {
+				ctx := &api.ContextMock{Context: &api.Context{Context: context.Background()}}
+				ctx.SetEchoContext(c)
+				c.Set("context", ctx.Context)
+				c.Set("cancel", func() context.CancelFunc {
+					return nil
+				}())
+			} else {
+				timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
+				ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}}
+				ctx.SetEchoContext(c)
+				c.Set("context", ctx.Context)
+				c.Set("cancel", cancel)
+			}
 
 			err := webhookMiddleware(tc.mod).Handler(tc.next)(c)
 
@@ -320,10 +350,10 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
 
 	buildWebhookModule := func() *Webhook {
 		return &Webhook{
-			allowList:      regexp.MustCompile(""),
-			denyList:       regexp.MustCompile(""),
-			errorAllowList: regexp.MustCompile(""),
-			errorDenyList:  regexp.MustCompile(""),
+			allowList:      regexp2.MustCompile("", 0),
+			denyList:       regexp2.MustCompile("", 0),
+			errorAllowList: regexp2.MustCompile("", 0),
+			errorDenyList:  regexp2.MustCompile("", 0),
 			maxRetry:       0,
 			retryMinWait:   0,
 			retryMaxWait:   0,
@@ -426,22 +456,17 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
 			c.Set("trace", "foo")
 			c.Set("startTime", time.Now())
 
-			ctx := &api.ContextMock{Context: &api.Context{}}
+			timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
+			ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}}
 			ctx.SetLogger(zap.NewNop())
 			ctx.SetEchoContext(c)
 
 			c.Set("context", ctx.Context)
-			c.Set("cancel", func() context.CancelFunc {
-				return func() {
-					return
-				}
-			}())
+			c.Set("cancel", cancel)
 
 			webhook := echo.New()
 			webhook.HideBanner = true
 			webhook.HidePort = true
-
-			rand.Seed(time.Now().UnixNano())
 			webhookPort := rand.Intn(65535-1025+1) + 1025
 
 			c.Request().Header.Set("Gotenberg-Webhook-Url", fmt.Sprintf("http://localhost:%d/", webhookPort))

programming_language: Go
filename: webhook.go
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,9 @@
 package webhook
 
 import (
-	"regexp"
 	"time"
 
+	"github.com/dlclark/regexp2"
 	flag "github.com/spf13/pflag"
 
 	"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
@@ -17,10 +17,10 @@ func init() {
 // Webhook is a module which provides a middleware for uploading output files
 // to any destinations in an asynchronous fashion.
 type Webhook struct {
-	allowList      *regexp.Regexp
-	denyList       *regexp.Regexp
-	errorAllowList *regexp.Regexp
-	errorDenyList  *regexp.Regexp
+	allowList      *regexp2.Regexp
+	denyList       *regexp2.Regexp
+	errorAllowList *regexp2.Regexp
+	errorDenyList  *regexp2.Regexp
 	maxRetry       int
 	retryMinWait   time.Duration
 	retryMaxWait   time.Duration

programming_language: Go