Skip to content

Build1 publisher3 min readPublished

Bubblewrap hands the PDF binary one directory and no network by default

Escaping an uploaded filename keeps it from turning into a shell command, and it still leaves the parser reading everything the worker's account can read. A dev.to write-up draws the other boundary with Linux namespaces.

The Engineer · Build desk

Illustration accompanying Bubblewrap hands the PDF binary one directory and no network by default

What happened

  • A dev.to write-up takes the common Laravel pattern of a queued job shelling out to pdftotext with escapeshellarg wrapped around an uploaded file path, and asks what that child process can see if it goes wrong.
  • Its answer is Bubblewrap, which builds a restricted environment from Linux namespaces and mount controls, giving each job a private directory mounted inside as /work and withholding the application source tree.
  • The PHP application stays outside the sandbox; a runner sitting between Laravel and the tool creates it, and only the spawned document-processing binary runs inside.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Confined to a per-job workspace with the parent environment withheld, an exploit that lands inside the parser has to break out of the namespace before it can read a credential.
  • decision Teams that switched from shell_exec to Symfony Process for injection safety now have a second question to answer on its own: whether the parser's filesystem and network reach is acceptable.
  • cost Adoption turns every runtime dependency of the binary into an explicit mount, and whoever maintains that list pays again each time the tool or its packaging changes.
  • exposure When a document tool genuinely needs egress, granting it becomes a recorded choice in the runner configuration instead of a side effect of the account the worker happens to use.

The quoting is the easy part. escapeshellarg makes /bin/sh treat a filename as one argument, never as a command. The dev.to write-up says the Symfony Process version is "already better from a command-injection perspective because the arguments are separated rather than concatenated into a shell command" [3]. The exec that follows is the same either way. The queue worker runs as www-data, and the PDF processor starts under that same Unix identity [6].

The post lists what the identity reaches: application source code, configuration files, environment variables, credentials available to the process, other files owned by the same account, other job directories, network access, writable temporary directories, and CPU, file descriptors and disk resources [5]. Nine categories. Against that, the sandbox diagram shows the process seeing two things, /work and its own executable, with the parent environment not passed through [9][18].

The write-up separates the two jobs by the question each one answers. Validation asks "Should I accept this input?" [13]. Sandboxing asks "If the processor is compromised anyway, what can it reach?" [14]. The reason given for keeping both is that a valid PDF can still trigger a bug in a parser, and a newly discovered vulnerability may affect a file that passes every rule you currently have [15]. A PDF carries compressed streams, fonts, object graphs, embedded files and malformed structures, and the parser has to handle all of them [17].

The /work model transfers cleanly for a tool like pdftotext, which the post describes as needing only an input file and somewhere to write an output file [7]. It transfers less cleanly for a binary that wants something the diagram does not list. A converter that loads system fonts or reads a config file from a home directory needs each of those paths mounted on purpose. One that takes its settings from an environment variable has to be handed the variable, because the sandbox does not inherit the parent environment [9].

Network access is off unless someone turns it on, and the post argues there is no legitimate reason for a binary processing a local file to reach the internet, so enabling it should be an explicit decision [11].

The sandbox sits in a list with validation, patching, least-privilege Unix accounts, sensible server configuration, process timeouts and monitoring, none of which it replaces [16]. Its stated aim is to reduce attack surface and limit the blast radius when the processor is not safe [19]. The post does not publish the bubblewrap command line it generates, or any timing for per-job namespace setup [20].

Where this lands depends on the account the workers already use. A queue running as its own user, with no read access to the application tree, has most of the value before any namespace is created. A queue running as www-data beside the source and the configuration files has none of it, and the post's example is the second case [6].

What to watch

  • Whether the library the post describes publishes the bubblewrap flag set it generates, so its defaults can be audited.
  • A timing comparison between a bare pdftotext run and the same run with per-job namespace and mount setup.
  • Whether the same per-job /work model is applied to office-document converters and OCR tools, which pull in more runtime dependencies than pdftotext.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories