-
Notifications
You must be signed in to change notification settings - Fork 2
Technical Security
Cairn holds an organisation's risk register, its audit findings and its incident evidence. The threat model is therefore not only "an outsider gets in" but also "an insider sees a scope they were not assigned" and "a record changed and nobody can prove who changed it".
This page describes what the platform enforces and, at the end, what it leaves to the operator. The two lists are separate on purpose.
Four ways in, all landing on the same user record.
| Method | Used by | Notes |
|---|---|---|
| Email + password | The web interface | There is no username; the email is the identifier |
| Passkey (WebAuthn) | The web interface |
fido2, phishing-resistant, usable as the sole factor |
| JWT | Scripts and API clients | 30-minute access token, 7-day refresh, rotation on, blacklist after rotation |
| OAuth 2.0 bearer | MCP clients | Authorisation-code flow; see mcp-server.md |
Passwords go through five validators : Django's similarity, minimum length 12, common-password and numeric-only checks, plus a project complexity validator. After 5 failed attempts an account is locked for 15 minutes.
An invited user is created with an unusable password and receives a single-use activation link. No endpoint accepts a password on someone else's behalf.
Permissions are flat codenames shaped module.feature.action, declared once in
accounts/constants.py and created by a data migration. Django's per-model
add/change/delete permissions are not used, so there is exactly one
place a permission can come from.
The same codename gates all three surfaces : the web view, the REST endpoint and the MCP tool. This matters more than it sounds. A permission model that is enforced in the interface and re-derived in the API is a permission model with two answers, and an auditor will find the disagreement before you do.
The full list is in reference/generated/permissions.md. Six system groups ship with the platform and are kept in sync by the same migration, so a newly declared permission lands in the right groups without anyone editing a group by hand.
Scopes are the tenancy axis. A user is assigned scopes; ScopeFilterMixin
filters querysets to them, and the same filtering applies on the REST API and
the MCP layer.
Child entities do not carry their own scopes : they inherit the parent's perimeter through a declared scope path. An incident's timeline entry is unreachable when its incident is out of perimeter, and it is unreachable on every surface, including the generic history and workflow endpoints. Shared catalogues (reporting authorities, obligation templates) are deliberately not scope-filtered, because they are reference data rather than records.
A superuser bypasses scope filtering. That is the intended escape hatch, and it is why superuser is not a role you hand out.
Governance is not advisory here. A record's step decides whether it counts in
reports (counts_in_reports), whether other objects may link to it (linkable)
and whether it may be deleted (deletable). Deletion is only offered from a
deletable step, which is how a validated risk cannot quietly disappear.
Transitions are the only way a state changes. workflow_state is read-only on
the API : there is no PATCH that moves a record, because the transition
endpoint is where the permission gate, the mandatory comment and the recorded
lifecycle event live. Three incident registers go further and are append-only at
the router level : PUT, PATCH and DELETE generate no route at all and
answer 405. Correcting one means appending a correction, not rewriting
history.
Every domain model carries django-simple-history, so every write is a row with
an author and a timestamp, and every object carries a version counter.
Authentication and account events are recorded separately in the access log : successful and failed logins, logouts, token refreshes, password changes, lockouts, passkey registration and use, invitations, activations, and impersonation start and stop. Impersonation is recorded at both ends, so "the administrator was acting as this user" is a fact in the log rather than an inference.
GET /api/v1/incidents/<ledger>/<uuid>/history/ is the tamper-detection surface
on the append-only registers : a row whose trail shows more writes than the
design allows was altered outside the supported paths.
Evidence artefacts, filing proofs and Trust Center documents never appear in a
payload and are never served from a guessable media URL. They stream through
dedicated download endpoints resolved via the scoped queryset and
permission-checked, and gated Trust Center links are signed with a lifetime
(TRUST_CENTER_DOWNLOAD_TTL, seven days by default).
Evidence artefacts are hashed on acquisition and can be re-measured on demand,
returning match, mismatch or not_verifiable.
No page loads a script, a stylesheet or a font from a third party. Every
front-end library is declared in core/dependencies.py with its exact files and
their Subresource-Integrity digests, mirrored into static/vendor/ and served
from this instance. A download whose content does not match its digest is
refused rather than written, so a compromised mirror cannot reach a browser, and
a test fails the build if any template reintroduces a CDN reference.
For an audit this matters twice over : the code a user's browser executes is fixed at build time and verifiable, and no third party receives a request that would tell them who consults a compliance platform and when.
The Trust Center is the only surface meant to be public, and it is a curation
layer : nothing appears there unless someone explicitly published it. When
TRUST_CENTER_HOST is set, that hostname serves the Trust Center and nothing
else, with the application, the admin and the internal API returning 404 rather
than a redirect that would confirm they exist.
Public read endpoints are throttled to 120 requests per hour per anonymous client. The authenticated application is not throttled.
The assistant is off by default because enabling it sends data to a third party.
When on, it does not widen access : the routing model chooses among read-only
tools, and each of those tools runs the caller's permission and scope checks.
An answer cites real records the caller was already allowed to read. Choosing
the ollama provider keeps everything on your own infrastructure.
The platform cannot do these for you, and none of them are on by default because turning them on blindly breaks an HTTP-only deployment.
-
Serve over HTTPS and turn on the hardening flags :
SESSION_COOKIE_SECURE,CSRF_COOKIE_SECURE,SECURE_SSL_REDIRECT, then HSTS. The order and the HSTS caveat are in configuration.md. -
Set a real
SECRET_KEYand keep it out of the image. -
Set
DEBUG=Falseand a specificALLOWED_HOSTS. -
Run
python manage.py check --deployand read what it says. - Back up PostgreSQL and the media directory together. The history tables are in the database, the evidence artefacts are on disk, and a restore that has one without the other is not a restore. See operations.md.
- Review the group assignments. The default groups are a starting point; who is in them is your decision and your audit finding.
Open a private security advisory on the GitHub repository rather than a public issue.
Built from docs/ at v0.36.0. Edits made here are overwritten by the next release : open a pull request against the source instead.
- Administration
- Ask Cairn
- Assets and suppliers
- Compliance
- The dashboard
- Finding your way
- Getting started
- Incidents
- How records move
- Organisational context
- Reports and management review
- Risks
- Trust Center
- Architecture
- Configuration
- Contributing
- The documentation system
- Installation
- Internationalisation
- Operations
- Release process
- Security
- Testing
- Adding an assistant provider
- Adding a dashboard widget
- Adding a domain entity
- Declaring a lifecycle
- Adding an MCP tool
- Adding a REST endpoint
- Adding a report
- Interface conventions
- Dashboard widgets
- Lifecycles
- MCP tools
- MCP tool parameters : Assets
- MCP tool parameters : Compliance
- MCP tool parameters : Governance and context
- MCP tool parameters : General
- MCP tool parameters : Incidents
- MCP tool parameters : Reports and management review
- MCP tool parameters : Risks
- MCP tool parameters : System and administration
- MCP tool parameters : Trust Center
- Management commands
- Models
- Permissions
- REST endpoints
- Environment variables
- MCP server
- REST API
- Assistant module (Ask Cairn)
- Module 0: User Management and Access Control
- Module 1: Context and Organization
- Module 2: Asset Management
- Module 3: Compliance
- Module 4: Risk Management
- Module 4 bis - EBIOS Risk Manager
- Module 5 : Trust Center
- Module 6 : Security Incident Management
- Management review : ISO 27001:2022 compliance (clause 9.3)