Authentication fails closed
A passkey that looks cloned is disabled and the sign-in refused, never dropped to password-only. Backup codes belong to the account, so recovery still works.
Security & trust
You are trusting it with your policies, your access reviews and your auditor's evidence. So the source is public, every review is on record, and the residual risks are named.
Posture at a glance
| Licence | AGPL-3.0. Every line readable. |
|---|---|
| Telemetry | None. The only call you did not configure is the interface's two Google webfonts, which you can self-host. |
| Second factor | TOTP and passkeys, with backup codes |
| Single sign-on | OIDC and SAML 2.0, from the environment only |
| Transport | HttpOnly cookies, __Host- prefixes over https |
| Evidence integrity | SHA-256 per file, Ed25519-signed manifest |
| Audit trail | Server-side only. The API cannot edit it. |
| Uploads | Capped at 32 MB, active content refused, served sandboxed, optional ClamAV |
How it is built
A passkey that looks cloned is disabled and the sign-in refused, never dropped to password-only. Backup codes belong to the account, so recovery still works.
Refresh rotates and blacklists; sign-out revokes. Logins are throttled per client and shared across workers in Docker. It is rate limiting, not account lockout.
Environment only. External identities never link to an administrator, and SSO_STEP_UP asks for a local factor when the provider asserted none.
Folder grants gate folders and documents. They do not narrow the registers: any signed-in account reads the controls, risks and vendors, so the Auditor role reaches further than its name suggests. Narrowing it is on the list.
A view that forgets to filter still reads one workspace. Not absolute: sliced or combined querysets skip the filter, and a request with no workspace gets a 403.
Byte-checked before display. PDFs drawn by pdf.js, Office files rendered as structure, never as the file's own markup.
Field names only, with password, token and code keys dropped outright.
Jira is https-only, public hosts only, no redirects. Webhooks are https and operator-set. The TOTP seed and Jira token are encrypted per field.
The Django admin at /admin/ sits outside all of these rules: no second factor, no throttle. Restrict who can reach it.
Evidence integrity
The manifest is signed with Ed25519 by a key kept outside the database. The fingerprint is published at /api/signing-keys/.
The bundled verifier is standard library only, tested against the RFC 8032 vectors, so it runs on an air-gapped laptop. It checks the signature and every listed digest. It does not prove the bundle is complete.
manifest.json: the snapshot and the evidence digests. The auditor's conclusions are recorded after sealing and are not signed yet. A signature also cannot prove the key was never stolen, or when it signed. A digest you send the assessor out of band closes that gap.
conformiti-package-fy26-soc2/ ├── manifest.json the signed bytes: snapshots + digests ├── MANIFEST.sha256 the manifest's digest, on its own ├── manifest.sig detached Ed25519 signature ├── signing-key.pub the public key, for comparison ├── SHA256SUMS every file, hashed ├── verify.py stdlib only, no network ├── controls.csv scope, status, owner, conclusions ├── evidence.csv name, version, size, digest ├── samples.csv population, selections, verdicts ├── trail.csv what happened, and when ├── INTEGRITY.txt the re-hash result at export time ├── README.txt how to verify it, and what that proves └── evidence/ the files themselves
Check it with sha256sum -c SHA256SUMS and python3 verify.py.
Quality gates
A pipeline that fails the build. ./install.sh --test runs the local half on your own machine.
| Gate | What it proves |
|---|---|
tools/validate.py20 checks, no dependencies | Routes, the SPA-to-API contract, and a migration for every model change. |
manage.py test1,251 tests | Auth, MFA, RBAC, evidence access, the auditor's reach, import safety, the audit trail, WebAuthn, SAML and Ed25519 against RFC 8032. |
| Backend matrix | Python 3.11 to 3.14 on SQLite, plus PostgreSQL 16. |
| Frontend | A production build and npm audit --audit-level=high. |
| Docker | Both images build, and the API boots healthy. |
| End-to-end 217 Playwright tests | A real browser drives the built app under both auth transports, failing on any console error. |
SECURITY.md lists thirteen reviews, eight of them independent. REVIEWS.md records each finding and the release that fixed it.
Django 5.2 LTS, React 19, Vite 8, pdf.js 6. Dependabot bumps go through review, never merged blind.
Three outside findings are deferred, each with its reasoning beside it. The rest of the list is deployment risk, named in SECURITY.md and yours to close.
Coordinated disclosure
Acknowledged within two business days, then triaged with a severity and a target date.
/api/health/No bug bounty. We would rather promise a fast, honest answer than a payout we cannot fund.
In scope
Out of scope
/admin/)Managed hosting
We recommend self-hosting. If you ask us to run it, this is what you rely on.
| Area | Commitment |
|---|---|
| Region | Canada or the United States, never moved without written instruction. |
| Encryption | TLS 1.2+ in transit, encrypted disks, field-level encryption for secrets. |
| Isolation | A single-tenant instance and database, never a shared schema. |
| Backups | Nightly, encrypted, kept 90 days, with tested restores. |
| Signing keys | Your instance's own key, or one you supply from your own secret store. |
| Access by us | Named staff, second factor, every action in your audit log under our name. |
| Sub-processors | Listed in the DPA, with notice and a right to object. |
| Exit | A full export on demand. Data destroyed 60 days after termination. |
| Incidents | Notified within 72 hours of confirming a personal-data breach. |
A DPA on every managed engagement, and a BAA where you need one. Ask on the contact form.
Read the source, run the gates, seal a package and verify it yourself.