Security
How we protect your studio's data. Everything described here is implemented in the product today, and the first section says plainly what we do not have.
Certifications: where we actually stand
Kavvim is pre-certification. We do not hold a SOC 2 report. We are not certified to ISO/IEC 27001. We have not commissioned an independent penetration test, and we do not publish a status page. We would rather say so than let a badge imply otherwise.
When we obtain an attestation we will publish it here with its scope and date, and we will tell customers. Until then, judge us on the controls below, each of which is in the running product.
Encryption in transit and at rest
Everything between your browser and Kavvim travels over TLS 1.2 or better. Plain HTTP is redirected to HTTPS, and every response carries a strict-transport-security header instructing your browser not to try HTTP again for a year.
Inside our infrastructure the same rule holds: services reach the database over an encrypted connection, and files are stored with server-side encryption applied by the object store, which is stated as a rule on the storage bucket rather than left to each upload to remember.
Certificates are issued and renewed automatically by Let’s Encrypt. We terminate TLS ourselves; no content-delivery network sits in front of the Service and decrypts your traffic.
Files, photographs and the links to them
No file you upload is publicly readable. Project files, message attachments, presentation images, profile photographs, studio logos and branding all live in storage that refuses anonymous access, and every one of them is served through a link our servers sign.
A signed link is checked before anything is returned, is good for about a week, and covers exactly one file. It carries no session, which is what lets an image render in an email client or a shared client portal page without signing in.
The honest trade-off: for as long as a signed link is valid, anyone holding it can open that one file. If you forward one outside your studio, treat it as you would the file. Links stop working about a week after they were issued, and the app issues a fresh one every time the page loads, so nothing you see in the product ever goes stale.
What your browser contacts
Fonts, styles, scripts and images are served from our own domains on both kavvim.com and the app, so no third party learns your address from the bare fact that a page loaded. There is no advertising tag and no analytics tag on either site.
Inside the app there are two exceptions, both named in our Subprocessors list. Our error monitor receives a report when something breaks, and a count of sessions started so we can tell whether a release is failing; it records no session replay and no page contents. Stripe’s payment form loads from Stripe on the screens where you enter card details, which is exactly what keeps those details from passing through us.
The marketing site has neither, and contacts nobody but us.
Encryption of sensitive fields
Payment-card metadata, multi-factor authentication secrets, and the access tokens for third-party integrations you connect are encrypted with AES-256-GCM before they reach the database. They are unreadable in a database dump or a backup file.
In production the encryption key is not kept in configuration or in environment variables. It is stored as a blob wrapped by AWS Key Management Service and unwrapped in memory at service start, so plaintext key material never sits at rest.
Full card numbers and security codes never touch our servers. Card entry happens in an element served directly by Stripe. We store only brand, last four digits, expiry, and cardholder name, all encrypted.
The credential vault is zero-knowledge
Vendor logins stored in Vendors → Logins are encrypted and decrypted entirely in your browser. The server only ever holds ciphertext. We cannot read them, and we could not hand them over if we were asked to. Not for support, not under subpoena.
- Each member holds an RSA-OAEP 2048 keypair. The public key is shared with teammates so access can be granted to them; the private key never leaves the browser unencrypted.
- Vault contents are encrypted with AES-GCM under a 256-bit key.
- Your private key is wrapped by a key derived from your vault passphrase using PBKDF2-HMAC-SHA256 at 600,000 iterations.
The trade-off is real. Because we cannot read the vault, we cannot recover it for you. If every member of your studio forgets the vault passphrase, the contents are unrecoverable, including by us. Keep the recovery key issued at setup somewhere safe.
Passwords and sessions
Passwords are hashed with BCrypt at work factor 10 and are never stored, logged, or emailed in any recoverable form. A complexity policy is enforced on the server, not only in the browser.
A signed-in session uses a short-lived access token: an RSA-signed JWT that expires after 15 minutes and is verified by every service against a published JWKS endpoint. Renewal uses an opaque random refresh token, of which the server keeps only a SHA-256 hash and which expires after 30 days. Every renewal rotates that token and invalidates the one it replaced, so a stolen refresh token stops working the moment the real session refreshes.
Suspending a studio or a member revokes all of their refresh tokens at once, and any access token already in flight expires within its 15-minute lifetime.
Multi-factor authentication
Members can enable MFA by authenticator app (TOTP) or by emailed one-time code. Enrollment issues single-use backup codes, which are stored only as hashes. Verification attempts are rate limited, and a device you choose to trust skips the challenge for 30 days using a token stored as a hash with an expiry rather than as a readable value.
Tenant isolation and permissions
Every read and every write of studio data is scoped to the caller’s studio in the query itself, at the repository layer. Asking for a record that belongs to another studio returns the same 404 as asking for a record that does not exist, so the API cannot be used to work out whether another studio’s data exists.
Access within a studio is governed by role-based permissions across a fixed privilege catalog. Client-portal users hold no studio permissions at all and can reach only their own projects, presentations, messages, bookings, and sent invoices.
Rate limiting
Login, registration, password reset, and MFA verification are rate limited through a shared Redis counter, so the limit holds across every running instance rather than per process. Five failed sign-in attempts within five minutes lock further attempts on that account until the window expires. The gateway applies its own limits at ingress.
Inbound webhooks and internal calls
Stripe webhooks are verified against Stripe’s HMAC signature before the payload is parsed; an unsigned or wrongly signed delivery is rejected outright. Parcel-tracking webhooks authenticate with a per-endpoint shared secret compared in constant time.
Calls between our own services carry a short-lived internal service token. That check fails closed: a service refuses to start at all if the shared secret is missing, too short, or still set to the value committed for local development.
Fetching URLs you supply
When you import an item from a vendor URL, the fetch runs through a guard that resolves the host and rejects private, loopback, and link-local address ranges. Redirects are followed by hand so every hop is re-validated, responses are size-capped, and content types are checked before a body is read.
Audit logging
Every mutation is written to an append-only audit log, partitioned by month, recording who acted, on what, and when. Partitions are created ahead of time by a scheduled job so a write never fails for want of one. Logs are retained 18 months live and 7 years archived.
Studios with the audit permission can review their own log at any time. Where a Kavvim operator accesses an account for support, the operator is identified in the audit record, and support access is limited to named administrators.
Backups
The database is backed up automatically on a schedule, and again immediately before every deploy. Write-ahead logs are archived continuously, which is what makes point-in-time recovery possible rather than only a restore to the last snapshot. Backups are stored encrypted in the United States.
A restore has been tested end to end, not merely configured. A backup nobody has restored is a hypothesis.
How the software is built
Every build runs code formatting (Spotless), style and complexity limits (Checkstyle), static analysis and copy-paste detection (PMD and CPD), and a test-coverage floor (JaCoCo). A build that fails any of them does not merge.
Reporting a vulnerability
Email security@kavvim.com. We aim to acknowledge within two business days, keep you posted while we investigate, and credit you if you would like to be credited.
We ask that you do not access other studios’ data, degrade the Service, or disclose publicly before we have had a fair chance to fix the issue. We will not pursue action over research conducted in good faith on those terms.
Incident response
If we confirm a security incident affecting your data, we will notify you without undue delay and in any event within 72 hours, tell you what we know and what we do not yet know, and follow up as the picture becomes clearer.