Trust & Security
Cerynix is a GRC platform, so we hold ourselves to the same standard we help you meet. This page describes how the product actually protects your data today — every statement below reflects what is implemented and documented in the product, stated plainly. Where something is planned rather than shipped, we say so.
Certifications — the honest status
Cerynix does not yet hold third-party certifications. You will not find a SOC 2 or ISO/IEC 27001 badge on this page, because we have not completed those audits — and we won't display badges we haven't earned.
What we can say truthfully today:
- The product's own control library is aligned to ISO/IEC 27001:2022 and NIS2, and we run Cerynix on Cerynix — our internal security programme is managed in the product itself.
- Formal certification is on the roadmap; as an early-stage vendor we prioritise the engineering controls described below first.
- Our infrastructure providers carry their own attestations (see sub-processors), which cover the hosting layer — not the Cerynix application.
Tenant isolation
Isolation between customers is enforced in two independent layers:
- Application layer. Every tenant-scoped row carries an organization and workspace id, and every query filters on the authenticated principal's tenant. Cross-tenant access returns
404— not403— so even the existence of another tenant's data is never leaked. This behaviour is covered by a dedicated automated test suite. - Database layer. PostgreSQL Row-Level Security is available as defense-in-depth beneath the application filter: RLS policies are enabled — and forced with
FORCE ROW LEVEL SECURITY— on every tenant table, matching a per-request transaction variable. The application connects as a non-superuser role so the policies actually bind. Enforcement is verified by an automated check (scripts/deploy/verify-rls.py) that asserts the role is neither a superuser norBYPASSRLS, that every tenant table carries an enabled, FORCEd tenant-isolation policy, and — behaviourally, on a probe table — that the policy actually hides another tenant's rows. It runs in CI on every change, and again at deploy time inside the API container — against the exact connection the application uses, not a separately-constructed one. A failure fails the deploy in our own staging pipeline and in the git-clone upgrade path; on a self-hostedinstall.shrun it is reported as a loud warning instead of aborting the install, because a box with a pre-existing database may legitimately predate the RLS role provisioning.
On top of that, self-hosted deployments give you the strongest isolation there is: your instance runs entirely on your own infrastructure, with no cloud dependency and no phone-home.
Encryption & secrets
- No hardcoded secrets. All secrets (JWT key, database, cache and object-store credentials) come from the environment; CI runs an enforced secret scan on every change.
- Passwords are hashed with bcrypt — no plaintext, no reversible storage.
- API tokens are stored hashed; only a short prefix is indexed for lookup.
- Integration credentials (e.g. an Entra ID client secret) are encrypted at rest with Fernet and are never returned by the API — endpoints expose only a "has secret" flag.
- Backups are encrypted with
agebefore they leave the box (see backups). - Object storage keys are namespaced per organization; the local backend prevents path traversal.
Authentication & access control
- TOTP multi-factor authentication (RFC 6238) with enrolment, verification and backup codes, including a replay guard. The login flow issues a dedicated MFA-challenge token that cannot be used as a session.
- Enterprise sign-on: SAML SSO, LDAP authentication and SCIM 2.0 provisioning.
- Short-lived JWT sessions (access + refresh pair) with configurable lifetimes.
- Account lockout after repeated failed logins, with uniform failure messages to prevent user enumeration.
- Rate limiting: tight per-endpoint limits on auth routes plus a coarse per-IP throttle on every mutating request.
- Role-based access control with five system roles (admin, security manager, IT manager, auditor, executive) resolved from a single in-code permission matrix — fast, testable and auditable.
Application & transport security
- Security headers on every response:
X-Content-Type-Options,X-Frame-Options: DENY,Referrer-Policy, cross-origin isolation headers, a restrictivePermissions-PolicyandStrict-Transport-Securityover TLS. - Strict Content-Security-Policy for the web app: per-request script nonces with
strict-dynamic— nounsafe-inline, no arbitrary script hosts — andconnect-src 'self'so an injected script has nowhere to exfiltrate to. The reverse proxy passes that policy through unchanged and applies an equally strict one of its own to responses the application did not police, such as static assets. - API responses are never cached (
Cache-Control: no-store) so tenant data can't linger in intermediaries; CORS is restricted to configured origins. - Minimal network surface: only the reverse proxy (80/443) and SSH face the network. The database, cache, object store and raw API bind to loopback;
/metricsis blocked from the internet, and the interactive API docs (/docs,/redoc,/api/v1/openapi.json) are off in production unless you explicitly setAPI_DOCS_ENABLED=true— the authenticated in-console API Reference is the supported way to read the spec.
app/core/session.py) with double-submit CSRF protection via a
cerynix_csrf cookie and an X-CSRF-Token header, so a
session token is not reachable from JavaScript at all. This page previously
said the migration was still "underway" — it had already shipped, which the
first pilot client noticed from the code and from hitting the CSRF check in
practice. A page that undersells what is built is the same defect as one
that oversells it, and it costs real credit for real work.
Auditability
Append-only audit logs capture logins and failed logins, role and permission changes, control status changes, evidence upload, deletion and export, risk acceptance, management approvals, member invites and more — with actor, IP, user agent, severity and correlation id.
The ledger is tamper-evident: each entry is SHA-256 hash-chained to
the previous entry per organization, carries a per-organization
sequence number, and a verification endpoint recomputes the chain on demand.
GET /api/v1/audit-logs returns each entry's
sequence_number, prev_hash and
entry_hash, so you can confirm an entry's position yourself
rather than taking this page's word for it.
- Authentication events are in the chain as of 1.0.3. In 1.0.2
and earlier they were written without an organization, which left them
outside that organization's chain and out of the org-scoped audit view —
found by the first pilot client, who queried the database directly. If you
are running 1.0.2 or earlier, your
auth.loginrows are not chained; upgrade before relying on them as evidence. - A failed login is attributed only when the address belongs to a real account. Otherwise it is recorded as a system-level event with its source address: attributing on a claimed address alone would let an outsider write rows into your ledger by guessing a domain.
- before/after state is populated for state changes, not for every event. An authentication or an export has no "before" — the field is empty there by design rather than by omission. This page previously listed it as if every entry carried one.
- Deployment-level events (for example a completed backup) belong to no organization and are therefore not part of an organization's chain. They are visible in the deployment's own log.
Supply chain & release integrity
- Signed licensing, no phone-home. License keys are Ed25519-signed offline by the vendor and verified locally by your instance — there is no license server, no telemetry and no callback from your deployment.
- Provenance chain. The codebase maintains a deterministic source manifest with SHA-256 hashes of every file, regenerated and verified in CI, alongside signed commits and signed release tags.
- Continuous scanning in CI: an enforced Trivy secret scan (no hardcoded secrets can merge), a repository vulnerability and misconfiguration scan, and image-level scans of every built container so OS-package and base-image CVEs are caught before release.
Backups & resilience
The bundled backup tooling encrypts every backup with
age public-key encryption before it is
written, so a stolen backup file is useless without the key — which the
install guide instructs you to keep off the server it protects.
The updater snapshots the database and running application before every
upgrade and attempts to restore the application snapshot if the new build
fails its self-test. Application rollback is not database rollback: a schema
migration can require recovery from the pre-upgrade backup. See the
upgrade and
disaster-recovery runbooks.
See the backups section of the install guide for the exact procedure.
Data protection & sub-processors
Self-hosted deployments involve no Cerynix sub-processors for your content — your data never leaves your infrastructure. For the managed (SaaS) model, primary storage is in the EU (Germany) and the current sub-processor list is short:
| Sub-processor | Role | Location | Transfer basis |
|---|---|---|---|
| Hetzner Online GmbH | Infrastructure hosting (compute, storage, backups) | EU — Germany (EEA data residency by default) | Intra-EEA — no third-country transfer |
| Cloudflare, Inc. | CDN / edge, WAF, TLS termination, DNS — traffic metadata only; no persistent storage of customer content by design | Global edge network (incl. non-EEA) | EU Standard Contractual Clauses / EU–US Data Privacy Framework |
| Lead-intake notification recipient operator-configured chat webhook |
Delivers demo / contact requests from this portal's contact form to the Cerynix team. Nothing to do with the platform or your tenant data. | Depends on the configured provider | Named with its transfer basis in the sub-processor list — see below |
If you run Cerynix on your own infrastructure, Cerynix engages no sub-processor for your Customer Content at all. The product does not phone home, does not validate licences over the network and sends nothing to us. The table above describes the managed (SaaS) model. The one item that can apply to a self-hosted customer is the lead-intake webhook, and only for what someone types into this portal's contact form — never platform data.
The full Annex III list for the managed model is still in legal
review and is available on request from
privacy@cerynix.com. This page used
to link to cerynix.com/subprocessors and call it authoritative;
that URL returned 404, which the first pilot client reported (their
P-01). Publishing the draft instead would have been worse — it still carries
unresolved placeholders — so this states the position plainly and names the
address that answers. Under GDPR Art. 28(2) you are entitled to advance
notice of sub-processor changes; that commitment stands and is in the DPA
regardless of where the list is hosted.
- Per-tenant configurable data retention, and a personal-data flag on evidence records.
- AI features are retrieval-only over your own records and call no external model, so no customer data reaches any third-party LLM — and the platform refuses any attempt to configure one. PII and secret masking is a prerequisite we must build before an external model provider is ever offered; it is not a control running today, because today nothing leaves the deployment for it to protect.
- Sub-processor changes are notified in advance under the DPA, with a right to object on data-protection grounds.
Responsible disclosure
If you believe you have found a security vulnerability in Cerynix, please report it privately — do not open a public issue or post details publicly before a fix is available.
- Email support@cerynix.com with the subject prefix
SECURITY— reports with this prefix are triaged with priority. - Include reproduction steps, affected component/version and impact as you understand it.
- Disclosure is coordinated: we will acknowledge your report, keep you informed while we fix, and credit you if you wish.