v0.538.5 (Private Alpha)
v1 — 2026-06-10 · SOC 2: audit deliberately deferred until commercially gated; this page is the structural answer.
Audience: InfoSec reviewers, procurement teams, and vendors completing a security questionnaire. This page is honest about what is shipped, what is in progress, and what is explicitly deferred. Every claim cites the artifact or code path that backs it.
A fully-compromised coordinator — stolen disk, compromised process, subpoenaed operator — yields:
It does not yield: original documents (since v0.117 the platform has no document upload and stores no document bytes — only platform-authored sample PDFs exist server-side), decryption keys (the ceremony Content Key lives only in the URL fragment and in exported artifacts — it is never sent to the server), plaintext face images (E2E design; see §2), or the ability to forge a signer's consent (SHA-256 chain integrity; see INV-3 below).
Source: docs/ZERO_TRUST_INVARIANTS.md
| Invariant | Statement | Enforcement evidence |
|---|---|---|
| INV-1 Content integrity | Every document, face, and chain block returned by the server is addressed by its SHA-256; any mismatch is rejected client-side. | blobstore keys are content hashes; verify.verify_chain re-derives every block hash; signer re-hashes the PDF vs the QR code. Tests: test_invariants.test_inv1_*, test_seal_and_qr.test_blobstore_self_verifies, test_verify.test_pdf_hash_mismatch. |
| INV-2 Chain independence | Ceremony validity is decidable from the chain document alone — no server contact required. | verify.verify_chain is a pure function; the browser port chain-verify.js mirrors it. Tests: test_invariants.test_inv2_*, full test_verify.py suite. |
| INV-3 No consent-forging key on the server | The server holds no key whose compromise lets it forge a signer's consent. v1 has no per-signer signing keys; a link is fabricatable only by producing a block whose hash the next signer already committed (SHA-256; computationally infeasible). | Tests: test_invariants.test_inv3_*; code-grep confirms no signer private keys in src/. |
| INV-4 Offline verifiability | A holder can verify a closed artifact with no coordinator. | Verifier SPA + chain-verify.js run fully client-side; OTS proof is checkable against Bitcoin independently. Test: test_invariants.test_inv4_offline_verify_closed_artifact. |
| INV-5 No lock-in | A participant can export and verify independently of the coordinator, forever — even if COSigna disappears. | Chain document is self-contained JSON; verify_chain accepts it directly; round-trips without loss. Test: test_invariants.test_inv5_export_roundtrip_verifies. |
| The server CAN | The server CANNOT (clients detect it) |
|---|---|
| Refuse service / degrade availability | Forge or alter a signer's consent |
| See ceremony metadata (who, when) | Substitute the document or a face undetectably |
| Hold the additive witness key | Fabricate a chain link or a closure |
| Order, relay, notify | Make a tampered chain verify |
| Count credits (billing) | Backdate closure past what OTS attests |
Cookie-free sessions. COSigna sets no cookies. Session state is held in localStorage as a bearer token; Set-Cookie and document.cookie are never used. This is an intentional product differentiator. Source: docs/VERSIONS.md (v0.105.0); enforced by STATUS.md invariant 12.
No third-party assets or analytics. Every asset (fonts, JS, CSS) is served from the same origin. No third-party analytics scripts, no CDN includes. Source: UX/DESIGN_UI_SPEC.md; UX/CLAUDE_DESIGN_BRIEF.md.
First-party telemetry only, DNT/GPC-respecting. All telemetry is first-party and identifier-free: the client-error beacon (/telemetry/client-error), the anonymous option-flip beacon (/telemetry/option), the k-anonymous performance RUM beacon (/telemetry/perf, k=20 display floor), the ephemeral-visit A/B beacon (/telemetry/ab), and — since v0.523.0 — server-side aggregate request accounting (counts by cyclic hour-of-day / coarse path family / status class, plus edge-derived country codes; the schema is structurally incapable of holding an IP, raw path, UA, or session id, and a test asserts it). The client-side beacons honour the browser Do-Not-Track signal and fire nothing when DNT=1; the server-side accounting honours both DNT: 1 and Sec-GPC: 1 (Global Privacy Control) — an opted-out request is not counted at all, even in aggregate (v0.523.1; the stricter reading, chosen deliberately). None of it captures PII, capability tokens, session IDs, or URL fragments. Source: UX/libs/telemetry.js (inline DNT check, capability-token redaction); UX/libs/perf-beacon.js; src/cosigna/server.py (_privacy_opted_out, _record_traffic, _classify_path); tests/test_traffic_counters.py (DNT/GPC suppression + schema-has-no-identifier tests).
End-to-end face encryption. The cryptographic core is implemented and independently reviewed. Per ceremony, a 32-byte Content Key (CK) is generated in the initiator's browser. Each consent selfie is encrypted with AES-256-GCM (nonce || ciphertext, AAD = SHA-256 of the plaintext) before upload. The server stores ciphertext only; it never receives CK. CK travels in the URL fragment (#k=...) which browsers strip before the request is sent. An independent Opus review confirmed: AES-256-GCM correct, AAD binding correct, face_hash integrity preserved, Python/JS wire-compatible, no key/plaintext/timing leak. Status: crypto core AND browser wiring shipped (src/cosigna/facecrypto.py, UX/mobile-signer/facecrypto.js, 31 parity tests; all 3 SPAs encrypt/decrypt since v0.1.1). Source: docs/E2E_FACE_ENCRYPTION.md.
No biometric templates. COSigna stores raw consent selfies (or their E2E-encrypted ciphertext), never a biometric embedding or template. This is a locked architectural decision (Q4 Option A, 2026-05-28). Source: NEOCORTEX/_archive/Q4_biometric_template.md; STATUS.md invariant 5.
Hash is the contract. Every PDF reference travels with its SHA-256. No code path trusts a PDF without re-verifying. Source: CLAUDE.md § Working rules; STATUS.md invariant 3.
GDPR erasure = crypto-erasure. Deleting a ceremony's CK renders every consent selfie of that ceremony unrecoverable noise while the hash commitments (legal weight of the chain) remain untouched. Single key destruction erases all faces of a ceremony at once, provably. Source: docs/E2E_FACE_ENCRYPTION.md §6; src/cosigna/lifecycle.py.
Secrets never in git. The repository mirrors to a third-party GitLab instance (gitlab.setip.io). This is precisely why no secrets may appear in any tracked file. All production secrets (HMAC key, witness Ed25519 key, admin token) are injected at docker run via --env-file (never per individual -e flags, which expose values in process listings). Source: CLAUDE.md § Notification/secrets rules; STATUS.md invariant 10; src/cosigna/server.py line 1: "No secrets in code (§16)".
Secrets rotation runbook. A documented rotation procedure covers four secret surfaces in priority order by blast radius: tunnel master token, GitLab PAT (scoped to write_repository), app secrets (COSIGNA_SECRET / COSIGNA_ADMIN_TOKEN via scripts/rotate-secrets.sh), and the Ed25519 witness key (rotate only on suspected compromise; old proofs keep verifying against the old public key). Source: docs/SECRETS_ROTATION.md.
Admin surface gating. /admin/* endpoints require the X-COSIGNA-Admin header equal to COSIGNA_ADMIN_TOKEN (runtime env var). Fails closed: an unconfigured token disables the surface. Source: src/cosigna/server.py lines 110, 3250.
Capability token scoping. Every signer/initiator action requires an HMAC capability token scoped to (cid, kind, subject). Cross-actor escalation (a signer performing initiator actions) is blocked at the token level. Source: src/cosigna/auth.py; tests test_auth.test_signer_and_initiator_tokens_differ, test_server.test_signer_cannot_use_initiator_endpoint.
Input validation. All hash parameters are validated against ^[0-9a-f]{64}$ (path traversal impossible). PDFs are structurally parsed via pypdf; face uploads are magic-byte checked; uploads are chunked with a size cap. Source: src/cosigna/server.py _validate_hash; tests test_server_s4.test_hash_path_traversal_rejected, test_server_s5.test_upload_polyglot_pdf_rejected.
Transport encryption. TLS is assumed at the deployment layer (tunnel termination). Not enforced in application code (documented residual R3 in docs/THREAT_MODEL.md).
Deployment. Currently a single-host alpha on a tunneled VPS (Scaleway EU migration planned). Both server and worker run as Docker containers from a pinned image tag with a documented rollback path.
| Control | Status | Notes |
|---|---|---|
| ✅ Hash-chain integrity (INV-1..5) | Shipped | 901 backend tests green; adversarial suite in tests/test_invariants.py |
| ✅ No consent-forging key on server (INV-3) | Shipped | No signer private keys in src/; code-grep + test coverage |
| ✅ E2E face encryption — crypto core | Shipped | facecrypto.py + facecrypto.js, 31 parity tests, Opus-reviewed |
| ✅ No biometric templates | Shipped | Architectural lock; no template code path exists |
| ✅ Cookie-free sessions | Shipped | localStorage bearer; no Set-Cookie ever issued |
| ✅ No third-party assets / analytics | Shipped | Own-origin only; enforced by design spec + code review |
| ✅ DNT-respecting first-party telemetry | Shipped | UX/libs/telemetry.js; DNT check at load, token redaction |
| ✅ Secrets never in git | Shipped | --env-file injection; repo mirrors to third party (gitlab.setip.io) |
| ✅ Secrets rotation runbook | Shipped | docs/SECRETS_ROTATION.md; scripts/rotate-secrets.sh |
| ✅ Admin surface token-gated | Shipped | X-COSIGNA-Admin header; fails closed |
| ✅ Capability token scoping + cross-actor enforcement | Shipped | src/cosigna/auth.py; test suite |
| ✅ Input validation (path traversal, polyglot, size) | Shipped | _validate_hash; pypdf structural parse; magic-byte check |
| ✅ Face-access token gating | Shipped | GET /faces/{hash} requires HMAC face token; test_face_retrieve_requires_token |
| ✅ Crypto-erasure (Art. 17) | Shipped | src/cosigna/lifecycle.py; CK-destruction design in docs/E2E_FACE_ENCRYPTION.md §6 |
| ✅ Offline verifiability (INV-4/5) | Shipped | Verifier SPA + chain-verify.js fully client-side; OTS / Bitcoin anchor |
| ✅ OpenTimestamps / Bitcoin anchor | Shipped | Background task; never blocks close; stub vs real distinguished by anchor_verified field |
| ☐ COSIGNA_ADMIN_TOKEN set on live deployment | In progress | Env var exists; not yet confirmed set on the production container; pending go-live hardening (docs/PRODUCTION_GOLIVE_RUNBOOK.md) |
| ☐ Offsite backups | Not yet started | SQLite snapshot to a separate host; pending Scaleway/Postgres decision |
| ☐ PostgreSQL (production DB) | In progress | SQLite today (alpha); Postgres migration path validated (test_store_portability.py); Scaleway cutover gated on founder decisions |
| ☐ Single-host deployment | Alpha state | One VPS today; Scaleway EU production environment planned (docs/DEPLOY_FOUNDER_CHECKLIST_ovh_scaleway.md) |
| ☐ Token revocation / short-lived query tokens | Residual R1/R1b | Stateless HMAC tokens valid until secret rotates; query-param token in server logs (documented in docs/THREAT_MODEL.md); mitigation path: per-ceremony versioning or short-lived JWTs |
| ☐ SOC 2 Type II | Deferred | See §6 |
To report a security issue: p@phileos.eu (PGP on request) or use the in-product /bug page (available at the live service URL), which routes directly to the operator.
We aim to acknowledge within 48 hours and to provide a status update within 7 days. There is no formal bug-bounty programme at this stage.
SOC 2 Type II attests that a service operator's controls around security, availability, processing integrity, confidentiality, and privacy operated effectively over an observation period — it is an audit of organisational and operational controls, not a certification of cryptographic architecture.
COSigna's blind-server architecture materially changes the risk calculus for several Trust Services Criteria (the server cannot produce plaintext documents or faces even under compulsion; consent integrity is mathematically enforced rather than procedurally attested), but it does not replace the audit: organisational controls around physical access, personnel, incident response, and vendor management are not covered by the zero-trust protocol and require a separate assessment.
The SOC 2 audit is deliberately deferred until the service is commercially gated — specifically, until the Scaleway production environment is live, the Stripe live mode is active, and the first paying cohort is onboarded. Pursuing the audit before those gates would produce a report over an alpha-phase control environment that does not represent the production posture. The structural answer to the gap, in the interim, is this document.
Last updated: 2026-06-10. Version: v1.