Skip to main content

CommonGage Security Overview

Last updated: 2026-06-29

What CommonGage Is

CommonGage is a multi-tenant SaaS platform for school district engagement operations. It stores operational and staff data — event calendars, zone/school deliverable tracking, coordinator and building-staff accounts — for district employees. CommonGage does not store student records, grades, SSNs, or direct student PII.


Architecture

Runtime: Cloudflare Workers (V8 isolate, no persistent process, no OS). There is no server to SSH into, no filesystem, no network perimeter. Every request is stateless and isolated by the Cloudflare platform.

Database: Neon PostgreSQL (serverless, TLS-only). All connections use sslmode=require. The application connects as a least-privilege role (commongage_app) with no SUPERUSER, no BYPASSRLS, and no CREATEROLE attributes.

Frontend: Static SPA served from Cloudflare Pages. No server-side rendering; the API is the only data path.


Encryption

LayerImplementation
In transitTLS 1.2+ enforced by Cloudflare on all inbound and Neon on all DB connections
At restNeon encrypts data at rest (AES-256); Cloudflare encrypts Worker secrets at rest
PasswordsPBKDF2-HMAC-SHA256 @ 100,000 iterations with 16-byte random salt (Cloudflare Workers Web Crypto caps PBKDF2 at 100,000 iterations; OWASP 2024 recommends 600,000 but that is not supported on this runtime)
JWTsHS256 signed with a 32-byte random SESSION_SECRET stored as a Cloudflare Worker secret (never in wrangler.toml)

Multi-Tenant Isolation

All tenant data is stored in a single PostgreSQL database, isolated by Row-Level Security (RLS).

How it works:

  1. The application connects as commongage_app (PostgreSQL role with NOBYPASSRLS).
  2. Before any query against tenant-scoped tables, the application executes SET LOCAL app.current_tenant_id = '<uuid>' inside a transaction.
  3. Every tenant-scoped table has an RLS policy: USING (tenant_id = current_setting('app.current_tenant_id')::UUID).
  4. FORCE ROW LEVEL SECURITY is set on all 13 tenant-isolated tables, ensuring the policy applies even to DDL-level operations.

Verified: An integration test asserts that a query executed with Tenant B's context returns zero rows from a table owned by Tenant A.


Authentication & Authorization

SSO: ClassLink OAuth 2.0. Districts authenticate through their existing ClassLink launchpad. CommonGage validates the ClassLink TenantId against a pre-provisioned district mapping it controls; unknown tenant IDs are rejected with 403.

Password auth (staff accounts not provisioned via ClassLink): Invite-token flow. Admin sends invite; recipient sets password on first login. Passwords are never stored in plaintext.

JWT session tokens: HS256, 8-hour expiry, issued by the API and verified on every protected request via jose (WebCrypto-native, no hand-rolled crypto).

OAuth CSRF: The ClassLink redirect generates a 16-byte random state value stored as an HttpOnly; SameSite=Lax; Secure cookie. The callback validates the state parameter before making any external calls.

Authorization model: Three roles (org_admin, zone_coordinator, building_staff). Role is encoded in the JWT and re-enforced by the API on every request. zone_coordinator and building_staff access is further constrained by scope grants.

Rate limiting: POST /auth/login, POST /auth/invite/accept, and POST /internal/login are rate-limited via Cloudflare Workers Rate Limiting (10 requests/minute per IP).


CORS

The API allows requests only from https://commongage.com, https://www.commongage.com, and https://commongage.pages.dev. All other origins receive an empty Access-Control-Allow-Origin, blocking cross-origin reads in compliant browsers.


Secrets Management

All secrets (DATABASE_URL, SESSION_SECRET, CLASSLINK_CLIENT_SECRET, ANTHROPIC_API_KEY, APP_URL) are stored as Cloudflare Worker secrets (encrypted at rest, injected at runtime, never in source control or wrangler.toml).

The .dev.vars file (local development only) is in .gitignore and has never been committed.


AI / LLM Usage

CommonGage uses Anthropic Claude to classify calendar events by engagement type. The input is structured event data (title, description, dates) from school iCal feeds. No student PII is sent to Anthropic. The classifier's output (engagement_type, confidence score) informs UI display only; it makes no access-control decisions.

Calendar feeds are treated as untrusted external input. Parsed event content is stored as structured data and passed to the LLM as a fixed schema; the classifier cannot modify data outside its designated output fields.


Logging

Worker logs (accessible via Cloudflare dashboard) contain:

  • Request method, path, status code, duration (from Hono's logger middleware)
  • Error messages and HTTP status codes for failed external calls (ClassLink, iCal fetches)
  • Cron job summary statistics (source counts, event upsert counts)

Logs do not contain: JWT tokens, passwords, password hashes, auth codes, database credentials, or any field that could identify an individual student.