diff --git a/cmd/root.go b/cmd/root.go index 8c80bdc..d666bdc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -65,6 +65,7 @@ type AppConfig struct { IsLeakless bool `mapstructure:"leakless"` BlockResources string `mapstructure:"block_resources"` BlockTrackers bool `mapstructure:"block_trackers"` + NoSandbox bool `mapstructure:"no_sandbox"` DebugEndpoints bool `mapstructure:"debug_endpoints"` LogFormat string `mapstructure:"log_format"` MaxProcesses int `mapstructure:"max_processes"` @@ -133,6 +134,7 @@ var flagToConfigKey = map[string]string{ "cb_recovery": "circuit_breaker.recovery_seconds", "cb_successes": "circuit_breaker.successes", "log_format": "app.log_format", + "no-sandbox": "app.no_sandbox", } var RootCmd = &cobra.Command{ @@ -186,6 +188,7 @@ func sanitizedConfigForLog(cfg Config) map[string]interface{} { "leakless": cfg.App.IsLeakless, "block_resources": cfg.App.BlockResources, "block_trackers": cfg.App.BlockTrackers, + "no_sandbox": cfg.App.NoSandbox, "debug_endpoints": cfg.App.DebugEndpoints, "log_format": cfg.App.LogFormat, "max_processes": cfg.App.MaxProcesses, @@ -407,6 +410,7 @@ func setConfigDefaults(v *viper.Viper) { v.SetDefault("app.leakless", false) v.SetDefault("app.block_resources", "") v.SetDefault("app.block_trackers", false) + v.SetDefault("app.no_sandbox", false) v.SetDefault("app.debug_endpoints", false) v.SetDefault("app.max_processes", 4) v.SetDefault("app.idle_ttl", "10m") @@ -468,4 +472,5 @@ func init() { RootCmd.PersistentFlags().IntVar(&config.CircuitBreaker.RecoverySeconds, "cb_recovery", 60, "Seconds before retrying an engine with open circuit") RootCmd.PersistentFlags().IntVar(&config.CircuitBreaker.Successes, "cb_successes", 2, "Consecutive successful half-open checks needed to close circuit") RootCmd.PersistentFlags().StringVar(&config.App.LogFormat, "log_format", "", "Log format: json or text (default: json in production, text in debug)") + RootCmd.PersistentFlags().BoolVar(&config.App.NoSandbox, "no-sandbox", false, "Launch browser with --no-sandbox (for environments where the sandbox SUID helper is unavailable)") } diff --git a/cmd/search.go b/cmd/search.go index b59f77a..0ab293f 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -304,6 +304,7 @@ func newCLIExtractBrowser(cfg extractpkg.Config, proxyURL string, captchaSolverE BrowserPath: config.App.BrowserPath, ProxyURL: proxyURL, Insecure: config.Server.Insecure, + NoSandbox: config.App.NoSandbox, BlockResourceTypes: blockedResourceTypes, BlockTrackers: config.App.BlockTrackers, } @@ -363,6 +364,7 @@ func searchBrowser(ctx context.Context, spec engineSpec, query core.Query, brows BrowserPath: config.App.BrowserPath, ProxyURL: browserProxyURL, Insecure: config.Server.Insecure, + NoSandbox: config.App.NoSandbox, BlockResourceTypes: blockedResourceTypes, BlockTrackers: config.App.BlockTrackers, } diff --git a/cmd/serve.go b/cmd/serve.go index 24cfd0a..58d5979 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -137,6 +137,7 @@ func buildFingerprintBrowserOptions() core.BrowserOpts { Timeout: time.Second * time.Duration(config.App.Timeout), BrowserPath: config.App.BrowserPath, Insecure: config.Server.Insecure, + NoSandbox: config.App.NoSandbox, BlockResourceTypes: blockedResourceTypes, BlockTrackers: config.App.BlockTrackers, } diff --git a/config.yaml b/config.yaml index 39dc90f..fd8f5b1 100644 --- a/config.yaml +++ b/config.yaml @@ -16,6 +16,7 @@ app: leave_head: false # Keep tabs open after request block_resources: "image,font,css,media" # Block heavy subresources in browser mode block_trackers: true # Block known tracker domains + no_sandbox: false # Launch browser with --no-sandbox (use when the sandbox SUID helper is unavailable, e.g. nix on non-NixOS) max_processes: 6 # Concurrent Chrome processes idle_ttl: 5m # close a Chrome that has not served traffic for this long diff --git a/core/browser.go b/core/browser.go index 540d8a4..69674e0 100644 --- a/core/browser.go +++ b/core/browser.go @@ -51,6 +51,9 @@ type BrowserOpts struct { ProxyLaneStore *LaneStore // Insecure allows invalid TLS certificates for browser requests. Insecure bool + // NoSandbox launches Chromium with --no-sandbox. Needed when the sandbox + // helper binary cannot get the SUID bit (e.g. /nix/store is read-only). + NoSandbox bool // UserAgent optionally overrides browser-reported user agent during emulation. UserAgent string // BlockResourceTypes are blocked during page navigation when non-empty. @@ -293,6 +296,9 @@ func NewBrowser(opts BrowserOpts) (*Browser, error) { } else { l = l.Headless(false) } + if opts.NoSandbox { + l = l.NoSandbox(true) + } if path != "" { logrus.WithField("browser_path", path).Debug("Using browser binary") l = l.Bin(path) @@ -355,6 +361,7 @@ func browserOptsLogFields(opts BrowserOpts) logrus.Fields { "browser_path_configured": strings.TrimSpace(opts.BrowserPath) != "", "proxy": maskedProxyLogValue(opts.ProxyURL), "insecure": opts.Insecure, + "no_sandbox": opts.NoSandbox, "user_agent_override": strings.TrimSpace(opts.UserAgent) != "", "block_resource_types": len(opts.BlockResourceTypes), "block_trackers": opts.BlockTrackers,