Security
This page is a plain statement of how Velin is engineered. Every claim below is sourced to a specific architectural decision, database migration, or repository rule; nothing here is forward-looking marketing.
This site sets no cookies and loads no third-party scripts. The product application uses only the session cookies required to sign in.
Data residency
All production data is held in Frankfurt (eu-central-1). No region of the service runs outside the EU.
- Postgres, object storage, edge functions, and email all run in EU regions.
- There is no US-region failover. If the EU region is unavailable, Velin is unavailable.
- Backups remain in the EU.
- Every sub-processor is EU-hosted or has explicit EU data-residency commitments documented in the DPA.
Tenant isolation
Every customer-data table has row-level security enabled with policies that gate access through workspace membership.
- Reads and writes are scoped to the caller's workspace memberships at the database layer, not the application layer.
- A tenant-isolation test suite runs on every pull request. It exercises cross-workspace reads, writes, RPC calls, and storage paths.
- Object storage uses path-scoped policies that key on the workspace identifier segment of every object name. A cross-workspace upload or read is rejected at the database layer before it reaches application code.
Audit log integrity
The audit log is append-only at the database role level. UPDATE and DELETE are revoked from every Supabase-exposed role.
- The only sanctioned write path is the append_audit_log SECURITY DEFINER function. Application code cannot insert into the table directly.
- Every state-changing mutation writes an audit_log entry. A mutation that does not is, by repository policy, a bug.
- Each entry carries a SHA-256 hash that links to the previous entry. The chain begins at GENESIS and extends through every state change in the workspace.
- The producer emits the exact byte string fed into SHA-256 for each entry. An external verifier reproduces the chain by hashing those strings — no database access required.
No destructive access to customer systems
Velin does not delete data from customer systems. It records and signs evidence about deletions the customer has performed.
- We do not store, hold, or transit customer credentials for external systems. Source systems are catalogued as metadata only.
- The product never issues a destructive call to a customer system. It does not call customer APIs at all in v1.
- Read-only verification is scoped to v2 and gated behind explicit customer onboarding. Destructive integrations, if ever introduced, are v3 and require dedicated workspaces, customer-managed keys, mandatory dual-control, and mandatory dry-run.
Cryptography
Velin uses standard, named primitives: SHA-256 for hashing, Ed25519 for signatures, and RFC 8785 (JCS) for canonicalisation. We do not write our own cryptographic primitives.
- Signing and verification use the @noble/ed25519 library; hashing uses Node's built-in crypto module and @noble/hashes in the open-source verifier.
- The asserted state hash binds an approval to a content digest of the case state. Canonicalisation follows RFC 8785, so any external verifier can recompute the hash with stock tools.
- The pack content hash is SHA-256 over a deterministic concatenation of the four sourced attachments. A verifier reads the same four attachments and reproduces the same hash.
- Cryptographic library versions are pinned in the lockfile, not floated. Upgrades pass through the same review as any other change.
Key management
The Ed25519 private key that signs evidence packs lives in Supabase Vault. It is read by exactly one piece of code — the sign_pack Edge Function.
- The private key is never written to a developer workstation, a CI system, a log, an error report, or a backup that we control. It exists in Supabase Vault and in the founder's password manager only.
- Keys are rotated annually. Old public keys are published indefinitely so packs signed under previous keys continue to verify forever.
- Each pack records the fingerprint of the key that signed it. Rotation does not invalidate historical packs.
- The current platform public key and every key ever used to sign a Velin pack are published as JSON at /.well-known/keys.json on this domain.
- First-key provisioning is manual and human-witnessed. A cryptographic gate and a deployment gate must both pass before the key is treated as live; both gates are documented in the per-key provisioning record under decisions/keys/.
Independent verification
Evidence packs can be verified end-to-end without any Velin infrastructure. The verifier is open source and small enough to read in one sitting.
- The verifier ships as the @usevelin/verify npm package and as the /verify page on this site. The browser version processes the pack entirely on the auditor's machine; no upload is made.
- A passing verification confirms five properties: each attachment's hash matches the producer's claim; the pack content hash recomputes to the value the producer signed; the Ed25519 signature verifies against the published platform public key; the named fingerprint genuinely corresponds to its public-key bytes; and the embedded audit chain rehashes from GENESIS through the export sequence.
- The verifier recomputes chain hashes by SHA-256 of the exact byte string the producer fed into SHA-256, eliminating any reliance on Postgres-specific timestamp formatting.
- The verifier is MIT-licensed. An auditor or buyer is free to read, fork, run, and embed it.
Authentication
Authentication is handled by Supabase Auth. We do not write our own auth, password hashing, or session management.
- TOTP-based multi-factor authentication is mandatory on every account. There is no account state in which a user can hold workspace access without an enrolled factor.
- Recovery codes are issued only after an AAL2 challenge. Each code is stored as a SHA-256 hash, is single-use, and is invalidated as soon as it is redeemed.
- Redeeming a recovery code forces re-enrolment of a fresh TOTP factor before any sensitive operation succeeds.
- The recovery-code hashing approach uses SHA-256 today. We have documented the trade-off and the v2 hardening path (Argon2id, per-user pepper from Vault) publicly in the architectural record.
Evidence handling
Evidence files live in private object storage. Every read is workspace-scoped at the database layer; nothing is publicly listable or publicly downloadable.
- The storage bucket enforces a 50 MB cap per file and a MIME allowlist covering PDF, PNG, JPEG, CSV, plain text, JSON, and XLSX.
- Once uploaded, evidence and pack bytes are immutable. There is no UPDATE or DELETE policy on storage objects. Redaction is a non-destructive lifecycle transition on the row, not a file overwrite.
- Each evidence item is referenced inside the asserted state by SHA-256 content hash. A signed approval binds to the digest of the evidence at signing time; later mutations are detectable by recomputing.
- Velin does not virus-scan uploaded files in v1. The threat model is documented in the architectural record (ADR 0016): the actor is an authenticated member of the same workspace, evidence is never public, and there is no inline rendering of file contents. The compensating controls — private bucket, signed-URL-only access, MIME allowlist, 50 MB cap, content hashing, download-only — remove the public and stored-XSS vectors. Self-hosted ClamAV scanning is queued for v2 and will land before the first external penetration test.
No PII in logs
Velin's logs do not carry personally identifiable information. Audit-log payloads are stored as SHA-256 hashes, never as plaintext.
- The audit log stores payload_hash as a SHA-256 digest. Plaintext payload bodies are not persisted.
- Sentry scrubbing is configured to strip PII fields. Adding a new field to any logged object requires updating the scrub list in the same change.
- Where personal data appears inside the canonical case state, it is row-level-security gated to workspace members, is not rendered in the human-readable PDF body, is not exported in the pack, and is on the required Sentry scrub list.
Vulnerability disclosure
Report security issues to security@usevelin.com.
If you believe you have found a security vulnerability in Velin, we want to hear about it. Include enough detail to reproduce the issue. We acknowledge reports within 3 business days and complete an initial assessment within 10.
In scope: usevelin.com, app.usevelin.com, the @usevelin/verify package, and the evidence pack format itself. If you can forge a pack that verifies, or read another workspace's data, that is precisely what we want to know.
Out of scope: vulnerabilities in our sub-processors' platforms (please report those to them directly), denial-of-service and rate-limiting findings, and social engineering of any kind.
Ground rules: test only against a workspace you created yourself — until self-serve signup opens, email us for a test workspace. Never access, modify, or retain data belonging to anyone else; if you encounter personal data, stop and report. Do not degrade the service for others.
Safe harbor: we will not pursue legal action against research conducted in good faith within these rules.
Disclosure: we ask for 90 days from your report before public disclosure, less by mutual agreement. We do not operate a bug bounty at this time; with your permission, we credit verified reports on this page.
Penetration testing and certifications
Velin has not yet completed an external penetration test or a SOC 2 audit. Both are scheduled work, and neither is claimed today.
- The first external penetration test is scheduled for Phase 6. The executive summary will be published on this page once the engagement completes.
- SOC 2 Type 1 is planned, not held. Velin does not display a SOC 2 badge today and will not display one until the report is issued.
- Self-hosted virus scanning is queued to land before the first penetration test, so the test runs against the v2 evidence-handling posture.