Status: design requirements, not implemented controls. Updated: 2026-11-08.
The initial deployment serves authorized staff of one nonprofit. It holds donor identities, contact details, source financial records, contribution decisions, and annual letters. Both confidentiality and integrity matter: an incorrect allocation or altered acknowledgment is a security-relevant failure even if no data is disclosed.
The public website is a separate application. Its process must not receive CRM database credentials, storage credentials, session keys, or payment-reporting credentials. Reusing a hostname prefix, code repository, or database server does not itself establish or remove this boundary; actual process privileges and credentials determine access.
Separate databases and restricted roles may share a database server. Shared host or infrastructure administrator access remains a common risk, and containers share host-level dependencies. The hosting design must document which compromises it is intended to contain.
The CRM keeps external references to website orders, accounts and subscriptions rather than foreign keys or required imports into the website's models, and needs no access to the website database. One nonprofit per installation is the first scope; Wagtail's site selection is not a tenant authorization boundary, so shared multi-organization hosting needs its own design and isolation tests.
The PayPal export is untrusted input, even when uploaded by a trusted staff member. Donor names, notes, and addresses are untrusted text. Wagtail content-editing access does not automatically authorize CRM access. Technical superusers and infrastructure administrators remain privileged operators, not actors this application can fully constrain.
This is a proposed capability matrix, not a requirement to create three separate staff accounts. The Editor may hold both operational and finance capabilities. Ordinary technical administration accounts should receive donor access only when explicitly required; a superuser can override application permissions and must be treated accordingly.
| Capability | Editor | Finance reviewer | Technical administrator |
|---|---|---|---|
| View donor and payment records | Yes | Yes | Explicit grant |
| Import and classify payments | Yes | Yes | Explicit grant |
| Resolve donor links and refund decisions | Yes | Yes | Explicit grant |
| Accept reconciliation and issue/revise letters | Yes | Yes | Explicit grant |
| Export data and record distribution | Yes | Explicit grant | Explicit grant |
| Manage accounts, roles, and deployment configuration | Explicit grant | Explicit grant | Yes |
Default to denied access. Enforce capabilities on every list, detail, mutation, bulk action, export, download, and future API. Hiding a menu, field, or Wagtail chooser is not sufficient. Avoid granting powerful user-management permissions to ordinary content editors.
Before real donor data is used in a hosted environment, provide secure staff authentication, MFA through the application or a supported identity gateway, login throttling, session expiry, and a tested account recovery/removal procedure. Use individual accounts rather than a shared Editor password.
Use HTTPS, secure and HttpOnly session cookies, CSRF protection, and host-scoped cookies with separate session configuration from the public website. Restrict trusted origins; avoid broad cross-subdomain trust. Django documents sibling-subdomain cookie risks, so a CRM subdomain alone is not complete browser isolation.
The specific VPN or gateway is deferred. If a gateway is selected, prevent bypass through an exposed origin and trust authentication/proxy headers only from the configured proxy. Gateway authentication supplements application authorization. A local development mode must be clearly separate from production configuration.
Raw imports, PDFs, mailing manifests, backups, and temporary renderer files belong in restricted storage outside public media access. Require authorization before minting any short-lived file URL. Protect direct storage access as well as application routes; use private responses that shared caches cannot retain.
Wagtail's chooser restrictions do not guarantee secrecy. Its document serving configuration also requires protecting direct storage URLs. The first implementation should use dedicated private storage for financial artifacts to make that requirement explicit.
Restrict CSV uploads by size and supported format, bound parser work, and validate values before application. Escape imported text in HTML and PDF content. Protect spreadsheet exports against formula interpretation. Restrict PDF rendering to approved local assets; imported text must not cause arbitrary URL or file fetching. Editable letter content must not enable arbitrary code or executable template expressions.
Validate and freeze correspondence addresses before distribution. Batch exports must be restricted and must not accidentally expose one donor's letter to another. Generating or downloading a file must not implicitly send it.
Keep production secrets in deployment configuration or a secrets service, with separate credentials per environment. CSV-only imports need no payment API secrets. Future adapters must request only the capabilities needed and must not share broad payment account credentials with the public site.
Use synthetic data in the public repository, CI, examples, and screenshots. Do not put production exports, real donor letters, or database dumps in GitHub issues or pull requests.
Audit source application, identity mapping, allocation and refund decisions, exclusions, reconciliation acceptance, issuance/revision, exports, and distribution. Include actor, time, record references, and meaningful changes. Restrict the audit trail; avoid full source payloads or contact data in routine logs, error reports, or analytics.
Application audit records are not automatically tamper-proof against administrators. Stronger audit retention can be added if operational requirements justify it. Define retention separately for raw imports, generated artifacts, audit records, and backups, including what happens when storage expires.
Before production use, identify an owner for dependency updates, incident handling, restricted encrypted backups, and recovery. Test restoring data and private artifacts together. Define a method for revoking an account, invalidating its sessions, and rotating exposed credentials.
| Risk | Evidence required |
|---|---|
| Unauthorized donor access | Negative tests for anonymous users, users without CRM permissions, direct record IDs, exports, and PDF URLs |
| Bulk action or download bypass | Endpoint-level tests independent of menu visibility |
| Duplicate or concurrent imports | Same-file, overlapping-file, changed-record, and simultaneous-application tests |
| Invalid financial allocation | Tests through forms, importer/write services, and database constraints |
| Source or donor text becoming executable | Escaping, parser bounds, export formula handling, and restricted PDF asset tests |
| Private files becoming public | Hosted checks of direct file access and storage ACLs, not just Django routes |
| Letter alteration or unintended dispatch | Frozen snapshots, explicit revisions, and explicit distribution state transitions |
| Deployment configuration drift | Production settings reviewed with Django's deployment checks; secrets and origin access checked |
This documentation records intended controls. A production release must record which controls are implemented and verified, and resolve any remaining gaps before handling real donor records.