Security Whitepaper

Last updated: 12 June 2026 — Document version 1.3

This document describes the technical security controls implemented in the Cadrify platform. It is intended for IT security reviewers, procurement teams, and data protection officers conducting due-diligence assessments. A non-technical summary is available on the Security overview page.

Executive summary

What Cadrify protects

Cadrify is a multi-tenant platform for UK political campaign management. It processes some of the most sensitive categories of personal data in domestic politics: the full electoral register, door-knock records expressing individual voters’ political opinions, volunteer and supporter contact lists, and live operational data used to coordinate election-day activity. Security is treated as a core product requirement, not an afterthought.

Cadrify processes this data as a data processor on behalf of subscribing political organisations. Each organisation’s data is logically isolated from every other organisation on the platform. Platform operator access to customer data is restricted, audited, and limited to support and incident response scenarios.

Threat model

Threat Risk to political data Primary controls
Credential stuffing / account takeover High, political organisations are high-value targets for state and partisan actors scrypt password hashing · account lockout · rate limiting · session rotation on credential change
Insider threat / privilege abuse Medium, staff or former members with access could exfiltrate data Least-privilege RBAC · per-request operator audit log · account deactivation on role removal
Web application attack (SQLi, XSS, CSRF) Medium, successful exploit could expose register or door-knock data Parameterised queries · Jinja2 auto-escaping · Flask-WTF CSRF · nh3 sanitisation
Electoral data exfiltration High, electoral register and political opinions are restricted special-category data Authentication required on all data endpoints · org_id scoping on every query · no public data APIs
Session hijacking Low–Medium, stolen session token grants access equivalent to the user HttpOnly · SameSite=Lax · Secure flag · 24-hour expiry · server-side token store (SHA-256 hashed) · full session invalidation on password change
Supply chain / third-party compromise Low, no third party receives electoral register or door-knock (special-category) data Electoral and door-knock data never leave the platform · contact sync only to customer-configured CRMs under the Customer’s own credentials · geocoding receives postcodes only · DPAs with platform processors

Security architecture

EEA · Frankfurt, Germany Client browser Any device · any location HTTPS · TLS 1.2/1.3 Render edge TLS termination · HTTP → HTTPS redirect encrypted Cadrify application · Flask · Python Authentication · RBAC · CSRF protection · Rate limiting Parameterised SQL · Jinja2 auto-escaping · nh3 Audit logging · Input validation · Security headers encrypted PostgreSQL 14+ Frankfurt, Germany (EEA) AES-256 at rest · org_id row scoping Parameterised queries · connection pool HTTPS only External services third-party processors Resend email · EEA (Ireland) CRM sync contact data · customer-configured postcodes.io · DC postcodes only, no register data

Dashed arrow = HTTPS only. Electoral register and door-knock (special-category) data never leave the platform. Activist contact data syncs only to customer-configured CRMs.

Data classification

Classification Examples Regulatory basis Risk
Public Campaign pages, petition forms, event listings N/A Low
Platform account data User names, email addresses, profile pictures, role memberships UK GDPR Art. 6, Contract / Legitimate interests Medium
Volunteer & supporter data Activist contact records, opt-in status, event sign-ups UK GDPR Art. 6, Consent / Legitimate interests (Customer’s) Medium–High
Electoral register data Full names, addresses, elector numbers, dates of birth, ward data RPA 1983 (as amended); Customer must hold lawful basis under RPA and UK GDPR High
Operational campaign data Door-knock rounds, GOTV assignments, calling-card drops, route plans UK GDPR Art. 6, Contract (as processor) Medium–High
Political opinion data Voting intentions, G-scores, party preferences, door-knock responses UK GDPR Art. 9, Special category. Customer must document Art. 9(2) condition (typically (d)). Very High

Key controls summary

