mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
fix(chromium): better default security and DX for allow / deny lists
This commit is contained in:
@@ -225,7 +225,12 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
||||
)
|
||||
}
|
||||
|
||||
err := gotenberg.FilterDeadline(downloadFromCfg.allowList, downloadFromCfg.denyList, dl.Url, deadline)
|
||||
err := gotenberg.FilterDeadline(
|
||||
gotenberg.RegexpToSlice(downloadFromCfg.allowList),
|
||||
gotenberg.RegexpToSlice(downloadFromCfg.denyList),
|
||||
dl.Url,
|
||||
deadline,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filter URL: %w", err)
|
||||
}
|
||||
@@ -430,6 +435,11 @@ func (ctx *Context) FormData() *FormData {
|
||||
}
|
||||
}
|
||||
|
||||
// DirPath returns the path to the request's working directory.
|
||||
func (ctx *Context) DirPath() string {
|
||||
return ctx.dirPath
|
||||
}
|
||||
|
||||
// GeneratePath generates a path within the context's working directory.
|
||||
// It generates a new UUID-based filename. It does not create a file.
|
||||
func (ctx *Context) GeneratePath(extension string) string {
|
||||
|
||||
@@ -42,8 +42,8 @@ type browserArguments struct {
|
||||
hyphenDataDirPath string
|
||||
|
||||
// Tasks specific.
|
||||
allowList *regexp2.Regexp
|
||||
denyList *regexp2.Regexp
|
||||
allowList []*regexp2.Regexp
|
||||
denyList []*regexp2.Regexp
|
||||
clearCache bool
|
||||
clearCookies bool
|
||||
disableJavaScript bool
|
||||
@@ -356,9 +356,10 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
||||
// the extra HTTP headers, if any.
|
||||
// See https://github.com/gotenberg/gotenberg/issues/1011.
|
||||
listenForEventRequestPaused(taskCtx, logger, eventRequestPausedOptions{
|
||||
allowList: b.arguments.allowList,
|
||||
denyList: b.arguments.denyList,
|
||||
extraHttpHeaders: options.ExtraHttpHeaders,
|
||||
allowList: b.arguments.allowList,
|
||||
denyList: b.arguments.denyList,
|
||||
allowedFilePrefixes: options.AllowedFilePrefixes,
|
||||
extraHttpHeaders: options.ExtraHttpHeaders,
|
||||
})
|
||||
|
||||
var (
|
||||
|
||||
@@ -172,6 +172,12 @@ type Options struct {
|
||||
// OmitBackground hides the default white background and allows generating
|
||||
// PDFs with transparency.
|
||||
OmitBackground bool
|
||||
|
||||
// AllowedFilePrefixes restricts file:// sub-resource access to only these
|
||||
// directory prefixes. Applied in listenForEventRequestPaused in addition
|
||||
// to the global allow/deny lists. Set internally by route handlers, not
|
||||
// via form data.
|
||||
AllowedFilePrefixes []string
|
||||
}
|
||||
|
||||
// EmulatedMediaFeature gathers the available entries for emulating a media
|
||||
@@ -421,8 +427,8 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor {
|
||||
fs.Bool("chromium-allow-file-access-from-files", false, "Allow file:// URIs to read other file:// URIs")
|
||||
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.StringSlice("chromium-allow-list", []string{}, "Set the allowed URLs for Chromium using regular expressions - supports multiple values")
|
||||
fs.StringSlice("chromium-deny-list", []string{`^file:(?!//\/tmp/).*`}, "Set the denied URLs for Chromium using regular expressions - supports multiple values")
|
||||
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")
|
||||
@@ -469,8 +475,8 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
|
||||
wsUrlReadTimeout: flags.MustDuration("chromium-start-timeout"),
|
||||
hyphenDataDirPath: hyphenDataDirPath,
|
||||
|
||||
allowList: flags.MustRegexp("chromium-allow-list"),
|
||||
denyList: flags.MustRegexp("chromium-deny-list"),
|
||||
allowList: flags.MustRegexpSlice("chromium-allow-list"),
|
||||
denyList: flags.MustRegexpSlice("chromium-deny-list"),
|
||||
clearCache: flags.MustBool("chromium-clear-cache"),
|
||||
clearCookies: flags.MustBool("chromium-clear-cookies"),
|
||||
disableJavaScript: flags.MustBool("chromium-disable-javascript"),
|
||||
|
||||
@@ -24,7 +24,8 @@ import (
|
||||
)
|
||||
|
||||
type eventRequestPausedOptions struct {
|
||||
allowList, denyList *regexp2.Regexp
|
||||
allowList, denyList []*regexp2.Regexp
|
||||
allowedFilePrefixes []string
|
||||
extraHttpHeaders []ExtraHttpHeader
|
||||
}
|
||||
|
||||
@@ -58,6 +59,25 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, option
|
||||
allow = false
|
||||
}
|
||||
|
||||
// Additional restriction: if the sub-resource is a file:// URL
|
||||
// and we have allowed file prefixes, restrict access to only
|
||||
// those directories. This prevents cross-request file access
|
||||
// in /tmp.
|
||||
if allow && strings.HasPrefix(e.Request.URL, "file://") && len(options.allowedFilePrefixes) > 0 {
|
||||
prefixMatch := false
|
||||
for _, prefix := range options.allowedFilePrefixes {
|
||||
if strings.HasPrefix(e.Request.URL, "file://"+prefix) {
|
||||
prefixMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !prefixMatch {
|
||||
logger.Warn(fmt.Sprintf("'%s' is not within any allowed file prefix", e.Request.URL))
|
||||
allow = false
|
||||
}
|
||||
}
|
||||
|
||||
cctx := chromedp.FromContext(ctx)
|
||||
executorCtx := cdp.WithExecutor(ctx, cctx.Target)
|
||||
|
||||
|
||||
@@ -512,6 +512,7 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("file://%s", inputPath)
|
||||
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths, watermark, stamp, rotateAngle, rotatePages)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert HTML to PDF: %w", err)
|
||||
@@ -542,6 +543,7 @@ func screenshotHtmlRoute(chromium Api) api.Route {
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("file://%s", inputPath)
|
||||
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
||||
err = screenshotUrl(ctx, chromium, url, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTML screenshot: %w", err)
|
||||
@@ -598,6 +600,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
||||
}
|
||||
|
||||
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths, watermark, stamp, rotateAngle, rotatePages)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert markdown to PDF: %w", err)
|
||||
@@ -637,6 +640,7 @@ func screenshotMarkdownRoute(chromium Api) api.Route {
|
||||
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
||||
}
|
||||
|
||||
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
||||
err = screenshotUrl(ctx, chromium, url, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("markdown screenshot: %w", err)
|
||||
|
||||
@@ -114,12 +114,22 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
|
||||
// Let's check if the webhook URLs are acceptable according to our
|
||||
// allowed/denied lists.
|
||||
err := gotenberg.FilterDeadline(w.allowList, w.denyList, webhookUrl, deadline)
|
||||
err := gotenberg.FilterDeadline(
|
||||
gotenberg.RegexpToSlice(w.allowList),
|
||||
gotenberg.RegexpToSlice(w.denyList),
|
||||
webhookUrl,
|
||||
deadline,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filter webhook URL: %w", err)
|
||||
}
|
||||
|
||||
err = gotenberg.FilterDeadline(w.errorAllowList, w.errorDenyList, webhookErrorUrl, deadline)
|
||||
err = gotenberg.FilterDeadline(
|
||||
gotenberg.RegexpToSlice(w.errorAllowList),
|
||||
gotenberg.RegexpToSlice(w.errorDenyList),
|
||||
webhookErrorUrl,
|
||||
deadline,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filter webhook error URL: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user