Open source¶
The client and the verifier are open source under Apache-2.0, published at github.com/tomdyson/blindproof-client. The backend and the desktop apps are closed. This page explains the boundary, the reasoning, and the publishing mechanics.
What's open, and why¶
BlindProof's central claim is that the manuscript's content never leaves the author's machine. That claim is only worth something if it can be checked, and the two pieces of code that embody it are exactly the two that are public:
client/blindproof.py— everything that touches plaintext: key derivation (argon2id → HKDF-SHA256), encryption (AES-256-GCM), per-snapshot HMAC commitments, and the sync code that shows precisely what is and isn't uploaded.client/verify.py— the verifier that ships inside every proof bundle. A publisher can audit the copy in their bundle, or read it here; the bundle's PDF records the shipped copy's SHA-256 so the two can be compared. Publishing its canonical source backs the durability promise: verification works with no BlindProof infrastructure, indefinitely.
The licence is Apache-2.0 — permissive, with a patent grant, and no copyleft obligations imposed on the closed Mac app, which loads client/blindproof.py at runtime as its single crypto implementation. SPDX headers live in both files; the full text ships as the public repo's LICENSE (kept in the monorepo at client/public/LICENSE).
The mirror model¶
The monorepo stays the source of truth; the public repo is a read-only downstream mirror. Rationale in commercial-considerations.md §Repository topology: the apps hard-depend on the client, so extracting it as a separate upstream would add cross-repo friction for no visibility gain.
Mechanics:
scripts/publish_client.pyassembles the public tree into a staging directory:client/blindproof.py→blindproof.py,client/verify.py→verify.py,client/tests/→tests/, plus the static overlay inclient/public/(README, LICENSE, the public repo's own CI workflow). An allowlist in the script refuses to stage any unexpected file — the guard against accidentally publishing something private is code, not convention..github/workflows/publish-client.ymlruns on every push tomainthat touches the mirrored paths (plusworkflow_dispatchfor seeding and on-demand republish). It runs the client suite against a disposable copy of the staging tree (pytest writes__pycache__/.pytest_cacheinto whatever it touches — testing the tree that gets pushed is how caches leaked into the first snapshot), re-verifies the pristine tree against the allowlist (publish_client.py --verify) immediately before publishing, then clones the public repo with theCLIENT_MIRROR_TOKENfine-grained PAT, replaces the tree wholesale, and pushes one squashed commit —Mirror blindproof@<short-sha>— when anything changed.- Monorepo history is never pushed. Each publish is a fresh snapshot commit on the mirror's own linear history, so nothing from the private repo's past (paths, messages, mistakes) can leak. The short SHA in the commit message is the traceability link back.
Consequences worth knowing:
- Editing the public repo directly is pointless — the next publish overwrites it. Changes land in
client/here and flow outward. - The wire contract is pinned from both sides. Published clients can't be updated retroactively, so
backend/api/tests/test_client_contract.pyandclient/tests/test_wire_contract.py(which itself mirrors publicly) fail if a schema change would strand them. client/verify.pychanges trigger a backend redeploy (deploy.ymlpath filter): the verifier ships from the backend image into every bundle, so the mirror and production must move together.
One-time setup¶
The mirror needs two things that live outside this repo: the public repository tomdyson/blindproof-client (created empty), and a fine-grained PAT scoped to only that repository with two permissions — Contents: Read and write and Workflows: Read and write — stored as the CLIENT_MIRROR_TOKEN Actions secret in the monorepo. Workflows write is needed because the published tree includes the public repo's own CI workflow (.github/workflows/tests.yml); without it, GitHub rejects the push with "refusing to allow a Personal Access Token to create or update workflow ... without workflow scope". Seed or re-seed the mirror by dispatching publish-client from the Actions tab. Repo-level settings (issues off, description) are manual, one-off.
What stays closed¶
The Django backend, the Mac app, the Windows app when it exists, and the docs/spec/strategy material. The survival promise for authors does not depend on any of it staying available: proof bundles verify independently forever, and the shutdown plan (see Threat model) commits to a source release if the service ever winds down.