Control area Implementation Section
Data encryption TLS 1.2/1.3 in transit · AES-256 at rest (AWS EBS) · Fernet application-layer encryption of stored integration secrets §2, §18
Authentication scrypt hashing · 256-bit tokens · lockout · forced rotation §3, §4
Access control Role-based access control · ancestor-chain inheritance · org_id isolation §5, §6
Application security Parameterised SQL · XSS escaping · CSRF tokens · input validation §7
Brute-force protection IP rate limiting (Flask-Limiter) · per-account lockout §8
Audit logging Authentication audit log · operator audit log · 12-month retention §9
Compliance UK GDPR · DPA 2018 · PECR · RPA 1983 · Article 9 §19

1. Infrastructure and hosting

Hosting provider

Cadrify runs on Render (render.com), a managed cloud platform. Application servers and the PostgreSQL database are provisioned in Render’s Frankfurt, Germany region (within the EEA). No customer data is persisted outside the EEA at rest. Render operates on Amazon Web Services infrastructure with physical access controls and ISO 27001-aligned operational practices.

Database

The primary data store is PostgreSQL, provisioned as a managed Render Postgres instance in Frankfurt. The application uses parameterised queries exclusively; no user-supplied input is ever interpolated directly into query strings. No raw string interpolation into query text is permitted.

Application stack

The application is a Python web application using the Flask framework with a domain-driven blueprint architecture. Rendering is server-side Jinja2, progressively enhanced with vanilla JavaScript. There is no single-page-application framework. Pages that display maps load the MapLibre GL library from a public CDN, constrained by the Content Security Policy (see §10).

Fail-closed configuration

On startup the application validates its own configuration and refuses to start if a security-critical setting is missing or unsafe, for example an unset signing key, or the development-default signing key in a production deployment. The process exits rather than run in a known-insecure state. In production the application runs behind Render’s TLS-terminating load balancer and trusts exactly one proxy hop when resolving the client IP address used for audit logging and rate limiting.

2. Data-at-rest and data-in-transit encryption

In transit

All connections between clients and the Cadrify application are encrypted using TLS 1.2 or 1.3, terminated at Render’s edge. Plain HTTP is not accepted; all HTTP requests are redirected to HTTPS at the infrastructure level. Secure flags are set on all application cookies in production, preventing transmission over unencrypted connections. Traffic between the application server and the PostgreSQL instance is encrypted within the Render private network.

At rest

Database volumes are encrypted at rest by Render’s underlying AWS EBS storage (AES-256). Profile images and cached geocoding data stored on application file storage are similarly encrypted at the storage layer. No customer data is stored on unencrypted media.

Application-layer encryption of secrets

Sensitive integration credentials are additionally encrypted at the application layer before they are written to the database, so they are never stored as readable text even within the encrypted database volume. This applies to the per-group credentials for every external CRM integration: the Action Network API key, the NationBuilder access token, the Movement API key, and the Salesforce private key. Encryption uses Fernet (AES-128 in CBC mode with an HMAC-SHA256 authentication tag) from the Python cryptography library. The encryption key is held in a dedicated environment variable, separate from the application code and from the database. Ciphertext is decrypted only at the moment an outbound API client is constructed. The plaintext credential is never logged or returned in any application response. Key management for this control is described in §18.

3. Authentication

Password hashing

Passwords are stored using the scrypt memory-hard key derivation function, applied via Werkzeug’s password hashing utilities. Passwords are never stored in plain text, logged, or transmitted beyond the login POST request. The minimum password length is enforced at 12 characters at both the server and client layers. Beyond the length floor, the server rejects passwords that lack sufficient character variety, appear on a weak-password denylist, or are derived from the user’s own email address.

Password reset

Password reset tokens are generated using Python’s cryptographically secure OS random source (the secrets module), producing a URL-safe random string. Tokens are single-use: they are marked as consumed on redemption and cannot be replayed. Tokens expire after 24 hours. Requesting a new reset token invalidates any previously issued token for that account. Reset tokens are delivered only to the email address already associated with the account.

Invite tokens

New users are onboarded via invite tokens, also generated using Python’s cryptographically secure OS random source. Tokens expire after 30 days and are single-use. Superseded invites are marked used_at='superseded' in the audit trail. Invite issuance and redemption are both recorded in the auth audit log with IP address.

