Ray Davis Portfolio
Security & CI/CD engineering — portfolio summary.
A genericized writeup of the security controls and delivery pipeline I designed and shipped for a production multi-tenant SaaS platform (Next.js App Router on Firebase / Google Cloud, serving multiple isolated business tenants from one codebase). Project and brand names are omitted intentionally; mechanisms and patterns are real.
At a glance
Security
- Deny-by-default API authorization across 150+
endpoints. Built a single
withRoutewrapper that every API handler must use, declaring a typed access level (public / authenticated user / tenant member / specific permission / tenant owner / superadmin / machine-cron / machine-webhook) plus automatic tenant-scoping. Why it matters: makes “accidentally unprotected” impossible and directly addresses broken access control — the #1 category in the OWASP Top 10. - Turned the audit into an enforced invariant. A CI gate fails the build if any new route ships without a declared access level. Why: stops access-control regressions as the team and surface area grow — security that can’t silently rot.
- Full route authorization audit + remediation. Classified every endpoint by required access and fixed 6 critical tenant-scoped mutation gaps. Why: closed cross-tenant data-tampering holes before launch.
- Least-privilege RBAC with step-up auth. Base role + per-tenant membership role + granular permission grants and permission groups, plus per-permission re-authentication and approval gates (fresh-session enforcement) on sensitive actions. Why: minimizes blast radius and forces re-verification for high-risk operations.
- Enforced multi-tenant isolation in the database layer. Default-deny Firestore Security Rules with server-resolved tenant context, continuously proven by an automated rules-test suite that runs against the Firestore emulator in CI. Why: one tenant can never read or write another tenant’s data — and it’s verified on every PR, not assumed.
- Secrets moved out of the database into Google Secret Manager, with per-tenant isolation and least-privilege IAM. Why: credentials become rotatable, access-controlled, and never co-located with application data.
- Hardened HTTP responses. Per-request nonce
Content-Security-Policy (
strict-dynamic,object-src 'none', nounsafe-eval) plus HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy. Why: layered defense against XSS, clickjacking, and MIME-sniffing. - Passwordless, phishing-resistant authentication. Removed passwords entirely. Sign-up and sign-in are a single phone-first page (one rate-limited existence check branches new vs. returning), backed by SMS/TOTP multi-factor authentication, with an email magic-link as the account-recovery path. Sensitive admin actions now require an SMS step-up re-verification instead of a password re-prompt. Every sign-up is bound to its tenant via a formal customer-membership record kept isolated from staff/admin tooling. Why it matters: there’s no password store to breach, dump, or phish; recovery doesn’t hinge on a shared secret; and high-risk operations force a fresh second-factor challenge — not a reusable password.
- Enforced second factor for operators. Admin-capable
accounts (staff/admin/owner/superadmin) are nudged and then, after a
grace window, hard-gated to a dedicated enrollment page until they enrol
a second factor. The gate fails open on any lookup
error and is environment-flagged
(
off/grace/hard) so it can’t lock the team out before the identity-platform upgrade that makes enrollment possible. Why it matters: the highest-privilege accounts get the strongest authentication — delivered as a safe, reversible rollout rather than a big-bang lockout risk. - Detection and auditability. Structured security-event logging with Google Cloud-native email alerting and a scheduled daily security report. Why: you can’t respond to what you can’t see.
- Supply-chain & repository security. Automated secret scanning (gitleaks) in CI, Dependabot for dependency CVEs, and a clean fresh-history repository migration to purge a previously-committed credential from Git history. Why: prevent secret leakage going forward and remove already-leaked material from the past.
CI/CD & delivery
- Multi-stage CI gating every pull request: lint, type-check, unit tests with enforced coverage floors, the route-access security gate, production build, Cloud Functions build, Firestore rules tests, browser E2E smoke, and secret scan. Why: nothing reaches the main branch without clearing the full quality + security bar.
- Made the gate unbypassable via branch protection on the main branch — all checks required, no force-push, no branch deletion. Why: process guarantees only hold if they can’t be skipped.
- Browser-level E2E smoke (Playwright) asserting the unauthenticated auth redirect and the actual security headers served over the wire. Why: validates the full middleware → HTTP stack a unit test can’t reach.
- Hands-free scheduled production canary that exercises real phone-auth sign-in against the deployed environment nightly (using provider test numbers, so no real SMS). Why: continuous “is production actually working end-to-end” signal with no manual steps.
- Environment-parity automation that checks config drift between production and staging and re-verifies the live security headers post-deploy. Why: keeps two environments in lockstep and confirms headers survive the CDN/edge.
- Two-environment delivery (production + staging/demo) on managed container hosting, with documented rollout and rollback runbooks. Why: safe, reversible progressive delivery.
- Pipeline efficiency without cutting coverage: parallelized jobs, auto-cancellation of superseded runs, heavy jobs gated to PRs only, and the production build artifact shared with the E2E job instead of rebuilt. Why: faster feedback while keeping every check.
Technical appendix
Authentication & sessions
- Server-side Firebase session cookies (httpOnly, secure, SameSite) as the primary auth mechanism; short-lived ID tokens exchanged server-side.
- Cookie revocation checked on every request so a disabled/role-changed account loses access immediately; refresh tokens revoked on privilege changes.
- Generic auth error responses (no account-existence or reason leakage).
- Passwordless: phone-first (SMS) sign-in on a single
unified page — no passwords, no email/password, no federated provider.
Email verification and a passwordless email magic-link
for recovery; MFA via TOTP and SMS with a sign-in
resolver for the
multi-factor-auth-requiredchallenge; SMS step-up re-authentication gates sensitive admin actions in place of a password re-prompt. - Operator MFA enrollment gate for admin-capable
roles: a per-user grace clock escalates from an in-app nudge to a hard
redirect to a dedicated enrollment page. Fail-open on lookup errors and
controlled by an
off/grace/hardenvironment flag so it can’t lock operators out before MFA is provisionable.
Authorization (deny-by-default + RBAC)
withRoute(options, handler)wraps every API route; access kinds are typed and tenant source is explicit (cookie / path / hostname / none). Cross-tenant requests are rejected and logged as security events.- A standalone script (run in CI) asserts every
route.tsdeclares access viawithRouteor an explicit, reasoned exemption comment — enforcing, not advisory. - RBAC resolves effective permissions from (assigned groups ∪ direct grants) − revocations; owner/admin get implicit full access; staff are granted explicitly. Sensitive actions additionally require a fresh session (re-auth) and/or approval per a configurable policy.
Multi-tenant isolation
- Firestore Security Rules: a default-deny catch-all, server-resolved tenant config (clients can’t assert their own tenant), and write-locks on shared catalog/public content. Sensitive collections are Admin-SDK-only (no direct client access).
- Tenant isolation is covered by an emulator-backed rules test suite in CI plus an authorization-matrix test that probes each route’s required access.
Secrets management
- No secrets in source control (
.env*gitignored; gitleaks enforces it in CI). - Google Secret Manager is the canonical store; per-tenant third-party credentials (payments, email) are isolated and reach the runtime via the hosting platform’s secret injection — never the client bundle.
Input validation & API hardening
- Zod schema validation on all write paths; auth check precedes any data access; rate limiting on high-value endpoints; no internal error details returned to clients; App Check on payment endpoints.
HTTP security headers / CSP
- Per-request nonce CSP with
strict-dynamicand a tight allowlist (auth/reCAPTCHA, payments) — nounsafe-eval,object-src 'none',base-uri 'self',form-action 'self'. Baseline headers (HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) set in middleware and verified live post-deploy.
Security testing (four layers)
- Firestore/Storage Rules tests (emulator) — tenant isolation and default-deny.
- Authorization-matrix tests — every route’s access kind + permission.
- Input-validation tests — schema rejection on write paths.
- Browser E2E (Playwright) — auth redirect + real security headers over HTTP. Plus a scheduled live auth canary against the deployed site.
Monitoring, logging & alerting
- Structured security-event logging (auth denials, cross-tenant attempts, privilege changes); Google Cloud-native metric/log alerts emailed to owners; a daily security report; a service-inventory/monitoring catalog with live status.
Supply-chain & repository security
- gitleaks (CI), Dependabot (CVEs), branch protection with required checks, and a fresh-history repo migration to eliminate a leaked key from all reachable history.
CI pipeline (jobs)
Lint · Type check · Unit tests
(coverage floors enforced) · Route access gate ·
Next.js build · Cloud Functions build ·
Firestore rules tests · E2E smoke (Playwright)
· Secret scan (gitleaks) — parallelized,
cancel-in-progress on superseded runs, heavy jobs gated to
PRs, build artifact reused by E2E. All required by branch protection on
the main branch.
Deployment & environments
- Managed container hosting (Cloud Build → Cloud Run) across production and staging/demo; per-environment secret injection; rollout/rollback and DNS-cutover runbooks; an env-parity workflow guarding config + region consistency.
Representative incident-style fixes
- Diagnosed and fixed a CSP directive blocking
reCAPTCHA that broke phone-auth in the browser — traced from
console errors to the exact
frame-src/connect-srcgaps, patched with a unit-tested header change, and verified the corrected header live in production. - Resolved an authorized-domains misconfiguration
(
auth/captcha-check-failed) by reading the live Identity Platform config via the admin API and adding the serving hostnames — a fix grounded in the actual deployed state rather than guesswork.