How we protect your data
Security practices
These are the controls behind the portal — not marketing copy, but the actual technical decisions made by the team during the security remediation process. Every claim below is implemented in code and locked in by regression tests so it can’t silently regress.
Password storage — Argon2id
Every password is hashed with Argon2id, the OWASP-recommended algorithm as of 2023. Our parameters are 64 MiB memory cost, 3 iterations, 4-way parallelism, and a 16-byte salt auto-generated per hash — at the upper end of the OWASP defaults.
OAuth-only users have passwordHash = NULL in the database. We run a real Argon2id verify against a cached dummy hash on every failed login (user not found, OAuth-only) so a timing attack can’t distinguish “wrong password” from “no account with that email.”
Two-factor authentication — TOTP + backup codes
MFA uses the RFC 6238 TOTP standard so any authenticator app works: Google Authenticator, 1Password, Authy, YubiKey, Microsoft Authenticator. The secret is 160 bits of entropy, base32-encoded, displayed only once during enrollment.
Backup codes are Argon2id-hashed at issue time. The plaintext codes leave the server exactly once — in the confirmation response — and are never stored or re-shown. Each code is single-use, atomically consumed on use so a concurrent login can’t double-spend it.
MFA failures feed the same lockout counter as password failures. An attacker who has your password but not your authenticator gets rate-limited the same way they would for password brute force.
PII encryption at rest — AES-256-GCM
Personally identifiable information submitted to our data-broker scrubbing flow (names, addresses, phone numbers) is encrypted with AES-256-GCM before the database write. The data encryption key lives in GCP Secret Manager and is fetched into memory at service startup — never written to disk, never logged.
A keyVersion column on every encrypted row tracks which generation of the key encrypted the data, so a future key rotation can re-encrypt without forcing users to re-submit.
Transport security — HSTS with preload
Every response from our origin ships with Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. Browsers refuse to connect over plain HTTP to any subdomain of securityindeed.org for two years from their first visit. We’re on the HSTS preload list, so that protection kicks in before any visit at all.
Cross-origin isolation
We set Cross-Origin-Opener-Policy: same-origin so hostile popups can’t reach into our window via window.opener. We set Cross-Origin-Resource-Policy: same-site so no third-party origin can embed our responses as scripts or images. Combined with X-Frame-Options: DENY and a strict CSPframe-ancestors 'none', the portal cannot be iframed, opened, or embedded by any other site.
Content Security Policy — per-request nonces
The CSP is built per-request with a fresh 128-bit random nonce. No unsafe-inline, nounsafe-eval, no wildcards. Only nonced scripts and strict-dynamic-allowed ones can execute. object-src 'none' blocks Flash and other legacy plugins. base-uri 'self' prevents<base> injection.
Rate limiting — per-session, not just per-IP
Sensitive endpoints — login, billing checkout, account deletion, MFA enrollment, PII submission — are capped at 5 requests per 15 minutes. When the user is authenticated, the bucket is keyed on an FNV-1a hash of the NextAuth session cookie instead of just the client IP, so an attacker who rotates X-Forwarded-For can’t earn fresh budgets while holding one session.
Audit logging — append-only, SOC 2-ready
Every authentication event, every state-changing API call, and every failure writes to an append-only audit log table. The log never stores PII in the metadata column — only opaque IDs and event types. On DSAR deletion, the user’s row is wiped but the audit log entries are preserved with the foreign key set to null, satisfying SOC 2 CC7.3 incident-detection evidence requirements without leaving the user’s personal data behind.
CSRF protection — origin check, not just token
Every state-changing POST, PATCH, and DELETE route validates the Origin and Referer headers against NEXTAUTH_URL. In production, if NEXTAUTH_URL is not set, the check fails closed — we refuse to trust the Host header even as a fallback, because Host headers are attacker-controlled behind some proxy configurations.
DSAR — one button, full deletion
Any authenticated user can delete their entire account via the account security page. The deletion flow calls every vendor-side delete endpoint first (data-broker scrubbing provider, billing provider), then cascades through every user-linked table in one Postgres transaction. Audit log entries survive with userId set to null. A retry of the whole flow always converges to fully-deleted — at-least-once vendor-side semantics.
Secrets management
Zero secrets live in source code or environment files committed to git. Every secret — database URL, NextAuth signing key, encryption key, vendor API keys — is stored in GCP Secret Manager and mounted into the Cloud Run runtime at deploy time. A CI job greps every client bundle for known secret patterns on every pull request; a regression would fail the build before it could ship.
Dependency version floors
CVE-gated version floors are enforced by CI:
next≥ 14.2.25 to close CVE-2025-29927 (middleware auth bypass)next-mdx-remote≥ 6.0.0 to close CVE-2026-0969 (RCE via JS expressions in MDX braces)
A downgrade of either package fails the pull request before it can merge.
Independent security audits
Every major feature ships with a self-audit plus an independent Mythos-style review run by a separate security-reviewer agent. The review reports are committed in the repo under docs/security/ so our reasoning about specific findings is auditable by anyone with access to the codebase.
What we don’t claim
We don’t have SOC 2 Type II certification yet. We don’t have a formal penetration test report on file. We don’t have a bug bounty program. These are all on the roadmap. Until we do, this page is the honest answer to “what do you actually do for security?” — a list of code-backed decisions you can verify in the source tree, not a badge you’d have to take on trust.