Passwordless sign-in (magic link)

Existing users may request a one-time sign-in link sent to their registered email address. The link carries a token generated using the secrets module, valid for 15 minutes and single-use. Requesting a new link invalidates any previously issued unused link. Magic-link sign-in authenticates existing accounts only, it does not create accounts. Requests are rate limited per IP address and per email address (see §8), and both issuance and redemption are recorded in the authentication audit log. To prevent account enumeration, the same confirmation is shown whether or not the email matches an account.

Account enumeration prevention

The login endpoint returns a generic “Invalid email or password” message regardless of whether the email address exists. Account lockout is applied silently, without informing the requester whether the account exists, preventing the lockout mechanism from becoming an account-enumeration oracle.

Forced password change

When an administrator resets a user’s password, the must_change_password flag is set. The user is redirected to a forced password-change screen on next login and cannot access any other part of the application until the change is completed. A new session token is issued on completion and all prior sessions are invalidated.

4. Session management

Token generation and storage

On successful authentication, the application generates a session token using Python’s cryptographically secure OS random source (the secrets module). Only a SHA-256 hash of the token is stored server-side in the database, alongside the associated user identity, creation timestamp, expiry, client IP address, and active organisation context. The raw token exists only in the user’s cookie, so a read-only exposure of the session table yields no usable credentials. The token is issued as an HTTP-only cookie named auth_token. It is an opaque bearer token validated by server-side lookup, not a signed client-side payload.

Cookie security attributes

Attribute Value Purpose
HttpOnly true Prevents JavaScript access; mitigates XSS-based session theft
Secure true (production) Prevents transmission over plain HTTP
SameSite Lax Prevents cookie submission on cross-site requests; mitigates CSRF
Max-Age 86 400 s (24 hours) Limits session lifetime, requiring daily re-authentication. Chosen because campaign devices are often shared

Session rotation and invalidation

A new session token is issued on every login. On logout, the token is deleted from the database and the cookie is cleared. On password change (self-initiated or admin-forced), all active sessions for that user are deleted before the new session is created, ensuring no stale sessions survive a credential change. Sessions are also purged opportunistically during request processing, removing expired rows.

Session context

Each session record stores the user’s currently active organisation and group context. Switching between groups updates this record in place rather than issuing a new token. The client IP address captured when the session was created is retained on the session record.

5. Authorisation and role-based access control

Role hierarchy

Cadrify implements a layered role model within each organisation group:

Role Scope Access granted
Activist Assignment-only Electors and doors within active door-knock / GOTV assignment only; no register browsing
Organiser Exact group Data management and user management within the assigned group; no inheritance to parent or sibling groups
Admin Group and all descendants Full management of the group and all child groups; cannot access peer organisations
Data protection officer Organisation-wide, read-only Read-only access to the compliance area for the whole organisation: audit log, subject-access and erasure tooling. No campaign-data write access

Two refinements apply in practice. First, the Admin role distinguishes the organisation’s root-group administrator (organisation-wide settings) from an administrator of a sub-group, who manages that branch and its descendants only. Second, activist-level access is granted through specific assignment roles (for example door-knocker, teller, or GOTV knocker), each scoped to an active assignment and, where relevant, to a particular round.

Permission check order

Every protected route passes through server-side authorisation middleware that enforces the following check sequence, aborting with the appropriate HTTP status at the first failure:

  1. Is the user authenticated? → redirect 302 to login
  2. Is the user’s account active? → 403 Forbidden
  3. Is an organisation/group context set in the session? → redirect to the context picker
  4. Does the user hold the platform operator flag? → allow (operator access is recorded in the operator audit log earlier in the request pipeline)
  5. Does the user hold the minimum required role, considering ancestor-chain inheritance? → allow or 403

Ancestor-chain inheritance

Admin role checks traverse the group hierarchy using a recursive CTE query. A user who is admin of a parent group is implicitly admin of all descendant groups; the database query returns the full ancestor chain and checks for admin membership at any level. Organiser access does not inherit; it applies only to the exact group assigned.

