@@ -7,11 +7,11 @@ import (
77 "encoding/hex"
88 "errors"
99 "fmt"
10- "io/fs"
1110 "log/slog"
1211 "net"
1312 "os"
1413 "os/exec"
14+ "strconv"
1515 "strings"
1616 "sync"
1717 "syscall"
@@ -51,6 +51,8 @@ type Options struct {
5151 // ChromiumPort is the port where chromium will be instructed to listen.
5252 // Defaults to 5222.
5353 ChromiumPort string
54+ // DisableBubblewrap runs chromium directly, instead of using the bubblewrap sandbox (`bwrap`).
55+ DisableBubblewrap bool
5456 // Maximum time a browser is allowed to be running, after which it will be killed unconditionally.
5557 // Defaults to 5m.
5658 SessionTimeout time.Duration
@@ -254,20 +256,29 @@ func (s *Supervisor) launch(ctx context.Context, sessionID string) error {
254256 stdout := & bytes.Buffer {}
255257 stderr := & bytes.Buffer {}
256258
257- tmpDir , err := s .mkdirTemp ()
258- if err != nil {
259- return fmt .Errorf ("creating temporary directory: %w" , err )
259+ bwrapArgs := []string {
260+ "--die-with-parent" , // Ensures child process (COMMAND) dies when bwrap's parent dies.
261+ "--unshare-all" ,
262+ "--share-net" ,
263+ "--proc" , "/proc" ,
264+ "--dev" , "/dev" ,
265+ "--ro-bind" , "/etc" , "/etc" ,
266+ "--ro-bind" , "/usr" , "/usr" ,
267+ "--ro-bind" , "/lib" , "/lib" , // TODO: Remove after Alpine's /usr merge.
268+ "--ro-bind" , "/bin" , "/bin" , // TODO: Remove after Alpine's /usr merge.
269+ "--dir" , "/tmp" ,
270+ "--clearenv" ,
271+ "--setenv" , "TMPDIR" , "/tmp" ,
260272 }
261273
262- defer func () {
263- // Clean up files after chromium exits.
264- err := os .RemoveAll (tmpDir )
265- if err != nil {
266- panic (fmt .Errorf ("deleting tmpdir, bug or sandbox compromised: %w" , err ))
267- }
268- }()
274+ if s .opts .UserGroup != 0 {
275+ bwrapArgs = append (bwrapArgs ,
276+ "--uid" , strconv .Itoa (s .opts .UserGroup ),
277+ "--gid" , strconv .Itoa (s .opts .UserGroup ),
278+ )
279+ }
269280
270- args := []string {
281+ chromiumArgs := []string {
271282 // The following flags have been tested to be required:
272283 "--headless" ,
273284 "--remote-debugging-address=0.0.0.0" ,
@@ -291,42 +302,38 @@ func (s *Supervisor) launch(ctx context.Context, sessionID string) error {
291302 }
292303
293304 if s .userAgent != "" {
294- args = append (
295- args ,
305+ chromiumArgs = append (
306+ chromiumArgs ,
296307 "--user-agent=" + s .userAgent ,
297308 )
298309 }
299310
300- cmd := exec .CommandContext (ctx ,
301- s .opts .ChromiumPath ,
302- args ... ,
303- )
304- cmd .Env = []string {
305- // Chromium uses this env var to figure where the temporary directory is. We want that to be the directory
306- // we created for this session, because /tmp is read-only in production.
307- // https://github.com/chromium/chromium/blob/7c4f56ca9dba3a884212ef3a71c8db5d3633f0a6/base/files/file_util_posix.cc#L764
308- "TMPDIR=" + tmpDir ,
311+ var cmd * exec.Cmd
312+ if s .opts .DisableBubblewrap {
313+ logger .Warn ("bubblewrap is disabled, chromium will run with highly elevated permissions" )
314+
315+ cmd = exec .CommandContext (ctx ,
316+ s .opts .ChromiumPath ,
317+ chromiumArgs ... ,
318+ )
319+ } else {
320+ cmd = exec .CommandContext (ctx ,
321+ "bwrap" ,
322+ append (append (bwrapArgs , s .opts .ChromiumPath ), chromiumArgs ... )... ,
323+ )
309324 }
325+
310326 cmd .Stdout = stdout
311327 cmd .Stderr = stderr
312- if s .opts .UserGroup != 0 {
313- cmd .SysProcAttr = & syscall.SysProcAttr {
314- Credential : & syscall.Credential {
315- Uid : uint32 (s .opts .UserGroup ),
316- Gid : uint32 (s .opts .UserGroup ),
317- },
318- }
319- }
320328
321329 created := time .Now ()
322330 defer func () {
323331 s .metrics .SessionDuration .Observe (time .Since (created ).Seconds ())
324332 }()
325333
326- err = cmd .Run ()
327-
328334 attrs := make ([]slog.Attr , 0 , 9 )
329335
336+ err := cmd .Run ()
330337 if err != nil {
331338 attrs = append (attrs , slog.Attr {Key : "err" , Value : slog .AnyValue (err )})
332339 }
@@ -382,37 +389,6 @@ func (s *Supervisor) launch(ctx context.Context, sessionID string) error {
382389 return nil
383390}
384391
385- func (s * Supervisor ) mkdirTemp () (string , error ) {
386- _ , err := os .Stat (s .opts .TempDir )
387- if errors .Is (err , fs .ErrNotExist ) {
388- s .logger .Warn (
389- "Specified TempDir does not exist, is it mounted? Falling back to creating it." ,
390- "TempDir" , s .opts .TempDir ,
391- )
392- err = os .MkdirAll (s .opts .TempDir , 0o755 ) // 700 would not allow other users to descend into subdirectories.
393- if err != nil {
394- return "" , fmt .Errorf ("tmpdir does not exist and couldn't be created: %w" , err )
395- }
396- }
397-
398- tmpDir , err := os .MkdirTemp (s .opts .TempDir , "" )
399- if err != nil {
400- return "" , err
401- }
402-
403- if s .opts .UserGroup == 0 {
404- // No chowning necessary.
405- return tmpDir , nil
406- }
407-
408- err = os .Chown (tmpDir , s .opts .UserGroup , s .opts .UserGroup )
409- if err != nil {
410- return "" , fmt .Errorf ("chowning temporary dir: %w" , err )
411- }
412-
413- return tmpDir , nil
414- }
415-
416392// ComputeUserAgent runs chromium once, retrieves its default user agent, and stores a patched version so it can be used
417393// in all subsequent calls.
418394func (s * Supervisor ) ComputeUserAgent (ctx context.Context ) error {
0 commit comments