CommunityPay handles HOA financial data — assessments, bank accounts, disbursements, and governance records. This page describes the security controls that protect that data in production.
Everything described here is verifiable in our codebase. These are not aspirational controls — they are enforced today.
Transport Security
All connections to CommunityPay are encrypted in transit via TLS 1.2+.
HSTS (HTTP Strict Transport Security) is enforced with the following production configuration:
| Setting | Value |
|---|---|
| HSTS Duration | 63,072,000 seconds (2 years) |
| Include Subdomains | Yes |
| Preload | Yes |
| SSL Redirect | Enforced |
Once a browser connects to CommunityPay, it will refuse plaintext HTTP connections for two years. This is not configurable by users or attackable via downgrade.
Content Security Policy (CSP) is enforced in blocking mode in production with per-request nonce enforcement for inline scripts. Our CSP restricts script and connection sources to:
- Stripe.js (
js.stripe.com) for payment processing - Stripe hooks (
hooks.stripe.com) for embedded components - Approved CDNs for UI framework dependencies
- Our own API endpoints for data fetching
Inline scripts must carry a cryptographic nonce generated per request — scripts without a valid nonce are blocked by the browser. All other external script sources are blocked. Inline styles are permitted where required by framework dependencies.
Data at Rest
Field-level encryption using Fernet symmetric encryption is used for stored credentials and bank account identifiers. Database credentials are loaded from environment variables and never stored in code.
PostgreSQL connections require SSL in production. Public media files are stored in AWS S3. Sensitive documents — institutional packets, compliance reports, governance evidence, and violation records — are stored in a private S3 prefix with time-limited signed URLs (5-minute expiry). File downloads require application-layer authentication; the server generates a presigned URL and redirects the authenticated user — no file bytes are proxied through the application.
Permissions Policy restricts browser capabilities:
| Capability | Policy |
|---|---|
| Camera | Disabled |
| Microphone | Disabled |
| Geolocation | Disabled |
| Payment API | Self + Stripe only |
Authentication & Session Security
CommunityPay enforces strict session controls:
- Session timeout: 60 minutes of inactivity
- Browser close: Sessions expire when the browser closes
- HttpOnly cookies: Session and CSRF cookies are inaccessible to JavaScript
- SameSite: Strict in development, Lax in production (required for OAuth flows)
Brute-force protection via django-axes:
- 5 failed login attempts triggers a 30-minute account lockout
- Lockouts are IP-based, preventing credential stuffing attacks
- All authentication events are logged
Audit Logging
Every significant action in CommunityPay is captured in an immutable audit log.
Immutability enforcement: Audit log records cannot be updated or deleted. The AuditLog model raises a ValueError on any attempt to modify or remove an existing record. This is enforced at the application layer — not just by convention.
Checksum verification: Each audit log entry includes a SHA-256 checksum computed from its content. A verify_integrity() method can detect any tampering with log records after creation.
Coverage: The audit system tracks 50+ event types across categories:
- Financial: Payment creation, disbursement execution, journal entry posting
- Authentication: Login, logout, password changes, failed attempts
- Authorization: Permission changes, role assignments, approval decisions
- Security: Suspicious pattern detection, rate limit violations
Request-level logging: The audit middleware captures every POST, PUT, PATCH, and DELETE request with IP address, user agent, and response context. Request logging flags common injection and path traversal signatures for investigation. SQL injection is prevented at the ORM layer through parameterized queries.
Sensitive data masking: PII and credentials (passwords, tokens, SSN, account numbers) are redacted before reaching error reporting services.
Webhook Verification
CommunityPay receives webhooks from Stripe, Dwolla, and Plaid. Every webhook is verified before processing.
Stripe webhook verification:
- HMAC-SHA256 signature validation against the Stripe-Signature header
- Replay attack prevention: signatures older than 300 seconds (5 minutes) are rejected
- Timing-safe comparison via hmac.compare_digest() to prevent timing attacks
Dwolla webhook verification: - HMAC-SHA256 with constant-time signature comparison
Rate limiting prevents webhook flooding:
| Provider | Rate Limit |
|---|---|
| Stripe | 100 requests/minute |
| Dwolla | 60 requests/minute |
| Plaid | 50 requests/minute |
Payloads exceeding 512 KB are rejected before processing.
Stripe Connect Isolation
CommunityPay uses Stripe Express Connect accounts for HOA payment processing. This architecture means:
- CommunityPay never custodies HOA funds. Payments flow through Stripe directly to each HOA's connected account.
- Each HOA has its own Stripe Express account with independent bank verification and identity checks.
- Payment credentials are isolated — one HOA's payment methods cannot be used for another.
- Regulatory scope is limited — because we don't hold or transmit funds, we operate as a software platform, not a money transmitter.
This is a deliberate architectural choice. Custody creates regulatory risk. We avoid it entirely.
For security and operational reasons, we do not publish processor routing or rail-selection details publicly. Those are available under NDA during diligence.
Security Middleware Stack
All requests pass through a defense-in-depth middleware chain, in order:
- SecurityMiddleware — Django's built-in security headers
- PermissionsPolicyMiddleware — Browser capability restrictions
- AdditionalSecurityHeadersMiddleware — Clickjacking protection, content-type nosniff
- DomainRoutingMiddleware — Multi-tenant request routing
- RouteAwareCSPMiddleware — Content Security Policy enforcement with per-request nonce generation
- CsrfViewMiddleware — Cross-site request forgery protection
- AxesMiddleware — Brute-force detection and lockout
- WebhookRateLimitMiddleware — Provider webhook throttling
- AuditLoggingMiddleware — Request/response audit trail
- SessionAuditMiddleware — Session lifecycle tracking
Each layer operates independently. A failure in one middleware does not bypass the others.
What We Don't Do
- We don't store raw credit card numbers (Stripe tokenization handles this)
- We don't store plaintext passwords (Django's PBKDF2 + SHA256 hashing)
- We don't allow public access to sensitive document storage (signed URLs with 5-minute expiry)
- We don't permit inline scripts without a per-request cryptographic nonce
- We don't permit external script sources outside our CSP allowlist
- We don't log sensitive field values in audit trails
Security Contact
Report security issues to security@communitypay.us. We investigate all reports and will respond within 48 hours.
How CommunityPay Enforces This
- HSTS enforced at 2-year duration with preload
- CSP with per-request nonce enforcement for inline scripts
- Sensitive documents served via time-limited signed URLs (5-min expiry)
- Immutable audit logs with SHA-256 checksum verification
- Webhook signatures validated with HMAC-SHA256 and 300-second replay window
- Session timeout at 60 minutes with browser-close expiry
- Brute-force protection: 5 failures triggers 30-minute lockout