Platform operator access

A small number of platform operators hold an elevated privilege flag that permits cross-organisation access for support and incident response. Every request made by a platform operator is recorded in a dedicated operator audit log, capturing the operator account, the action, the active organisation, and the client IP address. The record is written in the request pipeline before the route handler runs. These records are held in a separate table from the application’s ordinary data and are not editable through the application interface.

6. Multi-tenant data isolation

Cadrify is a multi-tenant application. Customer data is required to carry an organisation identifier that is checked on read and write operations. Scoping is enforced in the repository layer. Route handlers do not construct their own query predicates, and the value is always sourced from the authenticated session context, not from user-supplied input.

The risk of cross-tenant leakage is limited to application-layer bugs. Cadrify’s automated test suite includes explicit cross-organisation access assertions to guard against this class of error.

Reference data (Democracy Club election/party tables, geographic reference areas) is not org-scoped and is read-only for all users. It contains no personal data.

7. Application security controls

SQL injection prevention

All database interactions use parameterised queries. No user-supplied input is interpolated directly into query strings. This approach is enforced consistently across the codebase through a shared database access layer rather than relying on individual developer discipline per module.

Cross-site scripting (XSS)

Flask’s Jinja2 templating engine performs automatic HTML escaping on all variables rendered into HTML responses. User-supplied content is never rendered as raw HTML except in administrator-controlled branding fields, which are additionally sanitised using nh3, an HTML sanitiser that enforces a strict allowlist of tags and attributes.

Cross-site request forgery (CSRF)

CSRF protection is provided by Flask-WTF (CSRFProtect), initialised globally on application startup. All state-changing requests from the browser require a valid CSRF token, validated server-side on every form submission. CSRF protection is disabled in the development and test configurations (WTF_CSRF_ENABLED = False) and is always enforced in production.

Two server-to-server endpoints are exempt from CSRF tokens because they carry no session cookie and therefore present no CSRF surface. Each authenticates by a different mechanism:

Input validation

User-supplied strings are normalised before use: email addresses are .strip().lower()ed; name fields are .strip()ed; numeric IDs are cast with int() and caught on exception. File uploads are validated by MIME type before processing. The platform does not evaluate user-supplied data as code at any point. Data exported to CSV is additionally guarded against spreadsheet formula injection: any cell beginning with a formula trigger character (=, +, -, @, tab or carriage return) is prefixed so spreadsheet software treats it as text.

8. Rate limiting and brute-force protection

Cadrify deploys two independent layers of brute-force protection:

Endpoint rate limiting

Rate limiting is applied to authentication and other sensitive endpoints. Most limits are keyed on client IP address. Login and magic-link requests are additionally limited per submitted email address, and the data export API is limited per API key. Current limits:

Operation Limit
Login Per IP: 10 per minute, 50 per hour. Per account: 10 per minute, 30 per hour
Self-service password reset request 10 per hour
Password reset token redemption 20 per hour
Magic-link request 5 per hour per IP, 5 per hour per email
Magic-link verification 20 per hour
Invite token redemption 20 per hour
User creation 60 per hour
Admin-initiated password reset 30 per hour
Role assignment (add or remove) 20 per hour
Public contact form 8 per hour
Data export API 120 per hour per key, plus a 200 per hour per-key limit enforced from the request log
Integration webhook receivers 200 per minute

The application runs as a single worker process with multiple threads, so these in-process counters are consistent across concurrent requests. They are held in memory, not shared across multiple processes, and reset on deploy or restart. The durable, restart-surviving control against password guessing is the per-account lockout described below, which is stored in the database.

Per-account lockout

Independently of IP-based rate limiting, the application tracks failed login attempts per user account. After 10 failures within a 30-minute rolling window, the account is locked for 30 minutes. The lockout is cleared automatically on expiry or on a successful login. Account lockout events are recorded in the auth audit log with the actor IP address.

9. Logging and monitoring

Authentication logs

