Initial commit: Bitwarden export to PDF renderer
Adds the render.py WeasyPrint renderer, the pod-based bitwarden-print.sh wrapper, a Containerfile, a sample export (JSON + PDF), README and LICENSE.
This commit is contained in:
commit
41385b35e6
8 files changed
+1041
No files matched your search
@@ -0,0 +1,117 @@
|
||||
# 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
|
||||
|
||||
```sh
|
||||
./bitwarden-print.sh ~/Downloads/bitwarden_export_20261113.json
|
||||
# -> ~/Downloads/bitwarden_export_20261113.pdf
|
||||
```
|
||||
|
||||
Custom output and options:
|
||||
|
||||
```sh
|
||||
./bitwarden-print.sh vault.json -o ~/Desktop/vault.pdf \
|
||||
--paper letter --sort name --title "Vault 2026-11-13"
|
||||
```
|
||||
|
||||
Mask secrets instead of printing them:
|
||||
|
||||
```sh
|
||||
./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
|
||||
|
||||
1. **Header** — source file, item count, value count, timestamp.
|
||||
2. **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.
|
||||
3. **Index** — one line per item (type, name, folder, username) with a real
|
||||
page number, clickable in the PDF.
|
||||
4. **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:
|
||||
|
||||
```sh
|
||||
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 pod
|
||||
- `Containerfile` — `python:3.12-slim-bookworm` + WeasyPrint (Pango/Cairo) + DejaVu fonts
|
||||
- `render.py` — JSON → HTML → PDF, no data leaves the container
|
||||
- `examples/sample-export.json` — synthetic vault used for testing (all values fake)
|
||||
|
||||
Run the renderer directly on a host that has WeasyPrint installed:
|
||||
|
||||
```sh
|
||||
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-secrets` only 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 `.json` without a file password first.
|
||||
- The container is removed after each run, but the image
|
||||
(`localhost/bitwarden-print:latest`) stays cached for the next run.
|
||||
Reference in new issue
Block a user