> ## 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 prepares and restores a local disk snapshot, which is not available on microsandbox cloud."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Document parsers process complex, attacker-controlled file formats. This example 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 snapshot create office-tools --from office-base --integrity --force
    ```

    Verify the snapshot before using it:

    ```sh theme={null}
    msb snapshot verify 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">
    The example accepts a Word document named `input.docx`:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb run -d --name document-worker --replace \
        --from-snapshot office-tools \
        --cpus 2 --memory 2G \
        --user converter --security restricted \
        --no-net --max-duration 2m \
        -- sh -lc 'exec sleep 2m'
      ```

      ```powershell Windows theme={null}
      msb run -d --name document-worker --replace `
        --from-snapshot office-tools `
        --cpus 2 --memory 2G `
        --user converter --security restricted `
        --no-net --max-duration 2m `
        -- sh -lc 'exec sleep 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 rootfs patches cannot be combined with `--from-snapshot`. 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 snapshot rm 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>