Every login attempt, successful or failed, is written to the auth_audit_log table with timestamp, actor IP address, and outcome. Login failures are counted per account in a rolling 30-minute window; patterns that trigger account lockout are also recorded. This log provides the primary signal for detecting credential-stuffing campaigns or targeted account attacks.

Audit trail

All security-relevant platform events are written to auth_audit_log with the following fields: actor_user_id, action, target_user_id, target_group_id, target_role, details (JSONB, event-specific context), ip_address, created_at.

Recorded action types:

Every request made by a platform operator is additionally written to a separate operator audit log, capturing the operator account, the action, the active organisation, the client IP address, and the timestamp. This logging is performed in the request pipeline before any route handler executes.

Alongside the authentication audit log, a broader data-change audit trail records security-relevant and data-mutating actions within an organisation, for example record edits, exports, and erasures. It is available to organisation administrators and data protection officers through the compliance area.

Anomaly indicators

The following patterns in the audit log indicate potential security anomalies and can be used for manual or automated alerting:

Automated alerting and SIEM integration are on the security roadmap (see §20).

Retention periods

Log type Retention Notes
auth_audit_log Minimum 12 months Available to org admins on request
Operator audit log Retained for the life of the platform Held in a separate table; not editable through the application interface
Application server logs 30 days (Render default) HTTP access and error logs

Access review

Organisation administrators are responsible for periodically reviewing the list of active users and their role assignments within their organisation. Cadrify provides the user management interface for this purpose. When a user is deactivated, all subsequent requests are rejected by the authorisation middleware regardless of any existing session token. Customer-facing audit log export and access review tooling are on the roadmap (see §20).

Audit logging is designed to be highly available and failure-tolerant. Logging failures are independently captured and investigated, without affecting user availability or rolling back the triggering operation.

10. Security headers

The following HTTP security headers are applied by the application’s after_request hook (Strict-Transport-Security is sent on HTTPS responses):

Header Value Purpose
X-Frame-Options SAMEORIGIN Prevents clickjacking via iframe embedding on third-party origins
X-Content-Type-Options nosniff Prevents MIME-type sniffing attacks
Referrer-Policy strict-origin-when-cross-origin Limits referrer information sent to third-party origins
Content-Security-Policy see below Restricts the origins from which scripts, styles, images, fonts, frames, and connections may load. Complements output escaping against XSS
Strict-Transport-Security max-age=31536000; includeSubDomains Instructs browsers to use HTTPS only for one year, including subdomains. Sent on HTTPS responses

Content Security Policy

An enforcing Content Security Policy is applied to every response. The base policy is default-src 'self' with object-src 'none', base-uri 'self', and frame-ancestors 'self'. Scripts and styles are restricted to the application’s own origin plus a small allowlist of trusted CDNs (jsDelivr, unpkg, cdnjs) used for front-end libraries such as MapLibre GL. Map tiles load from tiles.openfreemap.org (permitted in connect-src, with worker-src 'self' blob: for the map renderer). On public marketing pages the policy additionally permits the cookieless analytics endpoint and the demo-booking widget. The policy currently allows 'unsafe-inline' for scripts and styles. This is a known limitation, retained because the server-rendered templates still use some inline handlers and style attributes. It does not allow 'unsafe-eval'. Moving inline allowances to a nonce-based model is on the roadmap (see §20).

11. File handling and upload security

Profile avatars

Avatar uploads are validated against an allowlist of MIME types (image/jpeg, image/png, image/gif, image/webp). The uploaded file is then opened, decoded, and re-encoded by Pillow (PIL), cropped to a 256×256 square and re-encoded as JPEG, before storage. This re-encoding strips any embedded metadata (EXIF, ICC profiles) and prevents polyglot file attacks. The global upload size limit is 32 MB. Stored profile images live in a private application directory restricted to the application user.

Data file imports

