Adds the render.py WeasyPrint renderer, the pod-based bitwarden-print.sh wrapper, a Containerfile, a sample export (JSON + PDF), README and LICENSE.
4.4 KiB
bitwarden-print
Render a Bitwarden .json export as a dense PDF: a summary, a page-numbered
index, and one compact block per vault item that prints every value in the
export — known fields first (username, password, TOTP, card, identity, SSH key,
custom fields, notes, history, attachments), then everything else
alphabetically, including unknown/extra keys and empty objects/lists.
Everything runs in a throwaway Podman pod on this machine; the vault never leaves it.
Requirements
- Podman (tested with 6.x)
- On macOS:
podman machine start(the default machine is used)
Quick start
./bitwarden-print.sh ~/Downloads/bitwarden_export_20261113.json
# -> ~/Downloads/bitwarden_export_20261113.pdf
Custom output and options:
./bitwarden-print.sh vault.json -o ~/Desktop/vault.pdf \
--paper letter --sort name --title "Vault 2026-11-13"
Mask secrets instead of printing them:
./bitwarden-print.sh vault.json --mask-secrets
Options:
| Option | Meaning |
|---|---|
-o, --output FILE |
output PDF (default: next to the export, same basename) |
--sort folder|name|type|as-is |
item order (default: folder, then name) |
--title TEXT |
document title (also the page footer) |
--paper a4|letter |
page size (default: a4) |
--mask-secrets |
mask login.password, login.totp, card.number, card.code, identity.ssn, sshKey.privateKey, …; values keep a length hint |
What the PDF contains
- Header — source file, item count, value count, timestamp.
- Summary — export metadata, item/type/folder counts, custom field, attachment and password-history totals, created/revised date ranges, plus a collections table if the export has one.
- Index — one line per item (type, name, folder, username) with a real page number, clickable in the PDF.
- Items — for each item a heading (number, type badge, name, folder) and a
two-column table of
json.path→ value. Values wrap, newlines in notes are preserved, and empty values (null,"",{},[]) are shown dimmed so nothing silently disappears.
Items are not truncated: an export with many entries produces many pages, but each line is only a few millimetres tall.
How it runs
./bitwarden-print.sh
-> podman build -t localhost/bitwarden-print:latest . # Python + WeasyPrint image
-> podman pod create --name bitwarden-print-pod
-> podman create --pod bitwarden-print-pod ... bitwarden-print-run
-> podman cp export.json bitwarden-print-run:/export.json # no bind mounts
-> podman start -a bitwarden-print-run # render.py -> /output.pdf
-> podman cp bitwarden-print-run:/output.pdf <output>
-> pod, container and temp file are removed
The input is copied into the container instead of bind-mounted, so arbitrary
host paths work (including /tmp, which the Podman machine does not share) and
no SELinux relabelling is required.
Manual equivalent:
podman build -t localhost/bitwarden-print:latest .
podman pod create --name bitwarden-print-pod
podman create --pod bitwarden-print-pod --name bw localhost/bitwarden-print:latest \
/export.json -o /output.pdf
podman cp vault.json bw:/export.json
podman start -a bw
podman cp bw:/output.pdf ./vault.pdf
podman rm -f bw && podman pod rm -f bitwarden-print-pod
Files
bitwarden-print.sh— CLI wrapper that builds the image and drives the podContainerfile—python:3.12-slim-bookworm+ WeasyPrint (Pango/Cairo) + DejaVu fontsrender.py— JSON → HTML → PDF, no data leaves the containerexamples/sample-export.json— synthetic vault used for testing (all values fake)
Run the renderer directly on a host that has WeasyPrint installed:
pip install weasyprint==62.3 pydyf==0.10.0
./render.py examples/sample-export.json -o /tmp/sample.pdf --html-out /tmp/sample.html
Security notes
- The generated PDF contains your vault in plaintext unless you pass
--mask-secrets. Treat the PDF like the export itself: keep it local, and delete it when you are done. --mask-secretsonly masks well-known secret paths. Secrets stored inside custom fields (fields[i].value), notes or file names are printed as-is.- Encrypted exports (
"encrypted": true) are rejected with a hint: re-export from Bitwarden as.jsonwithout a file password first. - The container is removed after each run, but the image
(
localhost/bitwarden-print:latest) stays cached for the next run.