Security
Last reviewed: 2026-05-03
This page describes the technical controls behind every WAHOA-hosted HOA portal. We don't pursue compliance certifications (yet — we're a small team), but every claim below corresponds to code you can audit in our public repository at github.com/SkyzFallin/wahoa.
Authentication
- Per-resident accounts. Every resident, board member, and admin has their own account. There are no shared credentials, no single "community password" to rotate.
- Password hashing. Passwords are stored as
scrypthashes (N=216, r=8, p=1 — at the OWASP recommendation). The encoded hash carries its parameters, so we can rotate to stronger settings without breaking existing logins. On every successful login we opportunistically re-hash if the stored params drift from current. - Two-factor authentication. Authenticator-app TOTP (RFC 6238) is available to every account and required for board and admin roles before they can post documents, send invites, or moderate the forum. Each enrollment generates 10 single-use recovery codes, hashed with
scryptat rest. - MFA secrets at rest. Every TOTP secret is encrypted with AES-256-GCM, with a fresh 12-byte IV per encryption. The decryption key is a Worker secret, never present in the database.
- Session tokens. Sessions are 256-bit random tokens; the KV store holds only their SHA-256 hash. A leaked KV snapshot cannot be replayed as a session. Session generation is bumped on password change, MFA enrollment, and right-to-be-forgotten, immediately invalidating every active session.
- Cookies.
HttpOnly(JS cannot read),Secure,SameSite=Lax, and host-scoped to the HOA's subdomain. - Single-use tokens. Password reset, invite, and email-change tokens are 256-bit random, stored only as SHA-256 hashes, atomically claimed (so two parallel confirms with the same token can't both win), and sibling tokens for the same user are revoked on a successful redemption.
Tenant isolation
- Every HOA lives at its own
{slug}.wahoa.orgsubdomain with a separate session cookie scope. - Every database query filters by the resolved tenant slug from the request host. Object storage keys are slug-prefixed. Cross-tenant access attempts return 404 — never 403 — so we don't even leak the existence of another tenant's data.
- Our test suite includes adversarial regression tests (
apps/portal/test/security.sh) that probe every cross-tenant path on every CI run. They must pass before any commit lands on main.
Transport & HTTP security
- HTTPS only, via Cloudflare. HSTS with a one-year max-age.
- Strict
Content-Security-Policywith a per-response nonce, nounsafe-inline, and no third-party origins. Every script and style on the page comes from our own Worker. X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy,Permissions-Policyon every response.- No third-party JavaScript anywhere — no Google Analytics, no Calendly, no chat widgets, no fingerprinting libraries. The marketing site renders without any JS at all.
Auditability
- Every login (success and failure), document access, admin action, MFA event, password change, and forum post is recorded in an append-only audit log scoped to the HOA.
- IP addresses and user-agents are HMAC-hashed with a per-environment salt, so the audit log can prove "the same IP attempted this twice" without storing the IP in plaintext.
- Audit retention is 24 months. A nightly cron sweep purges anything older.
- Admins can export their audit log to CSV. The export pipeline guards against CSV-injection (formula characters at the start of any cell are prepended with a single quote so spreadsheets render the literal text).
Right to be forgotten
On request from a resident or HOA board, we hard-delete the user row plus all dependent personal data (password resets, MFA secrets, recovery codes, email-change tokens, maintenance requests). Records the user contributed to the HOA — board posts, uploaded documents, audit-log entries — are retained as association records but anonymized (attribution set to NULL). Details: docs/RTBF.md.
Outbound email
Transactional email (invites, password resets, email-change confirmations) is sent through Resend. Our sending domain is configured with SPF, DKIM, and DMARC. Webhook callbacks from Resend are signed with Svix and verified server-side — replays outside a 5-minute timestamp tolerance are rejected, and the svix-id header is the primary key on the event store, so duplicate deliveries are silently idempotent.
Rate limits
Every authentication-adjacent endpoint (login, password reset, super-admin actions, lead form, forum posts) is rate-limited per-IP and where applicable per-email or per-user. Limits are tuned conservatively to slow brute force without inconveniencing legitimate use.
Data flow & third parties
The runtime stack is entirely Cloudflare: Workers (compute), D1 (database), KV (sessions / rate-limit counters / MFA challenges), R2 (document blobs), DNS, and SSL. The only third-party service in the data path is Resend, which transmits transactional email and sends back delivery webhooks. We do not use any analytics, tracking, advertising, or fingerprinting services.
Continuous testing
Every commit on the main branch runs:
- TypeScript type checking across all five workspaces.
- Unit tests for the cryptographic primitives, password hashing, recovery codes, and forum body sanitizer.
- End-to-end smoke tests covering login, MFA enrollment, document upload, announcement / event / maintenance / forum flows, and tenant isolation.
- Adversarial security regression tests covering cross-tenant IDOR (documents, announcements, events, forum threads & posts), SQL injection in tenant slugs, CSV injection in audit exports, path traversal in upload filenames, and CSRF (missing cookie, wrong content-type).
If any of these fail, the change cannot reach production.
Reporting a vulnerability
Email security@wahoa.org with details. We respond within two business days. Please give us a reasonable window to remediate before public disclosure.
What we don't claim
We do not currently hold SOC 2, ISO 27001, or any other formal certification. We are a small team and have not yet been through a third-party audit. If your HOA's bylaws or insurance policy requires a certified vendor, we will tell you so plainly — please ask before signing.