Electoral register imports are parsed with pandas from the raw bytes of the uploaded file in application memory. No uploaded file is written to a publicly accessible location. Between the upload and the user confirming the column mapping, the parsed data is stored server-side in the database as a compressed draft (not in a cookie), scoped to the importing organisation, so a large import survives a worker restart. Draft imports that are abandoned are purged automatically after 30 days. On confirmation the data is committed to the register tables and the draft is cleared.

Other uploads

The platform accepts a small number of other uploads, each validated before use:

Runtime directories used for uploads and generated files (profile images, map boundaries, geocoding cache, generated rounds and sheets) are created with restrictive permissions so they are readable only by the application user.

12. Browser storage and offline data

To support canvassing in areas with poor connectivity, the field tools keep a limited amount of operational data on the user’s device. This is a deliberate design choice for offline working, and it means some personal data is held on the device until it is synchronised back to the server.

All browser-stored data is scoped to the application’s own origin and is not accessible to third-party code. None of it is used for authentication tokens or session state, which remain in the HTTP-only session cookie. Signing out, and the device’s own controls for clearing site data, remove the on-device copies.

13. Third-party integrations and subprocessors

This section distinguishes two kinds of third party. Platform subprocessors are services Cadrify uses to operate the platform, engaged under data processing agreements. Customer-configured integrations are external systems a Customer chooses to connect, using the Customer’s own credentials, so that data flows between Cadrify and a system the Customer already controls.

Platform subprocessors

Provider Purpose Data handled Security posture
Render Application hosting and database All platform data AWS-backed; Frankfurt region; AES-256 at rest; SOC 2 Type II (via AWS)
Resend Transactional email delivery (Dublin, Ireland, EEA) Recipient email address and email content (password reset links, invite links, notifications) SOC 2 Type II; TLS in transit; data within EEA (eu-west-1, Ireland)
postcodes.io Postcode geocoding UK postcodes only (not linked to named individuals in the request) UK-based open-data service; results cached locally

Email delivery. Transactional email is delivered through Resend (EEA, Ireland). Cadrify may move to a different EEA email provider in future, and this section will be updated if it does.

Billing. Self-service billing is not yet enabled in the platform. No payment-card data is processed by Cadrify and no payment processor currently receives any data. When billing launches, the payment processor will be listed here and card data will be handled by that processor directly, never stored by Cadrify.

Customer-configured integrations

A Customer may connect one or more external CRM or organising systems so that supporter and volunteer records stay in step between Cadrify and that system. These integrations are optional, configured per group, and use credentials issued by the Customer for their own account in the external system. For these systems the Customer is the controller and the external provider is the Customer’s processor, not Cadrify’s.

System Direction Data exchanged Region
Action Network Both ways Supporter contact records: name, email, postal address and postcode, tags United States
NationBuilder Inbound, outbound available Supporter contact records: name, email, phone, postcode United States
Salesforce Inbound, outbound available Contact records: name, email, mobile, postcode, contact preferences The Customer’s own Salesforce instance
Movement Inbound, outbound available Supporter contact records and sign-up activity: name, email, phone, subscription status Per the Customer’s Movement account

Credentials for these integrations are stored encrypted at the application layer (see §2). Inbound data is written only to the volunteer-and-supporter contact records. No electoral register record and no door-knock or other special-category data is ever included in any integration sync. Because these systems are chosen and controlled by the Customer, the contact data they exchange may be processed outside the UK or EEA. The Customer is responsible for the lawful basis and transfer safeguards for their own CRM.

Reference-data and geocoding services

Cadrify calls a number of public services for reference data and geocoding. These receive only postcodes or public area codes, never names, full addresses linked to a named person, or any register record:

Per-address geocoding through Google Maps or Nominatim is available as an optional backend. When enabled it sends address text, without a person’s name, to that provider. It is not used by default; postcode-level geocoding via postcodes.io is the standard path.

Browser-side services on public pages

On the public marketing and legal pages only, the browser loads a cookieless analytics script (Plausible) and a demo-booking widget (TidyCal). Neither is loaded on authenticated application pages, so URLs that contain record identifiers are never sent to them. Map views load vector tiles directly from OpenFreeMap, and map and chart libraries from public CDNs. These requests carry only map coordinates and asset paths, no personal data.

