> ## Documentation Index
> Fetch the complete documentation index at: https://docs.microsandbox.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Convert documents and render PDFs

> Prepare LibreOffice once, then convert untrusted documents without network access

<Tooltip tip="This workflow uses local snapshot verification and destination security/network controls."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Document parsers process complex, attacker-controlled file formats. This guide installs LibreOffice and Poppler once, snapshots the toolchain, and performs each conversion in a fresh worker with networking disabled.

## Convert a document

<Steps>
  <Step title="Prepare the converter">
    Create one script to prepare the reusable toolchain:

    ```sh prepare-office.sh theme={null}
    #!/bin/sh
    set -eu

    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      libreoffice-writer poppler-utils
    rm -rf /var/lib/apt/lists/*
    useradd --system --create-home --shell /usr/sbin/nologin converter
    mkdir -p /input /out
    chown converter:converter /out
    chmod 0555 /input
    chmod 0700 /out
    libreoffice --version
    pdftoppm -v
    ```

    Create the workload script that each worker will run:

    ```sh convert-document.sh theme={null}
    #!/bin/sh
    set -eu

    test "$(id -u)" -ne 0

    if (printf x >> /input/input.docx) 2>/dev/null; then
      echo "input unexpectedly writable" >&2
      exit 1
    fi

    if getent ahosts example.com >/dev/null 2>&1; then
      echo "network unexpectedly reachable" >&2
      exit 1
    fi

    echo "input read-only; network blocked"
    libreoffice --headless --convert-to pdf --outdir /out /input/input.docx
    pdftoppm -png -singlefile -r 96 /out/input.pdf /out/preview
    test -s /out/input.pdf
    test -s /out/preview.png
    stat -c "%n: %s bytes" /out/input.pdf /out/preview.png
    ```

    Install both scripts in the guest, then run the preparation script as the entrypoint:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb run --name office-base --replace \
        --cpus 2 --memory 2G --root-disk 5G \
        --script-path prepare-office:./prepare-office.sh \
        --script-path convert-document:./convert-document.sh \
        --entrypoint prepare-office \
        debian:bookworm-slim
      ```

      ```powershell Windows theme={null}
      msb run --name office-base --replace `
        --cpus 2 --memory 2G --root-disk 5G `
        --script-path prepare-office:./prepare-office.sh `
        --script-path convert-document:./convert-document.sh `
        --entrypoint prepare-office `
        debian:bookworm-slim
      ```
    </CodeGroup>

    Capture the prepared toolchain:

    ```sh theme={null}
    msb snap create office-tools --sandbox office-base --integrity
    ```

    Verify the snapshot before using it:

    ```sh theme={null}
    msb snap verify office-base:office-tools
    ```

    Bookworm currently downloads roughly 130 MB and adds about 440 MB to the guest for this toolchain; package updates can change those numbers. The snapshot lets later workers skip that installation.
  </Step>

  <Step title="Start an offline worker">
    Restore boots an idle detached sandbox with the requested security, network, resource, and lifetime controls already applied. Use an unused destination name; remove a previous stopped worker explicitly before reusing its name. Workload commands run separately with `msb exec`.

    The example accepts a Word document named `input.docx`:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb snap restore office-base:office-tools --name document-worker \
        --cpus 2 --memory 2G \
        --user converter --security restricted \
        --no-net --max-duration 2m
      ```

      ```powershell Windows theme={null}
      msb snap restore office-base:office-tools --name document-worker `
        --cpus 2 --memory 2G `
        --user converter --security restricted `
        --no-net --max-duration 2m
      ```
    </CodeGroup>

    Copy the document into the running worker:

    ```sh theme={null}
    msb cp ./input.docx document-worker:/input/input.docx
    ```

    Make the copied input read-only:

    ```sh theme={null}
    msb exec --user root document-worker -- chmod 0444 /input/input.docx
    ```
  </Step>

  <Step title="Convert the document">
    Run the bounded conversion as the unprivileged `converter` user:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb exec --user converter --timeout 90s \
        --rlimit nproc=128 --rlimit nofile=512 --rlimit fsize=536870912 \
        document-worker -- convert-document
      ```

      ```powershell Windows theme={null}
      msb exec --user converter --timeout 90s `
        --rlimit nproc=128 --rlimit nofile=512 --rlimit fsize=536870912 `
        document-worker -- convert-document
      ```
    </CodeGroup>

    The worker boots before the document is copied because dedicated restore does not accept rootfs patches. The root-owned input directory prevents the unprivileged converter from replacing the document, while its home directory and `/out` hold the only workload-writable state. The untrusted conversion command sets its own process, file-descriptor, and per-file limits.
  </Step>

  <Step title="Export the PDF">
    Prepare a fresh artifact directory on the host:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      export OFFICE_ARTIFACT_DIR="${OFFICE_ARTIFACT_DIR:-$PWD/.office-artifacts}"

      if [ -e "$OFFICE_ARTIFACT_DIR" ] || [ -L "$OFFICE_ARTIFACT_DIR" ]; then
        echo "artifact path already exists: $OFFICE_ARTIFACT_DIR" >&2
        exit 1
      fi

      mkdir -m 700 "$OFFICE_ARTIFACT_DIR"
      ```

      ```powershell Windows theme={null}
      if (-not $env:OFFICE_ARTIFACT_DIR) {
        $env:OFFICE_ARTIFACT_DIR = Join-Path $PWD '.office-artifacts'
      }

      if (Test-Path -LiteralPath $env:OFFICE_ARTIFACT_DIR) {
        throw "artifact path already exists: $env:OFFICE_ARTIFACT_DIR"
      }

      New-Item -ItemType Directory -Path $env:OFFICE_ARTIFACT_DIR | Out-Null
      ```
    </CodeGroup>

    Stop the worker before exporting its output:

    ```sh theme={null}
    msb stop document-worker
    ```

    Copy out only the generated artifacts:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb cp document-worker:/out/input.pdf "$OFFICE_ARTIFACT_DIR/output.pdf"
      msb cp document-worker:/out/preview.png "$OFFICE_ARTIFACT_DIR/preview.png"
      ```

      ```powershell Windows theme={null}
      $pdfPath = Join-Path $env:OFFICE_ARTIFACT_DIR 'output.pdf'
      $previewPath = Join-Path $env:OFFICE_ARTIFACT_DIR 'preview.png'
      msb cp document-worker:/out/input.pdf $pdfPath
      msb cp document-worker:/out/preview.png $previewPath
      ```
    </CodeGroup>

    Validate both exported files:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      for artifact in output.pdf preview.png; do
        test ! -L "$OFFICE_ARTIFACT_DIR/$artifact" &&
          test -f "$OFFICE_ARTIFACT_DIR/$artifact" &&
          test -s "$OFFICE_ARTIFACT_DIR/$artifact" &&
          test "$(wc -c < "$OFFICE_ARTIFACT_DIR/$artifact")" -le 536870912
      done
      ```

      ```powershell Windows theme={null}
      foreach ($artifactPath in $pdfPath, $previewPath) {
        $artifact = Get-Item -LiteralPath $artifactPath
        $isLink = ($artifact.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0
        if ($artifact.PSIsContainer -or $isLink -or $artifact.Length -eq 0 -or $artifact.Length -gt 512MB) {
          throw "$artifactPath failed artifact validation"
        }
      }
      ```
    </CodeGroup>

    Stopping the worker prevents artifact races. The fresh private directory and checks require two nonempty regular files no larger than the conversion command's 512 MiB per-file limit.
  </Step>

  <Step title="Clean up">
    Remove the prepared sandbox and worker:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb rm -f office-base document-worker
      rm -f prepare-office.sh convert-document.sh
      ```

      ```powershell Windows theme={null}
      msb rm -f office-base document-worker
      Remove-Item prepare-office.sh, convert-document.sh
      ```
    </CodeGroup>

    Remove the reusable toolchain snapshot:

    ```sh theme={null}
    msb snap rm office-base:office-tools
    ```

    The checked `output.pdf` and `preview.png` remain in the artifact directory configured above.
  </Step>
</Steps>

<Warning>
  A VM boundary reduces host exposure but does not make every document safe to publish or trust. Enforce input and output size limits before boot, cap concurrent conversions, reject unexpected file types, and scan retained artifacts according to your threat model.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.