Security
Security pages usually mean "trust us." This one means "check the file." Every claim below links to the code, the test, or the scan that backs it up, plus a section on what we don't have yet. If you can't verify a claim on a vendor's security page, that's the page you should trust less, not more.
Live scores (verified 2026-07-11)
Run live against this site on 2026-07-11, mobile emulation,
simulated throttling. Raw report: docs/audits/LIGHTHOUSE_2026-07-11.json . We'll re-run and re-publish quarterly, or after any change
that could plausibly move the numbers.
- Lighthouse (mobile): Performance 92, Accessibility 100, Best Practices 100, SEO 100. LCP 2.7s, CLS 0, total blocking time 0ms.
- axe-core: 0 WCAG 2.1 A/AA violations across all five public pages (home, services, work, about, contact), scanned live the same day.
- CI:
required status checks gate merges to
mainon our three production-facing repos (portal, board runner, and the Lumen demo). The remaining portfolio-demo repos run the same CI but don't yet require it to pass before merge; that's a gap we're closing, not a claim we're dodging.
Tenant isolation
The customer portal uses a two-gate model for what a user can see,
and it's a pure function, not a pile of scattered if
checks: visibleAppIds() in src/lib/access.ts
(portal-shell repo). An app shows up for a customer-role user only
if both gates pass:
- Gate 1: the user's company (tenant) has that app assigned.
- Gate 2: an admin has individually flipped that specific user's access on for that app.
Losing either gate hides the app. Staff accounts bypass both by
design (they need to see everything to run support); internal
accounts only ever see internal-category apps, full stop. 19 of
19 tests pass against this logic (
src/lib/access.test.ts).
And it's not just the UI trusting itself: every admin server
action in src/app/admin/actions.ts
calls a requireStaff() check independently, in the
function body, not just at the page-layout level. That closes the
classic Next.js hole where a server action gets called directly,
skipping whatever gate the page around it had.
Auth and SSO
Cross-app SSO into the portal's tiles runs on signed JWTs, not shared cookies or a homegrown token scheme.
-
Signing keys are RS256, RSA-2048 (
src/lib/signing-keys.ts). -
Private keys are encrypted at rest with AES-256-GCM before they
touch the database (
src/lib/encryption.ts), keyed by an encryption key that lives in Infisical, not in the repo or the container image. -
The JWKS endpoint (
/.well-known/jwks.json) serves the active key plus the previous one during rotation, cached for 60 minutes, so an app checking tokens never gets caught mid-rotation with a key it doesn't recognize yet. -
Every token carries issuer, audience, and expiry, and every
verification checks all three (
src/lib/jwt-signing.ts,src/lib/portal-check.ts). A token minted for one app can't be replayed into another. -
Login failures are deliberately generic (
src/lib/auth-logic.ts): wrong password, no such account, and deactivated account all return the same "Invalid email or password" message. No enumeration surface from the error text.
Data hygiene
-
Database access runs through Drizzle with parameterized
queries everywhere except one scheduled cleanup job, which uses
a hardcoded table/column map (not user input) plus an explicit
integer cast on the one numeric value it interpolates (
src/db/cleanup-audit.ts). -
Public API routes validate their bodies with Zod, enforce an
origin allowlist (including explicitly rejecting a bare
nullorigin), and rate-limit by IP and by email on signup and portal-check endpoints. - No committed secrets: we pattern-scanned every tracked file and the full git history across our six active repos for API keys, private key blocks, and credentialed connection strings. Nothing turned up. Worth being precise about what that means: it's a pattern scan we ran ourselves, not a certified third-party audit, and it won't catch something cleverly obfuscated. We're not overselling it.
-
Dependency hygiene: GitHub's native vulnerability alerts and
automated security fixes are on across all six repos, and
delete_branch_on_mergeis on everywhere so merged branches don't pile up as forgotten attack surface. -
The portal runs Next.js 14.2.35 in production, which sits above
the patched line for CVE-2025-29927 (the middleware
auth-bypass CVE).
DEMO_MODEis pinnedfalseandNODE_ENVis pinnedproductionin the prod build, after an earlier incident where demo mode leaking into a real deploy was exactly the kind of mistake this page exists to make less likely.
What we don't have yet
This is the part most vendor security pages skip, and it's the part worth reading first if you're deciding whether to trust the rest of it.
- No SOC 2 or other formal compliance certification yet.
- No third-party penetration test yet. Everything above is self-audited and code-reviewed, but nobody outside the studio has been paid to try to break it.
- Tenant onboarding is still a manual, white-glove process for the first customers, not a self-serve signup flow.
- This is a solo-engineer shop. That's a real bus-factor risk and worth weighing against whatever else is on this page, not something we'd rather you find out later.
Report a vulnerability
Found something? Email [email protected]
directly. Don't open a public GitHub issue for it.
- Include the affected URL or app, steps to reproduce, and the impact if you can.
- We'll acknowledge within 2 business days and give you a straight timeline, not a form letter.
- Give us a reasonable window to fix it before any public disclosure.
- Good-faith testing that doesn't touch real customer data, doesn't degrade the service, and stays inside the scope you reported won't get you a legal threat from us.