In summary: electoral register data and door-knock (special-category) data never leave the platform to any third party. Supporter and volunteer contact data leaves only to the Customer’s own configured CRM integrations. Email content goes only to the email provider, and geocoding and reference lookups receive only postcodes or public area codes.

14. Electoral register data handling

Electoral register data is subject to the Representation of the People Act 1983 and the Electoral Registration and Administration Act 2013. Access to the full register is restricted to the purposes defined in law. Cadrify enforces the following additional controls specific to this data category:

15. Incident response and vulnerability disclosure

Incident response

In the event of a confirmed security incident affecting customer data, Cadrify will:

  1. Contain the incident and preserve evidence.
  2. Assess the scope and nature of data affected.
  3. Notify affected Customer organisations without undue delay, and in any event within 72 hours of becoming aware where applicable, including the nature of the incident, categories and approximate number of data subjects affected, and measures taken or proposed, to support Customers in meeting their own breach-notification obligations.
  4. Cooperate with Customer organisations in any required notifications to the ICO or affected data subjects.
  5. Provide a post-incident report to affected Customers on request.

Responsible disclosure

If you discover a potential vulnerability in the Cadrify platform, please report it to security@cadrify.org before public disclosure. Please include:

We will acknowledge receipt within 2 business days and aim to provide an initial assessment within 5 business days. We ask that you allow us reasonable time to remediate before any public disclosure. We will not take legal action against researchers acting in good faith under this policy.

Security contact

Email: security@cadrify.org

For data protection and privacy questions: privacy@cadrify.org

16. Backup and recovery

Database backups

The PostgreSQL database is backed up automatically by Render’s managed database service. Backups are taken daily and retained for a minimum of seven days. Point-in-time recovery (PITR) is available within the retention window, allowing the database to be restored to any point in time rather than only to a daily snapshot.

File storage

Uploaded register files and other persistent data stored on the application’s persistent disk are covered by Render’s disk snapshot capability. Snapshots are taken periodically and retained in accordance with Render’s infrastructure practices.

Restore testing

Restore procedures are tested periodically to verify that backups are valid and that recovery objectives can be met. Formal recovery time and recovery point objectives (RTO / RPO) are documented internally and shared with enterprise customers on request.

Relevance for political campaigns

Campaign data, particularly electoral register records, door-knock history, and GOTV operation records, is operationally critical during election periods. In the event of a data loss incident, Cadrify’s recovery objective prioritises restoration of these data categories within the shortest feasible window.

17. Dependency management and patching

Dependency tracking

Application dependencies are pinned in a requirements file and reviewed regularly. Known vulnerability databases (including GitHub Dependabot alerts and the Python Packaging Advisory Database) are monitored for security advisories affecting packages in use. Continuous integration runs an automated dependency vulnerability scan (pip-audit) and a static application security analysis (bandit) on every change, alongside linting and type checks.

Patch cadence

Security-relevant dependency updates are applied with priority. Critical vulnerabilities in direct dependencies are targeted for remediation within seven days of a confirmed advisory; lower-severity updates are applied in the normal release cycle. The operating system and runtime environment are kept current by Render’s managed infrastructure.

Infrastructure updates

Render manages patching of the underlying compute infrastructure, container runtime, and database engine. Cadrify reviews Render’s security communications and applies application-level changes required by infrastructure updates promptly.

18. Secrets management

Secret storage

Application secrets (database credentials, API keys, cryptographic signing keys, email service credentials) are stored exclusively as environment variables in Render’s secret environment configuration. No secrets are committed to the application code repository. Environment files are excluded from version control, and code review guards against accidental secret inclusion.

Encryption key for stored secrets

The application-layer encryption described in §2 uses a dedicated encryption key supplied as its own environment variable, held separately from the database. Where a dedicated key is not configured the key is derived from the application signing key, which is acceptable for development but a dedicated key is used in production. A re-encryption procedure is available so that stored integration credentials remain readable across a key change.

Secret rotation

