CORTEX
All projects

CampusSync

PROTOTYPE

Multi-tenant certificate verification for universities and recruiters, rebuilt after a security audit.

Personal project · Sep 2025 – Oct 2026 · 234 commits

THE PROBLEM

Certificates are checked by hand and are easy to forge, so recruiters can't trust the ones they receive.

HOW IT WORKS

  • Students upload certificates; Gemini vision extracts the fields and regular expressions normalise them; faculty approve.
  • Approved certificates are issued as signed credentials (RS256 JWS in a W3C-VC-shaped JSON, with a did:web identifier) with revocation and public verification. One verifyCredential() service answers every public endpoint and fails closed.
  • Roles live in one table (user_roles), credentials and the audit log are written only by the backend, and schema changes ship as staged migrations applied by a gated runner and replayed on PostgreSQL 17 in CI.

THE PIPELINE

  1. Student uploadThe student uploads a certificate.
  2. Gemini visionExtracts the fields; regular expressions normalise them.
  3. Faculty approvalA reviewer verifies or rejects.
  4. Signed credentialRS256 JWS in a W3C-VC-shaped JSON, with revocation.
  5. Public verificationAnyone can check a credential.
Only faculty-approved certificates are issued as credentials.

ENGINEERING EVIDENCE

  • 100 API route files. A test fails the build when a route is neither guarded nor on an explicit public list. route-inventory.test.ts ↗
  • 80 test files and 1,432 passing tests, run on main on 16 Oct 2026. They use in-memory fakes for the database.
  • A review of commit 91d0c77 (static reading plus targeted tests; not a penetration test and no outside reviewer) tracked 19 findings, 5 of them P1, in a public closure report. 17 are fixed in code and deployed with regression tests, one is partly fixed, and one needs an owner action outside the code. The P1s: every public credential verification failed, revocation was enforced on one path only, reviewers could approve their own certificates, and an admin of one organization could act on another. AUDIT-CLOSURE.md ↗
  • Credential lookup went from 1,172 ms to 0.207 ms (6 to 37,280 queries per second at about 280,000 credentials) by replacing a JSON scan with an indexed column. PostgreSQL 17, median of 3 rounds on a GitHub Actions runner, synthetic data, database time only. The production pending-list query is about 9 percent slower, and an insert speed-up from an earlier run did not reproduce and was withdrawn in writing. BENCHMARKS.md ↗
  • After the fixes: HttpOnly server-managed sessions, certificate files served by 5-minute signed URLs from a now-private bucket (an old public URL went from HTTP 200 to blocked), and a PostgreSQL-backed global rate limiter on the high-value endpoints (30 requests allowed, then 429 with Retry-After on the live site). AUDIT-CLOSURE.md ↗
  • An earlier Sep 2026 review of every route had already found and fixed real holes: role assignment and signup that did not require a session, credential issuing for arbitrary claims, unauthenticated document-status routes that used the service role, unsigned webhooks and open redirects. Dependency audit findings went from 71 to 2. safeRedirect.ts ↗

WHAT ISN'T DONE

  • Credentials are W3C-style (JWS, did:web), not a conformance-tested Verifiable Credentials implementation. The revocation status list, the transparency log and SD-JWT selective disclosure are implemented and unit-tested but switched off in production. Nothing here is zero-knowledge.
  • About 50 server files use the service-role client, which bypasses row-level security, so on those routes tenant isolation rests on the application checks alone. A stricter profiles policy migration is deliberately on hold.
  • There are no browser end-to-end tests, and a full live pass of the login, upload, review and recruiter flows is not finished. The migrations are tested against a model of the production schema, not against production itself, and the original baseline schema is still not in the repository.
  • Rate limiting is global only on the endpoints listed in the repository's rate-limiting document; the rest is per instance. There is no tracing, metrics or dashboards, only an optional alert webhook and a deep health check.
  • The recruiter and organization-admin invitation flow is incomplete: invited users stay pending. The Next.js major-version upgrade is pending.
  • The early version was much less secure than its README suggested; the public closure report says which claims were corrected.

NEXT STEPS

Each one comes from a gap listed above. It says what fixing the gap would take; it is not a promise.

  • Add browser end-to-end tests and finish the live pass of the login, upload, review and recruiter flows.
  • Move the remaining endpoints onto the global rate limiter.
  • Finish the invitation flow so invited users can be activated.
  • Take the stricter profiles row-level-security migration off hold once the service-role routes are narrowed.

STACK

  • Next.js
  • TypeScript
  • Supabase
  • PostgreSQL
  • Gemini
  • Vitest
  • GitHub Actions