Browse Source

Security processing options

DarthSim 2 years ago
parent
commit
9416168575

+ 4 - 0
config/config.go

@@ -42,6 +42,7 @@ var (
 	MaxAnimationFrameResolution int
 	MaxAnimationFrameResolution int
 	MaxSvgCheckBytes            int
 	MaxSvgCheckBytes            int
 	MaxRedirects                int
 	MaxRedirects                int
+	AllowSecurityOptions        bool
 
 
 	JpegProgressive       bool
 	JpegProgressive       bool
 	PngInterlaced         bool
 	PngInterlaced         bool
@@ -226,6 +227,7 @@ func Reset() {
 	MaxAnimationFrameResolution = 0
 	MaxAnimationFrameResolution = 0
 	MaxSvgCheckBytes = 32 * 1024
 	MaxSvgCheckBytes = 32 * 1024
 	MaxRedirects = 10
 	MaxRedirects = 10
+	AllowSecurityOptions = false
 
 
 	JpegProgressive = false
 	JpegProgressive = false
 	PngInterlaced = false
 	PngInterlaced = false
@@ -405,6 +407,8 @@ func Configure() error {
 
 
 	configurators.Bool(&SanitizeSvg, "IMGPROXY_SANITIZE_SVG")
 	configurators.Bool(&SanitizeSvg, "IMGPROXY_SANITIZE_SVG")
 
 
+	configurators.Bool(&AllowSecurityOptions, "IMGPROXY_ALLOW_SECURITY_OPTIONS")
+
 	configurators.Bool(&JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE")
 	configurators.Bool(&JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE")
 	configurators.Bool(&PngInterlaced, "IMGPROXY_PNG_INTERLACED")
 	configurators.Bool(&PngInterlaced, "IMGPROXY_PNG_INTERLACED")
 	configurators.Bool(&PngQuantize, "IMGPROXY_PNG_QUANTIZE")
 	configurators.Bool(&PngQuantize, "IMGPROXY_PNG_QUANTIZE")

+ 4 - 0
docs/configuration.md

@@ -114,6 +114,10 @@ Also you may want imgproxy to respond with the same error message that it writes
 
 
 * `IMGPROXY_DEVELOPMENT_ERRORS_MODE`: when true, imgproxy will respond with detailed error messages. Not recommended for production because some errors may contain stack traces.
 * `IMGPROXY_DEVELOPMENT_ERRORS_MODE`: when true, imgproxy will respond with detailed error messages. Not recommended for production because some errors may contain stack traces.
 
 
+* `IMGPROXY_ALLOW_SECURITY_OPTIONS`: when `true`, allows usage of security-related processing options such as `max_src_resolution`, `max_src_file_size`, `max_animation_frames`, and `max_animation_frame_resolution`. Default: `false`.
+
+**⚠️Warning:** `IMGPROXY_ALLOW_SECURITY_OPTIONS` allows bypassing your security restrictions. Don't set it to `true` unless you are completely sure that an attacker can't change your imgproxy URLs.
+
 ## Cookies
 ## Cookies
 
 
 imgproxy can pass cookies in image requests. This can be activated with `IMGPROXY_COOKIE_PASSTHROUGH`. Unfortunately the `Cookie` header doesn't contain information about which URLs these cookies are applicable to, so imgproxy can only assume (or must be told).
 imgproxy can pass cookies in image requests. This can be activated with `IMGPROXY_COOKIE_PASSTHROUGH`. Unfortunately the `Cookie` header doesn't contain information about which URLs these cookies are applicable to, so imgproxy can only assume (or must be told).

+ 44 - 0
docs/generating_the_url.md

@@ -795,6 +795,50 @@ Read more about presets in the [Presets](presets.md) guide.
 
 
 Default: empty
 Default: empty
 
 
+### Max src resolution
+
+```
+max_src_resolution:%resolution
+msr:%resolution
+```
+
+Allows redefining `IMGPROXY_MAX_SRC_RESOLUTION` config.
+
+**⚠️Warning:** Since this option allows redefining a security restriction, its usage is not allowed unless the `IMGPROXY_ALLOW_SECURITY_OPTIONS` config is set to `true`.
+
+### Max src file size
+
+```
+max_src_file_size:%size
+msfs:%size
+```
+
+Allows redefining `IMGPROXY_MAX_SRC_FILE_SIZE` config.
+
+**⚠️Warning:** Since this option allows redefining a security restriction, its usage is not allowed unless the `IMGPROXY_ALLOW_SECURITY_OPTIONS` config is set to `true`.
+
+### Max animation frames
+
+```
+max_animation_frames:%size
+maf:%size
+```
+
+Allows redefining `IMGPROXY_MAX_ANIMATION_FRAMES` config.
+
+**⚠️Warning:** Since this option allows redefining a security restriction, its usage is not allowed unless the `IMGPROXY_ALLOW_SECURITY_OPTIONS` config is set to `true`.
+
+### Max animation frame resolution
+
+```
+max_animation_frame_resolution:%size
+mafr:%size
+```
+
+Allows redefining `IMGPROXY_MAX_ANIMATION_FRAME_RESOLUTION` config.
+
+**⚠️Warning:** Since this option allows redefining a security restriction, its usage is not allowed unless the `IMGPROXY_ALLOW_SECURITY_OPTIONS` config is set to `true`.
+
 ## Source URL
 ## Source URL
 ### Plain
 ### Plain
 
 

+ 3 - 2
imagedata/download.go

@@ -11,6 +11,7 @@ import (
 
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
+	"github.com/imgproxy/imgproxy/v3/security"
 
 
 	azureTransport "github.com/imgproxy/imgproxy/v3/transport/azure"
 	azureTransport "github.com/imgproxy/imgproxy/v3/transport/azure"
 	fsTransport "github.com/imgproxy/imgproxy/v3/transport/fs"
 	fsTransport "github.com/imgproxy/imgproxy/v3/transport/fs"
@@ -205,7 +206,7 @@ func requestImage(imageURL string, header http.Header, jar *cookiejar.Jar) (*htt
 	return res, nil
 	return res, nil
 }
 }
 
 
-func download(imageURL string, header http.Header, jar *cookiejar.Jar) (*ImageData, error) {
+func download(imageURL string, header http.Header, jar *cookiejar.Jar, secopts security.Options) (*ImageData, error) {
 	// We use this for testing
 	// We use this for testing
 	if len(redirectAllRequestsTo) > 0 {
 	if len(redirectAllRequestsTo) > 0 {
 		imageURL = redirectAllRequestsTo
 		imageURL = redirectAllRequestsTo
@@ -234,7 +235,7 @@ func download(imageURL string, header http.Header, jar *cookiejar.Jar) (*ImageDa
 		contentLength = 0
 		contentLength = 0
 	}
 	}
 
 
-	imgdata, err := readAndCheckImage(body, contentLength)
+	imgdata, err := readAndCheckImage(body, contentLength, secopts)
 	if err != nil {
 	if err != nil {
 		return nil, ierrors.Wrap(err, 0)
 		return nil, ierrors.Wrap(err, 0)
 	}
 	}

+ 13 - 12
imagedata/image_data.go

@@ -13,6 +13,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
+	"github.com/imgproxy/imgproxy/v3/security"
 )
 )
 
 
 var (
 var (
@@ -61,17 +62,17 @@ func Init() error {
 
 
 func loadWatermark() (err error) {
 func loadWatermark() (err error) {
 	if len(config.WatermarkData) > 0 {
 	if len(config.WatermarkData) > 0 {
-		Watermark, err = FromBase64(config.WatermarkData, "watermark")
+		Watermark, err = FromBase64(config.WatermarkData, "watermark", security.DefaultOptions())
 		return
 		return
 	}
 	}
 
 
 	if len(config.WatermarkPath) > 0 {
 	if len(config.WatermarkPath) > 0 {
-		Watermark, err = FromFile(config.WatermarkPath, "watermark")
+		Watermark, err = FromFile(config.WatermarkPath, "watermark", security.DefaultOptions())
 		return
 		return
 	}
 	}
 
 
 	if len(config.WatermarkURL) > 0 {
 	if len(config.WatermarkURL) > 0 {
-		Watermark, err = Download(config.WatermarkURL, "watermark", nil, nil)
+		Watermark, err = Download(config.WatermarkURL, "watermark", nil, nil, security.DefaultOptions())
 		return
 		return
 	}
 	}
 
 
@@ -81,11 +82,11 @@ func loadWatermark() (err error) {
 func loadFallbackImage() (err error) {
 func loadFallbackImage() (err error) {
 	switch {
 	switch {
 	case len(config.FallbackImageData) > 0:
 	case len(config.FallbackImageData) > 0:
-		FallbackImage, err = FromBase64(config.FallbackImageData, "fallback image")
+		FallbackImage, err = FromBase64(config.FallbackImageData, "fallback image", security.DefaultOptions())
 	case len(config.FallbackImagePath) > 0:
 	case len(config.FallbackImagePath) > 0:
-		FallbackImage, err = FromFile(config.FallbackImagePath, "fallback image")
+		FallbackImage, err = FromFile(config.FallbackImagePath, "fallback image", security.DefaultOptions())
 	case len(config.FallbackImageURL) > 0:
 	case len(config.FallbackImageURL) > 0:
-		FallbackImage, err = Download(config.FallbackImageURL, "fallback image", nil, nil)
+		FallbackImage, err = Download(config.FallbackImageURL, "fallback image", nil, nil, security.DefaultOptions())
 	default:
 	default:
 		FallbackImage, err = nil, nil
 		FallbackImage, err = nil, nil
 	}
 	}
@@ -100,11 +101,11 @@ func loadFallbackImage() (err error) {
 	return err
 	return err
 }
 }
 
 
-func FromBase64(encoded, desc string) (*ImageData, error) {
+func FromBase64(encoded, desc string, secopts security.Options) (*ImageData, error) {
 	dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encoded))
 	dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encoded))
 	size := 4 * (len(encoded)/3 + 1)
 	size := 4 * (len(encoded)/3 + 1)
 
 
-	imgdata, err := readAndCheckImage(dec, size)
+	imgdata, err := readAndCheckImage(dec, size, secopts)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("Can't decode %s: %s", desc, err)
 		return nil, fmt.Errorf("Can't decode %s: %s", desc, err)
 	}
 	}
@@ -112,7 +113,7 @@ func FromBase64(encoded, desc string) (*ImageData, error) {
 	return imgdata, nil
 	return imgdata, nil
 }
 }
 
 
-func FromFile(path, desc string) (*ImageData, error) {
+func FromFile(path, desc string, secopts security.Options) (*ImageData, error) {
 	f, err := os.Open(path)
 	f, err := os.Open(path)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("Can't read %s: %s", desc, err)
 		return nil, fmt.Errorf("Can't read %s: %s", desc, err)
@@ -123,7 +124,7 @@ func FromFile(path, desc string) (*ImageData, error) {
 		return nil, fmt.Errorf("Can't read %s: %s", desc, err)
 		return nil, fmt.Errorf("Can't read %s: %s", desc, err)
 	}
 	}
 
 
-	imgdata, err := readAndCheckImage(f, int(fi.Size()))
+	imgdata, err := readAndCheckImage(f, int(fi.Size()), secopts)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("Can't read %s: %s", desc, err)
 		return nil, fmt.Errorf("Can't read %s: %s", desc, err)
 	}
 	}
@@ -131,8 +132,8 @@ func FromFile(path, desc string) (*ImageData, error) {
 	return imgdata, nil
 	return imgdata, nil
 }
 }
 
 
-func Download(imageURL, desc string, header http.Header, jar *cookiejar.Jar) (*ImageData, error) {
-	imgdata, err := download(imageURL, header, jar)
+func Download(imageURL, desc string, header http.Header, jar *cookiejar.Jar, secopts security.Options) (*ImageData, error) {
+	imgdata, err := download(imageURL, header, jar, secopts)
 	if err != nil {
 	if err != nil {
 		if nmErr, ok := err.(*ErrorNotModified); ok {
 		if nmErr, ok := err.(*ErrorNotModified); ok {
 			nmErr.Message = fmt.Sprintf("Can't download %s: %s", desc, nmErr.Message)
 			nmErr.Message = fmt.Sprintf("Can't download %s: %s", desc, nmErr.Message)

+ 6 - 28
imagedata/read.go

@@ -13,10 +13,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/security"
 	"github.com/imgproxy/imgproxy/v3/security"
 )
 )
 
 
-var (
-	ErrSourceFileTooBig            = ierrors.New(422, "Source image file is too big", "Invalid source image")
-	ErrSourceImageTypeNotSupported = ierrors.New(422, "Source image type not supported", "Invalid source image")
-)
+var ErrSourceImageTypeNotSupported = ierrors.New(422, "Source image type not supported", "Invalid source image")
 
 
 var downloadBufPool *bufpool.Pool
 var downloadBufPool *bufpool.Pool
 
 
@@ -24,34 +21,15 @@ func initRead() {
 	downloadBufPool = bufpool.New("download", config.Concurrency, config.DownloadBufferSize)
 	downloadBufPool = bufpool.New("download", config.Concurrency, config.DownloadBufferSize)
 }
 }
 
 
-type hardLimitReader struct {
-	r    io.Reader
-	left int
-}
-
-func (lr *hardLimitReader) Read(p []byte) (n int, err error) {
-	if lr.left <= 0 {
-		return 0, ErrSourceFileTooBig
-	}
-	if len(p) > lr.left {
-		p = p[0:lr.left]
-	}
-	n, err = lr.r.Read(p)
-	lr.left -= n
-	return
-}
-
-func readAndCheckImage(r io.Reader, contentLength int) (*ImageData, error) {
-	if config.MaxSrcFileSize > 0 && contentLength > config.MaxSrcFileSize {
-		return nil, ErrSourceFileTooBig
+func readAndCheckImage(r io.Reader, contentLength int, secopts security.Options) (*ImageData, error) {
+	if err := security.CheckFileSize(contentLength, secopts); err != nil {
+		return nil, err
 	}
 	}
 
 
 	buf := downloadBufPool.Get(contentLength, false)
 	buf := downloadBufPool.Get(contentLength, false)
 	cancel := func() { downloadBufPool.Put(buf) }
 	cancel := func() { downloadBufPool.Put(buf) }
 
 
-	if config.MaxSrcFileSize > 0 {
-		r = &hardLimitReader{r: r, left: config.MaxSrcFileSize}
-	}
+	r = security.LimitFileSize(r, secopts)
 
 
 	br := bufreader.New(r, buf)
 	br := bufreader.New(r, buf)
 
 
@@ -67,7 +45,7 @@ func readAndCheckImage(r io.Reader, contentLength int) (*ImageData, error) {
 		return nil, checkTimeoutErr(err)
 		return nil, checkTimeoutErr(err)
 	}
 	}
 
 
-	if err = security.CheckDimensions(meta.Width(), meta.Height(), 1); err != nil {
+	if err = security.CheckDimensions(meta.Width(), meta.Height(), 1, secopts); err != nil {
 		buf.Reset()
 		buf.Reset()
 		cancel()
 		cancel()
 		return nil, err
 		return nil, err

+ 86 - 0
options/processing_options.go

@@ -14,6 +14,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/imath"
 	"github.com/imgproxy/imgproxy/v3/imath"
+	"github.com/imgproxy/imgproxy/v3/security"
 	"github.com/imgproxy/imgproxy/v3/structdiff"
 	"github.com/imgproxy/imgproxy/v3/structdiff"
 	"github.com/imgproxy/imgproxy/v3/vips"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 )
@@ -108,6 +109,8 @@ type ProcessingOptions struct {
 
 
 	UsedPresets []string
 	UsedPresets []string
 
 
+	SecurityOptions security.Options
+
 	defaultQuality int
 	defaultQuality int
 }
 }
 
 
@@ -143,6 +146,8 @@ func NewProcessingOptions() *ProcessingOptions {
 		SkipProcessingFormats: append([]imagetype.Type(nil), config.SkipProcessingFormats...),
 		SkipProcessingFormats: append([]imagetype.Type(nil), config.SkipProcessingFormats...),
 		UsedPresets:           make([]string, 0, len(config.Presets)),
 		UsedPresets:           make([]string, 0, len(config.Presets)),
 
 
+		SecurityOptions: security.DefaultOptions(),
+
 		// Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
 		// Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
 		defaultQuality: config.Quality,
 		defaultQuality: config.Quality,
 	}
 	}
@@ -884,6 +889,78 @@ func applyReturnAttachmentOption(po *ProcessingOptions, args []string) error {
 	return nil
 	return nil
 }
 }
 
 
+func applyMaxSrcResolutionOption(po *ProcessingOptions, args []string) error {
+	if err := security.IsSecurityOptionsAllowed(); err != nil {
+		return err
+	}
+
+	if len(args) > 1 {
+		return fmt.Errorf("Invalid max_src_resolution arguments: %v", args)
+	}
+
+	if x, err := strconv.ParseFloat(args[0], 64); err == nil && x > 0 {
+		po.SecurityOptions.MaxSrcResolution = int(x * 1000000)
+	} else {
+		return fmt.Errorf("Invalid max_src_resolution: %s", args[0])
+	}
+
+	return nil
+}
+
+func applyMaxSrcFileSizeOption(po *ProcessingOptions, args []string) error {
+	if err := security.IsSecurityOptionsAllowed(); err != nil {
+		return err
+	}
+
+	if len(args) > 1 {
+		return fmt.Errorf("Invalid max_src_file_size arguments: %v", args)
+	}
+
+	if x, err := strconv.Atoi(args[0]); err == nil {
+		po.SecurityOptions.MaxSrcFileSize = x
+	} else {
+		return fmt.Errorf("Invalid max_src_file_size: %s", args[0])
+	}
+
+	return nil
+}
+
+func applyMaxAnimationFramesOption(po *ProcessingOptions, args []string) error {
+	if err := security.IsSecurityOptionsAllowed(); err != nil {
+		return err
+	}
+
+	if len(args) > 1 {
+		return fmt.Errorf("Invalid max_animation_frames arguments: %v", args)
+	}
+
+	if x, err := strconv.Atoi(args[0]); err == nil && x > 0 {
+		po.SecurityOptions.MaxAnimationFrames = x
+	} else {
+		return fmt.Errorf("Invalid max_animation_frames: %s", args[0])
+	}
+
+	return nil
+}
+
+func applyMaxAnimationFrameResolutionOption(po *ProcessingOptions, args []string) error {
+	if err := security.IsSecurityOptionsAllowed(); err != nil {
+		return err
+	}
+
+	if len(args) > 1 {
+		return fmt.Errorf("Invalid max_animation_frame_resolution arguments: %v", args)
+	}
+
+	if x, err := strconv.ParseFloat(args[0], 64); err == nil {
+		po.SecurityOptions.MaxAnimationFrameResolution = int(x * 1000000)
+	} else {
+		return fmt.Errorf("Invalid max_animation_frame_resolution: %s", args[0])
+	}
+
+	return nil
+}
+
 func applyURLOption(po *ProcessingOptions, name string, args []string) error {
 func applyURLOption(po *ProcessingOptions, name string, args []string) error {
 	switch name {
 	switch name {
 	case "resize", "rs":
 	case "resize", "rs":
@@ -965,6 +1042,15 @@ func applyURLOption(po *ProcessingOptions, name string, args []string) error {
 	// Presets
 	// Presets
 	case "preset", "pr":
 	case "preset", "pr":
 		return applyPresetOption(po, args)
 		return applyPresetOption(po, args)
+	// Security
+	case "max_src_resolution", "msr":
+		return applyMaxSrcResolutionOption(po, args)
+	case "max_src_file_size", "msfs":
+		return applyMaxSrcFileSizeOption(po, args)
+	case "max_animation_frames", "maf":
+		return applyMaxAnimationFramesOption(po, args)
+	case "max_animation_frame_resolution", "mafr":
+		return applyMaxAnimationFrameResolutionOption(po, args)
 	}
 	}
 
 
 	return fmt.Errorf("Unknown processing option: %s", name)
 	return fmt.Errorf("Unknown processing option: %s", name)

+ 1 - 1
processing/processing.go

@@ -120,7 +120,7 @@ func transformAnimated(ctx context.Context, img *vips.Image, po *options.Process
 	framesCount := imath.Min(img.Height()/frameHeight, config.MaxAnimationFrames)
 	framesCount := imath.Min(img.Height()/frameHeight, config.MaxAnimationFrames)
 
 
 	// Double check dimensions because animated image has many frames
 	// Double check dimensions because animated image has many frames
-	if err = security.CheckDimensions(imgWidth, frameHeight, framesCount); err != nil {
+	if err = security.CheckDimensions(imgWidth, frameHeight, framesCount, po.SecurityOptions); err != nil {
 		return err
 		return err
 	}
 	}
 
 

+ 1 - 1
processing_handler.go

@@ -295,7 +295,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
 			checkErr(ctx, "download", err)
 			checkErr(ctx, "download", err)
 		}
 		}
 
 
-		return imagedata.Download(imageURL, "source image", imgRequestHeader, cookieJar)
+		return imagedata.Download(imageURL, "source image", imgRequestHeader, cookieJar, po.SecurityOptions)
 	}()
 	}()
 
 
 	if err == nil {
 	if err == nil {

+ 42 - 0
security/file_size.go

@@ -0,0 +1,42 @@
+package security
+
+import (
+	"io"
+
+	"github.com/imgproxy/imgproxy/v3/ierrors"
+)
+
+var ErrSourceFileTooBig = ierrors.New(422, "Source image file is too big", "Invalid source image")
+
+type hardLimitReader struct {
+	r    io.Reader
+	left int
+}
+
+func (lr *hardLimitReader) Read(p []byte) (n int, err error) {
+	if lr.left <= 0 {
+		return 0, ErrSourceFileTooBig
+	}
+	if len(p) > lr.left {
+		p = p[0:lr.left]
+	}
+	n, err = lr.r.Read(p)
+	lr.left -= n
+	return
+}
+
+func CheckFileSize(size int, opts Options) error {
+	if opts.MaxSrcFileSize > 0 && size > opts.MaxSrcFileSize {
+		return ErrSourceFileTooBig
+	}
+
+	return nil
+}
+
+func LimitFileSize(r io.Reader, opts Options) io.Reader {
+	if opts.MaxSrcFileSize > 0 {
+		return &hardLimitReader{r: r, left: opts.MaxSrcFileSize}
+	}
+
+	return r
+}

+ 4 - 5
security/image_size.go

@@ -1,7 +1,6 @@
 package security
 package security
 
 
 import (
 import (
-	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/imath"
 	"github.com/imgproxy/imgproxy/v3/imath"
 )
 )
@@ -9,15 +8,15 @@ import (
 var ErrSourceResolutionTooBig = ierrors.New(422, "Source image resolution is too big", "Invalid source image")
 var ErrSourceResolutionTooBig = ierrors.New(422, "Source image resolution is too big", "Invalid source image")
 var ErrSourceFrameResolutionTooBig = ierrors.New(422, "Source image frame resolution is too big", "Invalid source image")
 var ErrSourceFrameResolutionTooBig = ierrors.New(422, "Source image frame resolution is too big", "Invalid source image")
 
 
-func CheckDimensions(width, height, frames int) error {
+func CheckDimensions(width, height, frames int, opts Options) error {
 	frames = imath.Max(frames, 1)
 	frames = imath.Max(frames, 1)
 
 
-	if frames > 1 && config.MaxAnimationFrameResolution > 0 {
-		if width*height > config.MaxAnimationFrameResolution {
+	if frames > 1 && opts.MaxAnimationFrameResolution > 0 {
+		if width*height > opts.MaxAnimationFrameResolution {
 			return ErrSourceFrameResolutionTooBig
 			return ErrSourceFrameResolutionTooBig
 		}
 		}
 	} else {
 	} else {
-		if width*height*frames > config.MaxSrcResolution {
+		if width*height*frames > opts.MaxSrcResolution {
 			return ErrSourceResolutionTooBig
 			return ErrSourceResolutionTooBig
 		}
 		}
 	}
 	}

+ 32 - 0
security/options.go

@@ -0,0 +1,32 @@
+package security
+
+import (
+	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/ierrors"
+)
+
+var ErrSecurityOptionsNotAllowed = ierrors.New(403, "Security processing options are not allowed", "Invalid URL")
+
+type Options struct {
+	MaxSrcResolution            int
+	MaxSrcFileSize              int
+	MaxAnimationFrames          int
+	MaxAnimationFrameResolution int
+}
+
+func DefaultOptions() Options {
+	return Options{
+		MaxSrcResolution:            config.MaxSrcResolution,
+		MaxSrcFileSize:              config.MaxSrcFileSize,
+		MaxAnimationFrames:          config.MaxAnimationFrames,
+		MaxAnimationFrameResolution: config.MaxAnimationFrameResolution,
+	}
+}
+
+func IsSecurityOptionsAllowed() error {
+	if config.AllowSecurityOptions {
+		return nil
+	}
+
+	return ErrSecurityOptionsNotAllowed
+}