Security whitepaper

Last updated: 05/10/2026 · Document version 1.5

This document describes the technical security controls in the Cadrify platform. It is written for IT security reviewers, procurement teams and data protection officers carrying out due diligence. A non-technical summary is on the security overview page, and the contractual and data protection position is in the security and data protection policy.

Executive summary

What Cadrify protects

Cadrify is a multi-tenant platform for British 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 the live operational data used to coordinate election-day activity.

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

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 access 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 · PostgreSQL row-level security · no public data APIs
Session hijacking Low to medium, a 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 18 Frankfurt, Germany (EEA) AES-256 at rest · row-level security by org Parameterised queries · background worker and queue 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 and supporter data Activist contact records, opt-in status, event sign-ups UK GDPR Art. 6, Consent / Legitimate interests (Customer’s) Medium to high
Electoral register data Full names, addresses, elector numbers, dates of birth, ward data RPA 1983 (as amended). The Customer must hold a lawful basis under the 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 to high
Political opinion data Voting intentions, support scores, core party affiliation, door-knock responses UK GDPR Art. 9, Special category. Customer must document Art. 9(2) condition (typically (d)). Severe

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 · PostgreSQL row-level security §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 and account log · organisation audit log · operator access log §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 18, provisioned as a managed Render Postgres instance in Frankfurt with daily automated backups. The application uses parameterised queries only. No user-supplied input is interpolated into query text.

Application stack

The application is a Python web application using the Flask framework with a domain-driven blueprint architecture. Pages are rendered server-side with Jinja2. Where the interface needs richer interaction, React components built with Vite are mounted into the rendered page. Every front-end library, including MapLibre GL for maps, is served from the application’s own origin at a pinned version. No script loads from a third-party CDN, and the Content Security Policy (§10) does not allow one.

Background work

Long-running work such as register imports, geocoding batches, integration syncs and scheduled email runs on a separate worker service, also in Frankfurt, fed by a Redis-compatible queue (Render Key Value). The queue sits on Render’s private network with no external access. The state of every job, including progress and retries, lives in PostgreSQL rather than in the queue, and the worker declares an explicit cross-organisation context for its database work because it has no user session (§6).

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. Every HTTP request is 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-branch 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 Blocks JavaScript access, which limits session theft through XSS
Secure true (production) Prevents transmission over plain HTTP
SameSite Lax Withholds the cookie on cross-site requests, a second guard against CSRF
Max-Age 86 400 s (24 hours) Limits session lifetime to a day, 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 branch context. Switching between branches 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 branch:

Role Scope Access granted
Activist Assignment-only Electors and doors within an active door-knock or GOTV assignment only, with no register browsing
Organiser Exact branch Data management and user management within the assigned branch, with no inheritance to parent or sibling branches
Admin Branch and all descendants Full management of the branch and all child branches. No access to other 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-branch administrator (organisation-wide settings) from an administrator of a sub-branch, 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
  3. Is an organisation/branch 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 access 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 branch hierarchy using a recursive CTE query. A user who is admin of a parent branch is implicitly admin of all descendant branches. 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 branch 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 access 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, and isolation between organisations is enforced twice.

In the application. Every customer record carries an organisation identifier that is checked on read and write. Scoping is applied in the repository layer, so route handlers do not build their own query predicates, and the identifier is always taken from the authenticated session, never from user-supplied input.

In the database. Every table whose rows belong to exactly one organisation has PostgreSQL row-level security enabled and forced, with a single policy that admits a row only when the connection has declared a matching organisation or an explicit, logged bypass. At the start of each request the application declares the active organisation on its database connection as a transaction-local setting, which is reset at commit or rollback, so a pooled connection can never carry one organisation’s context into the next request. A connection that declares nothing sees no tenant rows at all. The policy fails closed. A query that forgot its organisation clause would therefore return nothing rather than another organisation’s data.

The bypass exists for two cases only: platform operators, whose every request is written to the operator access log (§5) and work with no request context, such as background jobs and scheduled tasks, which legitimately span organisations. A signed-in user with no resolved organisation is treated as an anomaly and fails closed rather than being granted the bypass.

Cadrify’s automated test suite includes explicit cross-organisation access assertions for both layers.

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 trimmed and lower-cased, name fields are trimmed and numeric identifiers are cast to integers with the failure caught. 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 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 runs 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
Self-service organisation sign-up 5 per 15 minutes, with the verification email resend at 3 per hour
Single sign-on start and callback 60 per minute
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 (Stripe billing webhook: 300 per minute)