Secrets are rotated on a periodic basis and immediately on any suspected compromise. Session signing keys and API credentials are rotated independently of application deployments, with the application configured to tolerate a brief transition window where both old and new credentials are valid.

Access to secrets

Access to production environment secrets is restricted to a minimal set of platform operators. Secret values are not logged, echoed in error messages, or included in application responses under any circumstances.

19. Privacy and compliance alignment

Cadrify’s security controls are designed to meet its obligations as a data processor and, where applicable, as a data controller under UK data protection law. The table below maps key regulatory obligations to the platform controls that address them.

Regulation Obligation Cadrify controls
UK GDPR / DPA 2018 Art. 5(1)(f), integrity and confidentiality (security principle) TLS in transit · AES-256 at rest · RBAC · session management · account lockout
Art. 25, data protection by design and by default Least-privilege roles · org_id data isolation · minimal data sent to geocoding APIs · no analytics cookies
Art. 28, processor obligations Written DPA with each Customer · subprocessor DPAs · audit trail for operator access · prompt breach notification to support Customer obligations
Art. 33, breach notification Incident response procedure (see §15) · notification to Customers without undue delay to support their regulatory obligations · cooperation with ICO notification where required
UK GDPR Art. 9, special category data Political opinions (voting intentions, G-scores) require an Art. 9(2) condition in addition to an Art. 6 lawful basis Customer (as controller) must document their Art. 9(2) condition (typically (d) for political parties). Cadrify processes only under Customer instructions and applies heightened access controls and audit logging to all door-knock data.
PECR 2003 Cookies, consent requirements for non-essential cookies No advertising cookies used. The marketing-site analytics is cookieless. Session and auth cookies are strictly necessary. Functional cookie (signer_info) is disclosed in the Cookies policy. No consent banner required for current cookie set.
RPA 1983 (as amended)
Electoral Registration and Administration Act 2013
Full electoral register, restricted to lawful purposes only; supply and use tightly controlled Authentication required on all elector endpoints · org_id scoping · activist access limited to assigned electors only · no public API exposing register data · Customer is responsible for documenting their lawful basis under RPA for holding the register
UK GDPR Chapter V, international transfers Transfers outside UK/EEA require adequate safeguards Database in Frankfurt (EEA, UK adequacy). Email delivery via Resend in Dublin, Ireland (EEA, UK adequacy). Electoral register and door-knock (special-category) data stay within the EEA. Customer-configured CRM integrations may transfer supporter contact data outside the UK or EEA under the Customer’s own controller relationship and transfer safeguards (see §13).

20. Security roadmap

The controls described in this document represent Cadrify’s current security posture. The following improvements are planned or in active development. This roadmap is published to demonstrate commitment to continuous improvement, not to imply that current controls are insufficient.

Control Description Status
Content Security Policy hardening Replace the 'unsafe-inline' script and style allowances in the current enforcing policy (see §10) with per-response nonces Planned
Multi-factor authentication (MFA / 2FA) TOTP-based second factor for all platform users, with admin-enforced MFA policy per organisation Planned
Customer-facing audit log export Self-service export of authentication audit log entries within a Customer’s organisation, in CSV and JSON formats In development
Automated anomaly alerting Real-time alerting on login failure spikes, account lockout events, and operator access outside normal hours Planned
External penetration testing Annual CREST-accredited or equivalent penetration test of the application and infrastructure, with results shared on request Planned
Organisation SSO SAML 2.0 / OIDC integration allowing organisations to authenticate users via their own identity provider Planned
Customer-managed retention Organisation admins set their own data retention period for electoral and door-knock data, with automated deletion Planned
SOC 2-aligned controls Formal documentation of security policies, access review procedures, change management, and vendor management aligned to SOC 2 Trust Service Criteria Planned
Encryption key management Extend application-layer field encryption beyond integration secrets to the highest-sensitivity campaign data (political opinions, elector data), with a customer-managed key hierarchy Under evaluation

Questions about the security roadmap or specific controls can be directed to security@cadrify.org.