In production these counters are held in the Redis-compatible queue store rather than in application memory, so they are shared across every web process and survive a restart of the web service. The durable 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

Sign-in attempts, successful or failed, by password, passkey, single sign-on or sign-in link, are written to the auth_audit_log table with timestamp, actor IP address and outcome. A failed attempt against an email address that has no account is recorded too, with no account attached. Login failures are counted per account in a rolling 30-minute window, and lockouts are also recorded. This log provides the primary signal for detecting credential-stuffing campaigns or targeted account attacks.

Organisation administrators and data protection officers see sign-in and credential events on the Sign-ins page of the compliance area: password, passkey, single sign-on and sign-in link sign-ins, failed attempts, lockouts, password resets and changes, with the time, the account, the action and the IP address. The page covers accounts that hold a role in the organisation, and attempts through the organisation’s own single sign-on connection. A failed attempt against an email address with no account is not attributed to any organisation and is not shown to customers.

Audit trail

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 access 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. Downloads of an organisation’s personal data are also logged separately, with who downloaded what, when and how many rows. They appear on the compliance overview and on the exports page of the data hub.

None of these logs can be edited through the application. Two things change entries. When an erasure request is approved, the organisation audit trail entries about that elector or activist keep who did what and when, and lose the summary and details that describe the person. When an organisation is deleted, its audit trail and export records are deleted with it. Authentication log entries are not deleted. When an account is deleted, its entries stay without the link to the account.

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
Authentication and account log No automatic deletion Sign-in and credential events on the Sign-ins page. The rest available to org admins on request
Organisation audit log Kept while the account is open. Deleted 30 days after it closes, except as set out above Edits, exports and erasures, available to org admins
Operator access 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 is already available to an organisation's data protection officer (see §20).

A failure to write an audit row is itself logged and investigated separately. It does not block or roll back the action that triggered it.

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. Front-end libraries such as MapLibre GL and Bootstrap are served from that origin at pinned versions, with no third-party script CDNs in the allowlist. Map tiles load from tiles.openfreemap.org (permitted in connect-src, with worker-src 'self' blob: for the map renderer). Fonts may load from Google Fonts. Images may load from any HTTPS origin, because branding and leaflet images are supplied by customers. The policy also permits the cookieless analytics endpoint and the demo-booking widget used on the public pages, and Stripe’s hosted payment scripts and frames on donation and ticket pages. The policy currently allows 'unsafe-inline' for scripts and styles. This is a known limitation, kept 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 (§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, database, background worker and job queue 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 the EEA (eu-west-1, Ireland)
postcodes.io Postcode geocoding British postcodes only, not linked to a named individual in the request British 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. Subscription billing, and donation or ticket payments where a Customer uses them, are handled by Stripe, which processes in Ireland and the United States under standard contractual clauses. Card details are entered on a Stripe-hosted page and never reach Cadrify’s servers. No campaign data goes to Stripe.

AI features. A small number of AI-backed features are granted per organisation and are off unless Cadrify enables them for a Customer. Where none is enabled, nothing the organisation holds reaches an AI provider. Where one is, the request goes to Anthropic or to OpenAI where a deployment is configured to use it, both of which process outside the EEA. Voting intentions, support scores and door-knock notes are never sent. What each feature does send is itemised in the security and data protection policy.

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 branch 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. Those requests carry map coordinates and nothing else. Map and chart libraries are served from Cadrify’s own origin, not a CDN.

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

Electoral register records, door-knock history and GOTV records are what a campaign cannot work without in the final weeks. A restore puts those back first.

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 when a person with access to them leaves and immediately on any suspected compromise. Because sessions are server-side tokens rather than signed cookies, rotating the application signing key does not sign users out. Rotating the integration encryption key uses the re-encryption procedure above so no stored credential is orphaned.

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 · PostgreSQL row-level security · 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, 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, with 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 above are what is built today. The items below are planned or in progress. We publish them so a reviewer can see what is coming and what is not, rather than find out in month two.

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 CSV export of the audit log within a Customer’s organisation, available to the organisation’s data protection officer role Available
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 OIDC integration allowing organisations to authenticate users via their own identity provider. SAML 2.0 support is not yet built Available (OIDC)
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.